ご無沙汰しています。id:rokujyouhitomaです。
さてはて、PythonのStringIOモジュールをJavaScriptで実装しました。外部仕様を一緒にしたのではなく、内部仕様(プログラミング仕様)まで一緒にしました。
動機は使う予定があったから。なくても別にコーディングできるけど、抽象化したかったんで。
元コードはPyPy(Python2.7)のStringIO。
コード
書いたコードはgithubにpushしました。
前職のRTM以来ClosureCompilerにハマっているので、ClosureCompiler向けのコードを書いています。
引用すると下記の通り。(注記。私独自のutili関数に依存してるので、万が一使おうとする方はgithubのコードを引用してください。)
/** * StringIO * @see Python <a href='http://docs.python.org/library/stringio.html'>StringIO</ a> modules. */ /** @const */ var EINVAL = 22; /** * @param {boolean} closed . */ function _complain_ifclosed(closed) { if (closed) { throw new ValueError('I/O operation on closed file'); } } /** * StringIO is based on Python StringIO build-in modules. * @param {?string=} buf . * @constructor * @extends {Object} */ var StringIO = function(buf) { base(this); buf = buf ? buf : ''; this.buf = buf; this.len = buf.length; this.buflist = []; this.pos = 0; this.closed = false; this.softspace = 0; }; inherits(StringIO, Object); /** * return {StringIO} . */ StringIO.prototype.__iter__ = function() { return this; }; /** * @return {string} read line. */ StringIO.prototype.next = function() { _complain_ifclosed(this.closed); var r = this.readline(); if(!r) { throw new StopIteration(); //return new StopIteration(); } return r; }; StringIO.prototype['next'] = StringIO.prototype.next; /** * Free the memory buffer. */ StringIO.prototype.close = function() { if (!this.closed) { this.closed = true; delete this.buf; delete this.pos; } }; StringIO.prototype['close'] = StringIO.prototype.close; /** * Returns false because StringIO objects are not connected to a tty-like * device. */ StringIO.prototype.isatty = function() { _complain_ifclosed(this.closed); return false; }; StringIO.prototype['isatty'] = StringIO.prototype.isatty; /** * Set the file's current position. * * The mode argument is optional and defaults to 0 (absolute file * positioning); other values are 1 (seek relative to the current * position) and 2 (seek relative to the file's end). * There is no return value. */ StringIO.prototype.seek = function(pos, mode) { mode = mode ? mode : 0; _complain_ifclosed(this.closed); if (this.buflist) { this.buf += this.buflist.join(''); this.buflist = []; } if (mode === 1) { pos += this.pos; } else if (mode === 2) { pos += this.len; } this.pos = buildin.max(0, pos); }; StringIO.prototype['seek'] = StringIO.prototype.seek; /** * Return the file's current position. */ StringIO.prototype.tell = function(pos, mode) { mode = mode ? mode : 0; _complain_ifclosed(this.closed); return this.pos; }; StringIO.prototype['tell'] = StringIO.prototype.tell; /** * */ StringIO.prototype.read = function(n) { n = n ? n : -1; _complain_ifclosed(this.closed); if(this.buflist) { this.buf += this.buflist.join(''); this.buflist = []; } var newpos = null; if (n === null || n < 0) { newpos = this.len; } else { newpos = buildin.min(this.pos + n, this.len); } var r = this.buf.slice(this.pos, newpos); this.pos = newpos; return r; }; StringIO.prototype['read'] = StringIO.prototype.read; /** * @param {?number=} length . * @return {string} . */ StringIO.prototype.readline = function(length) { length = length ? length : null; _complain_ifclosed(this.closed); if (this.buflist) { this.buf += this.buflist.join(''); this.buflist = []; } var i = string.find(this.buf, '\n', this.pos); var newpos = null; if (i < 0) { newpos = this.len; } else { newpos = i + 1; } if (!(length === null) && length > 0) { if ((this.pos + length) < newpos) { newpos = this.pos + length; } } var r = this.buf.slice(this.pos, newpos); this.pos = newpos; return r; }; StringIO.prototype['readline'] = StringIO.prototype.readline; /** * @param {number} sizehint . */ StringIO.prototype.readlines = function(sizehint) { sizehint = sizehint ? sizehint : 0; var total = 0; var lines = []; var line = this.readline(); while (line) { lines.push(line); total += line.length; if (0 < sizehint && sizehint <= total) { break; } line = this.readline(); } return lines; }; StringIO.prototype['readlines'] = StringIO.prototype.readlines; /** * @param {?number=} size . */ StringIO.prototype.truncate = function(size) { size = size ? size : null; _complain_ifclosed(this.closed); if (size === null) { size = this.pos; } else if (size < 0) { throw new IOError(EINVAL, 'Negative size not allowed'); } else if (size < this.pos) { this.pos = size; } this.buf = this.getvalue().slice(0, size); this.len = size; }; StringIO.prototype['truncate'] = StringIO.prototype.truncate; /** * @param {string} s . */ StringIO.prototype.write = function(s) { //Write a string to the file. //There is no return value. _complain_ifclosed(this.closed); if (s === '') { return; } var spos = this.pos; var slen = this.len; if (spos === slen) { this.buflist.push(s); this.len = this.pos = spos + s.length; return; } if (spos > slen) { var str; var i; var len = spos - slen; for (i = 0; i < len; i++) { str += "\0"; } this.buflist.push(str); slen = spos; } var newpos = spos + s.length; if (spos < slen) { if (this.buflist) { this.buf += this.buflist.join(''); } this.buflist = [this.buf.substring(0, spos), s, this.buf.substring(newpos, s.length)]; this.buf = ''; if (newpos > slen) { slen = newpos; } } else { this.buflist.push(s); slen = newpos; } this.len = slen; this.pos = newpos; }; StringIO.prototype['write'] = StringIO.prototype.write; /** * @param {Array|Object} iterable . */ StringIO.prototype.writelines = function(iterable) { var key; var line; for (key in iterable) { line = iterable[key]; this.write(line); } }; StringIO.prototype['writelines'] = StringIO.prototype.writelines; /** * */ StringIO.prototype.flush = function() { _complain_ifclosed(this.closed); }; StringIO.prototype['flush'] = StringIO.prototype.flush; /** * @return {string} . */ StringIO.prototype.getvalue = function() { if (this.buflist) { this.buf += this.buflist.join(''); this.buflist = []; } return this.buf; }; StringIO.prototype['getvalue'] = StringIO.prototype.getvalue;
テストコード
当然テストコードもPyPyプロジェクトのリポジトリにあったテストコードをJasmineのテストで書き直しました。
/* * @see pypy-1.7/lib-python/2.7/test/test_StringIO.py */ var StopIteration = require('../src/template/template').StopIteration; var StringIO = require('../src/template/template').StringIO; describe('TestGenericStringIO', function() { var _line = ''; var _lines = ''; var _fp; beforeEach(function(){ _line = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!'; for (var i = 0; i < 5; ++i) { _lines += _line + '\n'; } _fp = new StringIO(_lines); }); afterEach(function(){ _line = ''; _lines = ''; _fp = null; }); it('test_reads', function(){ //expect(f.seek()).toThrow(); expect(_fp.read(10)).toEqual(_line.slice(0, 10)); expect(_fp.readline()).toEqual(_line.slice(10) + '\n'); expect(_fp.readlines(60).length).toEqual(2); _fp.seek(0); expect(_fp.readline(-1)).toEqual(_line + '\n'); }); it('test_writes', function(){ var f = new StringIO(); //expect(f.seek()).toThrow(); f.write(_line.slice(0, 6)); f.seek(3); f.write(_line.slice(20, 26)); f.write(_line[52]); expect(f.getvalue()).toEqual('abcuvwxyz!'); }); it('test_writelines', function(){ var f = new StringIO(); f.writelines([_line[0], _line[1], _line[2]]); f.seek(0); expect(f.getvalue()).toEqual('abc'); }); it('test_truncate', function(){ var f = new StringIO(); expect(f.closed).toBeFalsy(); f.close(); expect(f.closed).toBeTruthy(); f = new StringIO('abc'); expect(f.closed).toBeFalsy(); f.close(); expect(f.closed).toBeTruthy(); }); if('test_isatty', function(){ var f = new StringIO(); expect(f.isatty()).toBeFalsy(); f.close(); }); it('test_iterator', function(){ expect('__iter__' in _fp).toBeTruthy(); expect('next' in _fp).toBeTruthy(); var i = 0; var line; while (true) { try { line = _fp.next(); } catch (e) { if (e instanceof StopIteration){ break; } } i += 1; } expect(i).toEqual(5); _fp.close(); }); });
現状、このテストコードはクリアしてます。
まとめ
JSはPythonに比べて組み込み関数がしょぼいのが残念です。