diff --git a/Alfons.moon b/Alfons.moon index db0a766..0591b7f 100644 --- a/Alfons.moon +++ b/Alfons.moon @@ -1,6 +1,8 @@ tasks: build: => sh "moon bin/moonbuild.moon compile" + test: => + sh "busted" install: => sh "moon bin/moonbuild.moon install" release: => diff --git a/moonbuild.lua b/moonbuild.lua index fbef51e..dd64d16 100644 --- a/moonbuild.lua +++ b/moonbuild.lua @@ -28,6 +28,9 @@ makecached = function(fn) end local get get = function(val) + if cache == FROZEN then + return fn(val) + end local cached = cache[val] if cached ~= FROZEN and cached ~= nil then return unpack(cached) @@ -40,18 +43,30 @@ makecached = function(fn) end return unpack(ret) end + local enable + enable = function() + if cache == FROZEN then + cache = { } + end + end + local disable + disable = function() + cache = FROZEN + end return setmetatable({ get = get, invalidate = invalidate, freeze = freeze, - clear = clear + clear = clear, + enable = enable, + disable = disable }, { __call = function(self, val) return get(val) end }) end -return { +local cached = { attributes = makecached(attributes), dir = makecached(function(file) local _accum_0 = { } @@ -63,6 +78,31 @@ return { return _accum_0 end) } +local enable +enable = function() + for _, fn in cached do + fn:enable() + end +end +local disable +disable = function() + for _, fn in cached do + fn:disable() + end +end +local clear +clear = function() + for _, fn in cached do + fn:clear() + end +end +return setmetatable({ + enable = enable, + disable = disable, + clear = clear +}, { + __index = cached +}) end end @@ -70,10 +110,10 @@ end do local _ENV = _ENV package.preload[ "moonbuild.fsutil" ] = function( ... ) local arg = _G.arg; -local dir, attributes +local dir, attributes, clear, enable, disable do local _obj_0 = require('moonbuild.fscache') - dir, attributes = _obj_0.dir, _obj_0.attributes + dir, attributes, clear, enable, disable = _obj_0.dir, _obj_0.attributes, _obj_0.clear, _obj_0.enable, _obj_0.disable end local gmatch, match, gsub, sub do @@ -98,22 +138,23 @@ normalizepath = function(file) parts = _accum_0 end local absolute = (sub(file, 1, 1)) == '/' - for i = 1, #parts do + local i = 1 + while i <= #parts do local _continue_0 = false repeat if parts[i] == '.' then remove(parts, i) - i = i - 1 _continue_0 = true break end - if parts[i] == '..' and i ~= 1 then + if parts[i] == '..' and i ~= 1 and parts[i - 1] ~= '..' then remove(parts, i) remove(parts, i - 1) - i = i - 2 + i = i - 1 _continue_0 = true break end + i = i + 1 _continue_0 = true until true if not _continue_0 then @@ -121,7 +162,7 @@ normalizepath = function(file) end end if #parts == 0 then - return '.' + return absolute and '/' or '.' else return (absolute and '/' or '') .. concat(parts, '/') end @@ -173,7 +214,12 @@ mtime = function(f) end local matchglob matchglob = function(str, glob) - local patt = '^' .. (gsub((gsub(glob, '%*%*', '.*')), '%*', '[^/]*')) .. '$' + glob = gsub(glob, '[%[%]%%+.?-]', function(self) + return '%' .. self + end) + local patt = '^' .. (gsub(glob, '%*%*?', function(self) + return self == '**' and '.*' or '[^/]*' + end)) .. '$' local rst if (type(str)) == 'table' then local results, i = { }, 1 @@ -280,11 +326,6 @@ invalidatecache = function(file) dir.invalidate(parentdir(file)) return attributes.invalidate(file) end -local clearcache -clearcache = function() - dir.clear() - return attributes.clear() -end return { wildcard = wildcard, exists = exists, @@ -292,9 +333,12 @@ return { mtime = mtime, normalizepath = normalizepath, parentdir = parentdir, + matchglob = matchglob, freezecache = freezecache, invalidatecache = invalidatecache, - clearcache = clearcache + clearcache = clear, + enablecache = enable, + disablecache = disable } end diff --git a/moonbuild/fscache.moon b/moonbuild/fscache.moon index 8a6e982..d32ae06 100644 --- a/moonbuild/fscache.moon +++ b/moonbuild/fscache.moon @@ -16,6 +16,8 @@ makecached = (fn) -> cache = {} get = (val) -> + if cache == FROZEN + return fn val cached = cache[val] if cached!=FROZEN and cached!=nil return unpack cached @@ -24,10 +26,27 @@ makecached = (fn) -> cache[val] = ret unpack ret - setmetatable { :get, :invalidate, :freeze, :clear }, + enable = -> + cache = {} if cache==FROZEN + + disable = -> + cache = FROZEN + + setmetatable { :get, :invalidate, :freeze, :clear, :enable, :disable }, __call: (val) => get val -{ +cached = { attributes: makecached attributes dir: makecached (file) -> [k for k in dir file] } + +enable = -> + fn\enable! for _, fn in cached + +disable = -> + fn\disable! for _, fn in cached + +clear = -> + fn\clear! for _, fn in cached + +setmetatable { :enable, :disable, :clear }, __index: cached diff --git a/moonbuild/fsutil.moon b/moonbuild/fsutil.moon index 7d6f639..7c0214f 100644 --- a/moonbuild/fsutil.moon +++ b/moonbuild/fsutil.moon @@ -1,4 +1,4 @@ -import dir, attributes from require 'moonbuild.fscache' +import dir, attributes, clear, enable, disable from require 'moonbuild.fscache' import gmatch, match, gsub, sub from string import insert, remove, concat from table @@ -6,18 +6,19 @@ import insert, remove, concat from table normalizepath = (file) -> parts = [part for part in gmatch file, '[^/]+'] absolute = (sub file, 1, 1)=='/' - for i=1, #parts + i = 1 + while i<=#parts if parts[i]=='.' remove parts, i - i -= 1 continue - if parts[i]=='..' and i!=1 + if parts[i]=='..' and i!=1 and parts[i-1]!='..' remove parts, i remove parts, i-1 - i -= 2 + i -= 1 continue + i += 1 if #parts==0 - '.' + absolute and '/' or '.' else (absolute and '/' or '') .. concat parts, '/' @@ -40,7 +41,8 @@ mtime = (f) -> a and a.modification matchglob = (str, glob) -> - patt = '^'..(gsub (gsub glob, '%*%*', '.*'), '%*', '[^/]*')..'$' + glob = gsub glob, '[%[%]%%+.?-]', => '%'..@ + patt = '^'..(gsub glob, '%*%*?', => @=='**' and '.*' or '[^/]*')..'$' rst = if (type str)=='table' results, i = {}, 1 for s in *str @@ -111,14 +113,12 @@ invalidatecache = (file) -> dir.invalidate parentdir file attributes.invalidate file -clearcache = -> - dir.clear! - attributes.clear! - { :wildcard :exists, :isdir :mtime :normalizepath, :parentdir - :freezecache, :invalidatecache, :clearcache + :matchglob + :freezecache, :invalidatecache + clearcache: clear, enablecache: enable, disablecache: disable } diff --git a/moonbuild/stringutil.moon b/moonbuild/stringutil.moon index 91aeab8..f5d07f3 100644 --- a/moonbuild/stringutil.moon +++ b/moonbuild/stringutil.moon @@ -5,13 +5,25 @@ GLOB_PATT='^([^%%]*)%%([^%%]*)$' patsubst = (str, pattern, replacement) -> return [patsubst s, pattern, replacement for s in *str] if (type str)=='table' + + if str==pattern + return replacement + prefix, suffix = match pattern, GLOB_PATT - return str unless prefix + if not (prefix or suffix) + return str + reprefix, resuffix = match replacement, GLOB_PATT - return replacement unless reprefix + if not (reprefix or resuffix) + if (#prefix==0 or (sub str, 1, #prefix)==prefix) and (#suffix==0 or (sub str, -#suffix)==suffix) + return replacement + else + return str - if (sub str, 1, #prefix)==prefix and (sub str, -#suffix)==suffix - return reprefix..(sub str, #prefix+1, -#suffix-1)..resuffix + if #prefix==0 or (sub str, 1, #prefix)==prefix + str = reprefix..(sub str, #prefix+1) + if #suffix==0 or (sub str, -#suffix)==suffix + str = (sub str, 1, -#suffix-1)..resuffix str splitsp = (str) -> diff --git a/moonbuild/tableutil.moon b/moonbuild/tableutil.moon index cbb4ae2..ea4f687 100644 --- a/moonbuild/tableutil.moon +++ b/moonbuild/tableutil.moon @@ -48,7 +48,10 @@ flatten = (tab) -> out = {} for e in *tab if (type e)=='table' - insert out, v for v in *flatten e + if e[1] == nil and (next e)!=nil + insert out, e + else + insert out, v for v in *flatten e else insert out, e out diff --git a/spec/fsutil_spec.moon b/spec/fsutil_spec.moon new file mode 100644 index 0000000..fe24c1c --- /dev/null +++ b/spec/fsutil_spec.moon @@ -0,0 +1,111 @@ +describe 'fsutil', -> + describe 'normalizepath', -> + import normalizepath from require 'moonbuild.fsutil' + + test = (expected, source) -> + it "normalizes #{source} correctly", -> + assert.equal expected, normalizepath source + + testall = (tab) -> + for a, b in pairs tab + test b, a + + describe 'handles already normalized paths', -> + testall { + '.': '.' + '..': '..' + '../..': '../..' + '/': '/' + '/a': '/a' + '/a/b': '/a/b' + 'a': 'a' + 'a/b': 'a/b' + } + + describe 'trims leading slashes', -> + testall { + 'a/': 'a' + 'a/b/': 'a/b' + '/a/': '/a' + '/a/b/': '/a/b' + } + + describe 'normalizes absolute paths', -> + testall { + '/a/a/../b': '/a/b' + '/a/./b': '/a/b' + '/a/b/c/..': '/a/b' + '/./a/./b/././.': '/a/b' + } + + describe 'normalizes relative paths', -> + testall { + '../x/../../a': '../../a' + '../x/../a': '../a' + 'x/..': '.' + '../.': '..' + './a': 'a' + } + + describe 'matchglob', -> + import matchglob from require 'moonbuild.fsutil' + + test = (expected, source, glob) -> + if expected + it "matches #{glob} on #{source}", -> + assert.equal source, matchglob source, glob + else + it "doesn't match #{glob} on #{source}", -> + assert.equal nil, matchglob source, glob + + testall = (tab) -> + for a, b in pairs tab + test b, a[1], a[2] + + describe 'handles literal names', -> + testall { + [{'a', 'a'}]: true + [{'a.b', 'a.b'}]: true + [{'a/b', 'a/b'}]: true + [{'..', '..'}]: true + } + + describe 'doesn\'t treat things as special chars', -> + testall { + [{'a', '.'}]: false + [{'a.b.c', '%S+'}]: false + [{'%S+', '%S+'}]: true + [{'%d', '%d'}]: true + [{'a', '%S'}]: false + [{'aaa', 'a+'}]: false + } + + describe 'only matches fully', -> + testall { + [{'abcdef', 'bcde'}]: false + [{'a/b/c', 'b/c'}]: false + [{'a/b/c', 'a/b'}]: false + } + + describe 'handles *', -> + testall { + [{'abcde', '*'}]: true + [{'a/b/c/d', 'a/*/c/d'}]: true + [{'a/b/c/d', 'a/*/d'}]: false + [{'abcde', 'a*e'}]: true + [{'abcde', 'a*f'}]: false + [{'a/b/c/d/e', 'a/*/*/*/e'}]: true + [{'a/b/c/d/e', 'a*/*/*e'}]: false + } + + describe 'handles **', -> + testall { + [{'abcde', '**'}]: true + [{'a/b/c/d', 'a/**/c/d'}]: true + [{'abcde', 'a**e'}]: true + [{'a/b/c/d/e', 'a/**/**/**/e'}]: true + [{'a/b/c/d/e', 'a**e'}]: true + [{'a/b/c/d/e', 'a/**/e'}]: true + [{'a/b/c/d/e', 'a**f'}]: false + [{'abcde', 'a**f'}]: false + } diff --git a/spec/stringutil_spec.moon b/spec/stringutil_spec.moon new file mode 100644 index 0000000..c49c6d1 --- /dev/null +++ b/spec/stringutil_spec.moon @@ -0,0 +1,78 @@ +describe 'stringutil', -> + describe 'patsubst', -> + import patsubst from require 'moonbuild.stringutil' + + test = (expected, source, patt, subst) -> + it "substitutes #{source} into #{expected} with #{patt} and #{subst}", -> + assert.equal expected, patsubst source, patt, subst + + testall = (tab) -> + for a, b in pairs tab + test b, a[1], a[2], a[3] + + describe 'handles just adding pre/suffix', -> + testall { + [{'a', '%', '_%'}]: '_a' + [{'tx', '%', 'a_%'}]: 'a_tx' + [{'a', '%', '%_'}]: 'a_' + [{'tx', '%', '%_a'}]: 'tx_a' + [{'a', '%', '_%_'}]: '_a_' + } + + describe 'handles doing nothing', -> + for str in *({'a', 'aa', 'tx'}) + test str, str, '%', '%' + + describe 'handles literal change', -> + testall { + [{'a', 'a', 'b'}]: 'b' + [{'a', 'b', 'c'}]: 'a' + [{'aa', 'a', 'b'}]: 'aa' + } + + describe 'handles match change', -> + testall { + [{'-a_', '-%_', 'b'}]: 'b' + [{'-a_', '-%', 'b'}]: 'b' + [{'-a_', '%_', 'b'}]: 'b' + [{'-a_', '_%-', 'b'}]: '-a_' + } + + describe 'handles just removing pre/suffix', -> + testall { + [{'_a', '_%', '%'}]: 'a' + [{'a_', '%_', '%'}]: 'a' + [{'_a_', '_%_', '%'}]: 'a' + } + + describe 'handles not matching', -> + testall { + [{'a-', '%_', '%'}]: 'a-' + [{'-a', '_%', '%'}]: '-a' + [{'-a-', '_%_', '%'}]: '-a-' + } + + describe 'handles changing pre/suffix', -> + testall { + [{'a-', '%-', '%_'}]: 'a_' + [{'-a', '-%', '_%'}]: '_a' + [{'-a', '-%', '%_'}]: 'a_' + [{'_a-', '_%-', '-%_'}]: '-a_' + } + + describe 'splitsp', -> + import splitsp from require 'moonbuild.stringutil' + + test = (expected, source) -> + it "splits '#{source}' correctly", -> + assert.same expected, splitsp source + + for source, expected in pairs { + 'a b c': {'a', 'b', 'c'} + 'abc': {'abc'} + '': {} + ' a b c': {'a', 'b', 'c'} + ' ': {} + ' ab c': {'ab', 'c'} + } + test expected, source diff --git a/spec/tableutil_spec.moon b/spec/tableutil_spec.moon new file mode 100644 index 0000000..e9d6ea7 --- /dev/null +++ b/spec/tableutil_spec.moon @@ -0,0 +1,82 @@ +describe 'tableutil', -> + describe 'sortedpairs', -> + import sortedpairs from require 'moonbuild.tableutil' + + for src, dst in pairs { + [{a: '1', c: 2, b: 3}]: {'a', '1', 'b', 3, 'c', 2} + [{5, 4, 3}]: {1, 5, 2, 4, 3, 3} + } + it "works for #{src}", -> + i = 1 + for k, v in sortedpairs src + assert.equal k, dst[i] + assert.equal v, dst[i+1] + i += 2 + + describe 'min and max', -> + import min, max from require 'moonbuild.tableutil' + + for src, dst in pairs { + [{1, 2, 3, 4, 5}]: {1, 5} + [{5, 4, 3, 2, 1}]: {1, 5} + [{2, 4, 5, 1, 3}]: {1, 5} + [{1, 1, 1, 1, 1}]: {1, 1} + [{1}]: {1, 1} + } + it "min of #{table.concat src, ','} is #{dst[1]}", -> + assert.equal dst[1], min src + it "max of #{table.concat src, ','} is #{dst[2]}", -> + assert.equal dst[2], max src + + describe 'foreach', -> + import foreach from require 'moonbuild.tableutil' + + src = {1, 2, 5, '79'} + testall = (name, rst, fn) -> + it name, -> + assert.same rst, foreach src, fn + + + testall 'works with tostring', {'1', '2', '5', '79'}, tostring + testall 'works with tonumber', {1, 2, 5, 79}, tonumber + testall 'works with some mix of tonumber and comparison', {false, false, true, true}, => 3 + import first from require 'moonbuild.tableutil' + + test = (name, src, rst, fn) -> + it name, -> + assert.equal rst, (first src, fn) + + test 'works with == for first of list', {1, 3, 5}, 1, => @==1 + test 'works with == for something else', {1, 3, 5}, 3, => @==3 + test 'works with == for absent element', {1, 3, 5}, nil, => @==2 + + describe 'exclude', -> + import exclude from require 'moonbuild.tableutil' + unpack or= table.unpack + + test = (name, src, rst, ...) -> + rest = {...} + it name, -> + assert.equal src, exclude src, unpack rest + assert.same rst, src + + test 'works with nothing', {1, 2, 3}, {1, 2, 3} + test 'works with absent elements', {1, 2, 3}, {1, 2, 3}, 4, 5, 6 + test 'works with some elements', {1, 2, 3}, {1}, 2, 3 + test 'works with all elements', {1, 2, 3}, {}, 1, 2, 3 + test 'works with a mix', {1, 2, 3}, {1, 3}, 2, 4, 5 + + describe 'flatten', -> + import flatten from require 'moonbuild.tableutil' + + test = (name, src, rst) -> + it name, -> + assert.same rst, flatten src + + test 'works with empty table', {}, {} + test 'works with flat table', {1, 2, 3}, {1, 2, 3} + test 'works with one level', {1, {2}, {3}}, {1, 2, 3} + test 'works with multiple levels', {{{1, {{2}}}, 3}}, {1, 2, 3} + test 'skips maps', {1, {a: 2}, 3}, {1, {a: 2}, 3}