自作のモジュール、base62.js ver0.4.1をTypedCoffeeScriptで書き直してみました。
セットアップ
とりあえずTypedCoffeeScriptをインストールするので、package.jsonにtyped-coffee-scriptを追加します。ついでにnpm-scriptを実行するとコンパイルできるようにしておきます。
commit cbfc1b149e023cea1407f21cf56941cc0f216e3c
Author: sasaplus1 <sasaplus1@gmail.com>
Date: Sat Nov 23 14:29:26 2013 +0900
appended base62.tc.js
diff --git a/package.json b/package.json
index f434cfe..4de1497 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
},
"scripts": {
"bower": "bower install",
+ "c": "tcoffee --bare --js --output ./base62.tc.js --input ./coffee/base62.coffee",
"fix": "fixjsstyle --strict --nojsdoc -r ./lib -r ./test ./index.js",
"lint": "gjslint --strict --nojsdoc -r ./lib -r ./test ./index.js",
"mini": "uglifyjs ./lib/base62.js --comments -cm -d 'module=void 0' -r base62 -o ./base62.min.js",
@@ -24,6 +25,7 @@
"expect.js": "~0.2",
"mocha": "~1.14",
"testem": "~0.5",
+ "typed-coffee-script": "~0.8",
"uglify-js": "~2.4"
}
}
$ mkdir ./coffee
$ touch ./coffee/base62.coffee
$ npm i
これで確かセットアップは大丈夫。コンパイルするときは
$ npm run-script c
です。
コードを書く
coffee/base62.coffee
do (global :: Object = this) ->
isInteger :: Number -> Boolean = Number.isInteger or (value :: Number) ->
isFinite(value) and value is Math.floor(value)
createConverter :: String -> Base62 = (table :: String) ->
base62 :: Base62 = if arguments.length <= 0 then new Base62 else new Base62(table)
#base62.createConverter :: String -> Base62 = createConverter
##base62.createConverter :: String -> Base62
##base62.createConverter = createConverter
base62.createConverter = createConverter
base62
class Base62
table_ :: String
createConverter :: String -> Base62
constructor: (table :: String) ->
if arguments.length <= 0
table = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
else if table.length isnt 62
throw new Error 'parameter must be 62 chars'
@table_ = table
decode: (str :: String) ->
throw new Error "parameter is unsupported format: #{str}" if not /^-?[\da-zA-Z]+$/.test str
isNegative :: Boolean = str[0] is '-'
str = str.slice 1 if isNegative
table :: String = @table_
decodedNum :: Int = 0
i :: Int = str.length
len :: Int = str.length
while i--
decodedNum += table.indexOf(str[i]) * Math.pow 62, len - i - 1
if isNegative then decodedNum *= -1 else decodedNum
encode: (num :: Int) ->
throw new Error "parameter is not an integer: #{num}" if not isInteger num
return '0' if num is 0
isNegative :: Boolean = num < 0
num = Math.abs num
table :: String = @table_
encodedStr :: String = ''
while num > 0
encodedStr = table.charAt(num % 62) + encodedStr
num = Math.floor num / 62
if isNegative then "-#{encodedStr}" else encodedStr
if module?
module.exports ###:: Base62### = createConverter()
if process.env.NODE_ENV is 'test'
module.exports.Base62_ = Base62
module.exports.isInteger_ = isInteger
else
global.base62 ###:: Base62### = createConverter()
if Mocha?
global.base62.Base62_ = Base62
global.base62.isInteger_ = isInteger
return
こんな感じでしょうか。勘違いしてるところもままあるような。
さすがはCoffeeScriptで、コード量がだいぶ削減されたと思います。
型の無い生活に慣れたので意外と辛かった……
あと初めてCoffeeScriptでクラスを書いたような。
コンパイルしてみる
$ npm run-script c
でコンパイルしたJavaScriptが以下です。
// Generated by CoffeeScript 0.8.3
(function (global) {
var Base62, createConverter, isInteger;
isInteger = Number.isInteger || function (value) {
return isFinite(value) && value === Math.floor(value);
};
createConverter = function (table) {
var base62;
base62 = arguments.length <= 0 ? new Base62 : new Base62(table);
base62.createConverter = createConverter;
return base62;
};
Base62 = function () {
function Base62(table) {
if (arguments.length <= 0) {
table = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
} else if (table.length !== 62) {
throw new Error('parameter must be 62 chars');
}
this.table_ = table;
}
Base62.prototype.decode = function (str) {
var decodedNum, i, isNegative, len, table;
if (!/^-?[\da-zA-Z]+$/.test(str))
throw new Error('parameter is unsupported format: ' + str);
isNegative = str[0] === '-';
if (isNegative)
str = str.slice(1);
table = this.table_;
decodedNum = 0;
i = str.length;
len = str.length;
while (i--) {
decodedNum += table.indexOf(str[i]) * Math.pow(62, len - i - 1);
}
if (isNegative) {
return decodedNum *= -1;
} else {
return decodedNum;
}
};
Base62.prototype.encode = function (num) {
var encodedStr, isNegative, table;
if (!isInteger(num))
throw new Error('parameter is not an integer: ' + num);
if (num === 0)
return '0';
isNegative = num < 0;
num = Math.abs(num);
table = this.table_;
encodedStr = '';
while (num > 0) {
encodedStr = table.charAt(num % 62) + encodedStr;
num = Math.floor(num / 62);
}
if (isNegative) {
return '-' + encodedStr;
} else {
return encodedStr;
}
};
return Base62;
}();
if ('undefined' !== typeof module && null != module) {
module.exports = createConverter();
if (process.env.NODE_ENV === 'test') {
module.exports.Base62_ = Base62;
module.exports.isInteger_ = isInteger;
}
} else {
global.base62 = createConverter();
if ('undefined' !== typeof Mocha && null != Mocha) {
global.base62.Base62_ = Base62;
global.base62.isInteger_ = isInteger;
}
}
}(this));
ユニットテスト
ついでにユニットテストに通してみたり。
commit cbfc1b149e023cea1407f21cf56941cc0f216e3c
Author: sasaplus1 <sasaplus1@gmail.com>
Date: Sat Nov 23 14:29:26 2013 +0900
appended base62.tc.js
diff --git a/test/test-base62.js b/test/test-base62.js
index 2e1dff3..8e3716d 100644
--- a/test/test-base62.js
+++ b/test/test-base62.js
@@ -2,7 +2,7 @@ var expect, base62;
if (typeof module !== 'undefined') {
expect = require('expect.js');
- base62 = require('../');
+ base62 = require('../base62.tc.js');
} else {
expect = this.expect;
base62 = this.base62;
例外が投げられるかどうかのテストは当然失敗しますが、それ以外はちゃんと通ってました。
$ npm ts
> base62.js@0.4.1 test /Users/sasaplus1/Repos/git/base62.coffee
> NODE_ENV=test mocha
base62
#createConverter()
✓ should return Base62 instance
#decode()
✓ should throw error if parameter is not a integer
1) should throw error if parameter type is not a string
✓ should convert to integer from base62 string
#encode()
✓ should throw error if parameter is not a integer
2) should throw error if parameter type is not a number
✓ should convert to base62 string from integer
#Base62()
3) should throw error if parameter is not a string
✓ should throw error if parameter is not 62 chars
✓ should has default table
✓ should set another table
#isInteger()
✓ should return false if parameter is not a number
✓ should return false if parameter is not a finite number
✓ should return false if parameter is floating-point number
✓ should return true if parameter is integer
index
✓ should export some functions
13 passing (105ms)
3 failing
1) base62 #decode() should throw error if parameter type i 1) base62 #decode() should throw errn to throw an exception
at Assertion.assert (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:99:13)
at Assertion.throwError.Assertion.throwException (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:155:10)
at Context.<anonymous> (/Users/sasaplus1/Repos/git/base62.coffee/test/test-base62.js:50:23)
at Test.Runnable.run (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runnable.js:211:32)
at Runner.runTest (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:358:10)
at /Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:404:12
at next (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:284:14)
at /Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:293:7
at next (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:237:23)
at Object._onImmediate (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:261:5)
at processImmediate [as _immediateCallback] (timers.js:330:15)
2) base62 #encode() should throw error if parameter type is not a number:
Error: expected {} to be an instance of TypeError
at Assertion.assert (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:99:13)
at Assertion.a.Assertion.an [as a] (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:261:12)
at Function.a (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:480:17)
at fn (/Users/sasaplus1/Repos/git/base62.coffee/test/test-base62.js:97:25)
at Assertion.throwError.Assertion.throwException (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:136:9)
at Context.<anonymous> (/Users/sasaplus1/Repos/git/base62.coffee/test/test-base62.js:100:25)
at Test.Runnable.run (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runnable.js:211:32)
at Runner.runTest (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:358:10)
at /Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:404:12
at next (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:284:14)
at /Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:293:7
at next (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:237:23)
at Object._onImmediate (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:261:5)
at processImmediate [as _immediateCallback] (timers.js:330:15)
3) base62 #Base62() should throw error if parameter is not a string:
Error: expected {} to be an instance of TypeError
at Assertion.assert (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:99:13)
at Assertion.a.Assertion.an [as a] (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:261:12)
at Function.a (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:480:17)
at fn (/Users/sasaplus1/Repos/git/base62.coffee/test/test-base62.js:130:25)
at Assertion.throwError.Assertion.throwException (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/expect.js/expect.js:136:9)
at Context.<anonymous> (/Users/sasaplus1/Repos/git/base62.coffee/test/test-base62.js:133:23)
at Test.Runnable.run (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runnable.js:211:32)
at Runner.runTest (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:358:10)
at /Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:404:12
at next (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:284:14)
at /Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:293:7
at next (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:237:23)
at Object._onImmediate (/Users/sasaplus1/Repos/git/base62.coffee/node_modules/mocha/lib/runner.js:261:5)
at processImmediate [as _immediateCallback] (timers.js:330:15)
npm ERR! weird error 3
npm ERR! not ok code 0
$ git show
commit cbfc1b149e023cea1407f21cf56941cc0f216e3c
Author: sasaplus1 <sasaplus1@gmail.com>
Date: Sat Nov 23 14:29:26 2013 +0900
appended base62.tc.js
diff --git a/base62.tc.js b/base62.tc.js
new file mode 100644
index 0000000..5d4500d
--- /dev/null
+++ b/base62.tc.js
@@ -0,0 +1,77 @@
+// Generated by CoffeeScript 0.8.3
+(function (global) {
+ var Base62, createConverter, isInteger;
+ isInteger = Number.isInteger || function (value) {
+ return isFinite(value) && value === Math.floor(value);
+ };
+ createConverter = function (table) {
+ var base62;
+ base62 = arguments.length <= 0 ? new Base62 : new Base62(table);
+ base62.createConverter = createConverter;
+ return base62;
+ };
+ Base62 = function () {
+ function Base62(table) {
+ if (arguments.length <= 0) {
+ table = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ } else if (table.length !== 62) {
+ throw new Error('parameter must be 62 chars');
+ }
+ this.table_ = table;
+ }
+ Base62.prototype.decode = function (str) {
+ var decodedNum, i, isNegative, len, table;
+ if (!/^-?[\da-zA-Z]+$/.test(str))
+ throw new Error('parameter is unsupported format: ' + str);
+ isNegative = str[0] === '-';
+ if (isNegative)
+ str = str.slice(1);
+ table = this.table_;
+ decodedNum = 0;
+ i = str.length;
+ len = str.length;
+ while (i--) {
+ decodedNum += table.indexOf(str[i]) * Math.pow(62, len - i - 1);
+ }
+ if (isNegative) {
+ return decodedNum *= -1;
+ } else {
+ return decodedNum;
+ }
+ };
+ Base62.prototype.encode = function (num) {
+ var encodedStr, isNegative, table;
+ if (!isInteger(num))
+ throw new Error('parameter is not an integer: ' + num);
+ if (num === 0)
+ return '0';
+ isNegative = num < 0;
+ num = Math.abs(num);
+ table = this.table_;
+ encodedStr = '';
+ while (num > 0) {
+ encodedStr = table.charAt(num % 62) + encodedStr;
+ num = Math.floor(num / 62);
+ }
+ if (isNegative) {
+ return '-' + encodedStr;
+ } else {
+ return encodedStr;
+ }
+ };
+ return Base62;
+ }();
+ if ('undefined' !== typeof module && null != module) {
+ module.exports = createConverter();
+ if (process.env.NODE_ENV === 'test') {
+ module.exports.Base62_ = Base62;
+ module.exports.isInteger_ = isInteger;
+ }
+ } else {
+ global.base62 = createConverter();
+ if ('undefined' !== typeof Mocha && null != Mocha) {
+ global.base62.Base62_ = Base62;
+ global.base62.isInteger_ = isInteger;
+ }
+ }
+}(this));
diff --git a/coffee/base62.coffee b/coffee/base62.coffee
new file mode 100644
index 0000000..04cd6aa
--- /dev/null
+++ b/coffee/base62.coffee
@@ -0,0 +1,71 @@
+do (global :: Object = this) ->
+
+ isInteger :: Number -> Boolean = Number.isInteger or (value :: Number) ->
+ isFinite(value) and value is Math.floor(value)
+
+ createConverter :: String -> Base62 = (table :: String) ->
+ base62 :: Base62 = if arguments.length <= 0 then new Base62 else new Base62(table)
+ #base62.createConverter :: String -> Base62 = createConverter
+ ##base62.createConverter :: String -> Base62
+ ##base62.createConverter = createConverter
+ base62.createConverter = createConverter
+ base62
+
+ class Base62
+
+ table_ :: String
+ createConverter :: String -> Base62
+
+ constructor: (table :: String) ->
+ if arguments.length <= 0
+ table = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ else if table.length isnt 62
+ throw new Error 'parameter must be 62 chars'
+ @table_ = table
+
+ decode: (str :: String) ->
+ throw new Error "parameter is unsupported format: #{str}" if not /^-?[\da-zA-Z]+$/.test str
+
+ isNegative :: Boolean = str[0] is '-'
+ str = str.slice 1 if isNegative
+
+ table :: String = @table_
+
+ decodedNum :: Int = 0
+ i :: Int = str.length
+ len :: Int = str.length
+
+ while i--
+ decodedNum += table.indexOf(str[i]) * Math.pow 62, len - i - 1
+
+ if isNegative then decodedNum *= -1 else decodedNum
+
+ encode: (num :: Int) ->
+ throw new Error "parameter is not an integer: #{num}" if not isInteger num
+
+ return '0' if num is 0
+
+ isNegative :: Boolean = num < 0
+ num = Math.abs num
+
+ table :: String = @table_
+ encodedStr :: String = ''
+
+ while num > 0
+ encodedStr = table.charAt(num % 62) + encodedStr
+ num = Math.floor num / 62
+
+ if isNegative then "-#{encodedStr}" else encodedStr
+
+ if module?
+ module.exports ###:: Base62### = createConverter()
+ if process.env.NODE_ENV is 'test'
+ module.exports.Base62_ = Base62
+ module.exports.isInteger_ = isInteger
+ else
+ global.base62 ###:: Base62### = createConverter()
+ if Mocha?
+ global.base62.Base62_ = Base62
+ global.base62.isInteger_ = isInteger
+
+ return
diff --git a/package.json b/package.json
index f434cfe..4de1497 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
},
"scripts": {
"bower": "bower install",
+ "c": "tcoffee --bare --js --output ./base62.tc.js --input ./coffee/base62.coffee",
"fix": "fixjsstyle --strict --nojsdoc -r ./lib -r ./test ./index.js",
"lint": "gjslint --strict --nojsdoc -r ./lib -r ./test ./index.js",
"mini": "uglifyjs ./lib/base62.js --comments -cm -d 'module=void 0' -r base62 -o ./base62.min.js",
@@ -24,6 +25,7 @@
"expect.js": "~0.2",
"mocha": "~1.14",
"testem": "~0.5",
+ "typed-coffee-script": "~0.8",
"uglify-js": "~2.4"
}
}
diff --git a/test/test-base62.js b/test/test-base62.js
index 2e1dff3..8e3716d 100644
--- a/test/test-base62.js
+++ b/test/test-base62.js
@@ -2,7 +2,7 @@ var expect, base62;
if (typeof module !== 'undefined') {
expect = require('expect.js');
- base62 = require('../');
+ base62 = require('../base62.tc.js');
} else {
expect = this.expect;
base62 = this.base62;
その他
そういえば文法よくわかってなくて、以下のように書いたら
isInteger = (value :: Number) Number.isInteger or (value :: Number) ->
isFinite(value) and value is Math.floor(value)
以下のように出力されました。
isInteger = 3(Number.isInteger || function (value) {
return isFinite(value) && value === Math.floor(value);
});
3ってなんだろ……