diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index deaedf3..82e2ad0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ['1.14', '1.15', '1.16', '1.17', '1.18', '1.19'] + go: ['1.14', '1.15', '1.16', '1.17', '1.18', '1.19', '1.20', '1.21'] services: {} steps: diff --git a/filepath/api.go b/filepath/api.go index 06975e2..403887e 100644 --- a/filepath/api.go +++ b/filepath/api.go @@ -7,6 +7,18 @@ import ( lua "github.com/yuin/gopher-lua" ) +// Abs returns an absolute representation of path. +func Abs(L *lua.LState) int { + path := L.CheckString(1) + ret, err := filepath.Abs(path) + L.Push(lua.LString(ret)) + if err != nil { + L.Push(lua.LString(err.Error())) + return 2 + } + return 1 +} + // Basename lua filepath.basename(path) returns the last element of path func Basename(L *lua.LState) int { path := L.CheckString(1) @@ -14,6 +26,13 @@ func Basename(L *lua.LState) int { return 1 } +// Clean returns the shortest path name equivalent to path +func Clean(L *lua.LState) int { + path := L.CheckString(1) + L.Push(lua.LString(filepath.Clean(path))) + return 1 +} + // Dir lua filepath.dir(path) returns all but the last element of path, typically the path's directory func Dir(L *lua.LState) int { path := L.CheckString(1) @@ -21,6 +40,27 @@ func Dir(L *lua.LState) int { return 1 } +// EvalSymlinks returns the path name after the evaluation of any symbolic link. +func EvalSymlinks(L *lua.LState) int { + path := L.CheckString(1) + ret, err := filepath.EvalSymlinks(path) + L.Push(lua.LString(ret)) + if err != nil { + L.Push(lua.LString(err.Error())) + return 2 + } + return 1 +} + +// FromSlash returns the result of replacing each slash ('/') character +// in path with a separator character. Multiple slashes are replaced +// by multiple separators. +func FromSlash(L *lua.LState) int { + path := L.CheckString(1) + L.Push(lua.LString(filepath.FromSlash(path))) + return 1 +} + // Ext lua filepath.ext(path) returns the file name extension used by path. func Ext(L *lua.LState) int { path := L.CheckString(1) @@ -28,20 +68,39 @@ func Ext(L *lua.LState) int { return 1 } -// Join lua fileapth.join(path, ...) joins any number of path elements into a single path, adding a Separator if necessary. -func Join(L *lua.LState) int { - path := L.CheckString(1) - for i := 2; i <= L.GetTop(); i++ { - add := L.CheckAny(i).String() - path = filepath.Join(path, add) +// Glob: filepath.glob(pattern) returns the names of all files matching pattern or nil if there is no matching file. +func Glob(L *lua.LState) int { + pattern := L.CheckString(1) + files, err := filepath.Glob(pattern) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 } - L.Push(lua.LString(path)) + result := L.CreateTable(len(files), 0) + for _, file := range files { + result.Append(lua.LString(file)) + } + L.Push(result) return 1 } -// Separator lua filepath.separator() OS-specific path separator -func Separator(L *lua.LState) int { - L.Push(lua.LString(filepath.Separator)) +// IsAbs reports whether the path is absolute. +func IsAbs(L *lua.LState) int { + path := L.CheckString(1) + L.Push(lua.LBool(filepath.IsAbs(path))) + return 1 +} + +// Join lua fileapth.join(path, ...) joins any number of path elements into a single path, adding a Separator if necessary. +func Join(L *lua.LState) int { + var elems []string + for i := 1; i <= L.GetTop(); i++ { + elem := L.CheckAny(i).String() + elems = append(elems, elem) + } + path := filepath.Join(elems...) + L.Push(lua.LString(path)) return 1 } @@ -51,18 +110,74 @@ func ListSeparator(L *lua.LState) int { return 1 } -// filepath.glob(pattern) returns the names of all files matching pattern or nil if there is no matching file. -func Glob(L *lua.LState) int { +// Match reports whether name matches the shell file name pattern. +func Match(L *lua.LState) int { pattern := L.CheckString(1) - files, err := filepath.Glob(pattern) + name := L.CheckString(2) + matched, err := filepath.Match(pattern, name) + L.Push(lua.LBool(matched)) if err != nil { - L.Push(lua.LNil) - return 1 + L.Push(lua.LString(err.Error())) + return 2 } - result := L.CreateTable(len(files), 0) - for _, file := range files { - result.Append(lua.LString(file)) + return 1 +} + +// Rel returns a relative path +func Rel(L *lua.LState) int { + basepath := L.CheckString(1) + targpath := L.CheckString(2) + ret, err := filepath.Rel(basepath, targpath) + L.Push(lua.LString(ret)) + if err != nil { + L.Push(lua.LString(err.Error())) + return 2 } - L.Push(result) + return 1 +} + +// Separator lua filepath.separator() OS-specific path separator +func Separator(L *lua.LState) int { + L.Push(lua.LString(filepath.Separator)) + return 1 +} + +// Split splits path immediately following the final Separator, +// separating it into a directory and file name component. +func Split(L *lua.LState) int { + path := L.CheckString(1) + dir, file := filepath.Split(path) + L.Push(lua.LString(dir)) + L.Push(lua.LString(file)) + return 2 +} + +func SplitList(L *lua.LState) int { + path := L.CheckString(1) + ret := filepath.SplitList(path) + table := L.CreateTable(len(ret), 0) + for _, part := range ret { + table.Append(lua.LString(part)) + } + L.Push(table) + return 1 +} + +// ToSlash returns the result of replacing each separator character +// in path with a slash ('/') character. Multiple separators are +// replaced by multiple slashes. +func ToSlash(L *lua.LState) int { + path := L.CheckString(1) + L.Push(lua.LString(filepath.ToSlash(path))) + return 1 +} + +// VolumeName returns leading volume name. +// Given "C:\foo\bar" it returns "C:" on Windows. +// Given "\\host\share\foo" it returns "\\host\share". +// On other platforms it returns "". +func VolumeName(L *lua.LState) int { + path := L.CheckString(1) + L.Push(lua.LString(filepath.VolumeName(path))) return 1 } diff --git a/filepath/api_test.go b/filepath/api_test.go index 569b196..cb39292 100644 --- a/filepath/api_test.go +++ b/filepath/api_test.go @@ -2,10 +2,15 @@ package filepath import ( "github.com/stretchr/testify/assert" + "github.com/vadv/gopher-lua-libs/inspect" "github.com/vadv/gopher-lua-libs/tests" "testing" ) func TestApi(t *testing.T) { - assert.NotZero(t, tests.RunLuaTestFile(t, Preload, "./test/test_api.lua")) + preload := tests.SeveralPreloadFuncs( + Preload, + inspect.Preload, + ) + assert.NotZero(t, tests.RunLuaTestFile(t, preload, "./test/test_api.lua")) } diff --git a/filepath/loader.go b/filepath/loader.go index 3f3f290..de204ae 100644 --- a/filepath/loader.go +++ b/filepath/loader.go @@ -7,7 +7,7 @@ import ( // Preload adds filepath to the given Lua state's package.preload table. After it // has been preloaded, it can be loaded using require: // -// local filepath = require("filepath") +// local filepath = require("filepath") func Preload(L *lua.LState) { L.PreloadModule("filepath", Loader) } @@ -21,11 +21,22 @@ func Loader(L *lua.LState) int { } var api = map[string]lua.LGFunction{ - "dir": Dir, + "abs": Abs, "basename": Basename, + "clean": Clean, + "dir": Dir, + "eval_symlinks": EvalSymlinks, + "from_slash": FromSlash, "ext": Ext, "glob": Glob, + "is_abs": IsAbs, "join": Join, - "separator": Separator, "list_separator": ListSeparator, + "match": Match, + "rel": Rel, + "separator": Separator, + "split": Split, + "split_list": SplitList, + "to_slash": ToSlash, + "volume_name": VolumeName, } diff --git a/filepath/test/test_api.lua b/filepath/test/test_api.lua index 70202c6..63d8356 100644 --- a/filepath/test/test_api.lua +++ b/filepath/test/test_api.lua @@ -1,4 +1,7 @@ -local filepath = require("filepath") +local filepath = require('filepath') +local assert = require('assert') +local require = require('require') +local inspect = require('inspect') function Test_join_and_separator(t) local path = "1" @@ -7,6 +10,32 @@ function Test_join_and_separator(t) assert(path == need_path, string.format("expected %s; got %s", need_path, path)) end +function Test_join(t) + tests = { + { + name = "1/2/3", + args = { "1", "2", "3" }, + }, + { + name = "foo", + args = { "foo" }, + }, + { + name = "", + args = {}, + }, + { + name = "/a/b/c", + args = { "/", "a", "b", "c" }, + }, + } + for _, tt in ipairs(tests) do + t:Run(tt.name, function(t) + assert:Equal(t, tt.name, filepath.join(unpack(tt.args))) + end) + end +end + function Test_glob(t) local results = filepath.glob("test" .. filepath.separator() .. "*") assert(#results == 1, string.format("expected one glob result; got %d", #results)) @@ -15,4 +44,162 @@ function Test_glob(t) assert(v == "test" .. filepath.separator() .. "test_api.lua", v) end end -end \ No newline at end of file +end + +function Test_abs(t) + pwd = os.getenv('PWD') + path, err = filepath.abs('foo') + require:NoError(t, err) + assert:Equal(t, filepath.join(pwd, 'foo'), path) +end + +function Test_clean(t) + cleaned = filepath.clean('/foo//bar') + assert:Equal(t, '/foo/bar', cleaned) +end + +function Test_eval_symlinks(t) + temp_dir = t:TempDir() + temp_dir, err = filepath.eval_symlinks(temp_dir) + require:NoError(t, err) + + test_path = filepath.join(temp_dir, 'foo') + os.execute('ln -s . ' .. test_path) + path, err = filepath.eval_symlinks(test_path) + require:NoError(t, err) + assert:Equal(t, temp_dir, path) +end + +function Test_from_slash(t) + t:Skip('TODO') +end + +function Test_ext(t) + ext = filepath.ext('foo.bar') + assert:Equal(t, '.bar', ext) +end + +function Test_is_abs(t) + tests = { + { + name = 'is absolute', + path = '/foo/bar', + expected = true + }, + { + name = 'is not absolute', + path = 'foo/bar', + expected = false + }, + } + + for _, tt in ipairs(tests) do + t:Run(tt.name, function(t) + assert:Equal(t, tt.expected, filepath.is_abs(tt.path)) + end) + end +end + +function Test_match(t) + tests = { + { + name = 'Should match', + path = '/foo/bar', + pattern = '/*/bar', + expected = true, + }, + { + name = 'Should NOT match', + path = '/yada/yada', + pattern = '/*/bar', + expected = false, + }, + } + for _, tt in ipairs(tests) do + t:Run(tt.name, function(t) + assert:Equal(t, tt.expected, filepath.match(tt.pattern, tt.path)) + end) + end +end + +function Test_rel(t) + tests = { + { + name = 'targpath is under basepath', + basepath = '/foo/bar', + targpath = '/foo/bar/baz', + expected = 'baz', + want_err = false, + }, + { + name = 'targpath is NOT under basepath', + basepath = '/foo/bar', + targpath = 'yada/yada/baz', + want_err = true, + }, + } + for _, tt in ipairs(tests) do + t:Run(tt.name, function(t) + path, err = filepath.rel(tt.basepath, tt.targpath) + if tt.want_err then + require:Errorf(t, err, "basepath = %s, targpath = %s, path = %s", tt.basepath, tt.targpath, path) + return + end + require:NoError(t, err) + assert:Equal(t, tt.expected, path) + end) + end +end + +function Test_split(t) + tests = { + { + name = '/foo/bar/baz', + expected_dir = '/foo/bar/', + expected_file = 'baz', + }, + { + name = 'bar/baz', + expected_dir = 'bar/', + expected_file = 'baz', + }, + { + name = 'nodir', + expected_dir = '', + expected_file = 'nodir', + }, + } + for _, tt in ipairs(tests) do + t:Run(tt.name, function(t) + dir, file = filepath.split(tt.name) + assert:Equal(t, tt.expected_dir, dir) + assert:Equal(t, tt.expected_file, file) + end) + end +end + +function Test_split_list(t) + tests = { + { + name = 'foo/bar/baz:/yada/yada', + expected = { 'foo/bar/baz', '/yada/yada' }, + }, + { + name = 'a/b/c', + expected = { 'a/b/c' }, + }, + } + for _, tt in ipairs(tests) do + t:Run(tt.name, function(t) + assert:Equal(t, inspect(tt.expected), inspect(filepath.split_list(tt.name))) + end) + end +end + +function Test_to_slash(t) + t:Skip('TODO') +end + +function Test_volume_name(t) + t:Skip('TODO') +end diff --git a/tests/assert_const.go b/tests/assert_const.go index e787fd5..157a70a 100644 --- a/tests/assert_const.go +++ b/tests/assert_const.go @@ -1,7 +1,7 @@ // GENERATED BY textFileToGoConst // GitHub: github.com/logrusorgru/textFileToGoConst // input file: assert.lua -// generated: Wed Nov 16 13:31:38 PST 2022 +// generated: Tue Sep 12 17:34:09 PDT 2023 package tests diff --git a/tests/assertions.lua b/tests/assertions.lua index e5880ac..8b168be 100644 --- a/tests/assertions.lua +++ b/tests/assertions.lua @@ -19,6 +19,7 @@ function assertions:cleanseString(s) if s == nil then return s end + s = tostring(s) s = string.gsub(s, '\n', '\\n') s = string.gsub(s, '\t', '\\t') s = string.gsub(s, '\r', '\\r') diff --git a/tests/assertions_const.go b/tests/assertions_const.go index 5a79347..ec65ee8 100644 --- a/tests/assertions_const.go +++ b/tests/assertions_const.go @@ -1,7 +1,7 @@ // GENERATED BY textFileToGoConst // GitHub: github.com/logrusorgru/textFileToGoConst // input file: assertions.lua -// generated: Wed Nov 16 13:31:38 PST 2022 +// generated: Tue Sep 12 17:34:08 PDT 2023 package tests @@ -26,6 +26,7 @@ function assertions:cleanseString(s) if s == nil then return s end + s = tostring(s) s = string.gsub(s, '\n', '\\n') s = string.gsub(s, '\t', '\\t') s = string.gsub(s, '\r', '\\r') diff --git a/tests/lua_const.go b/tests/lua_const.go index ab0a1c7..1b879f8 100644 --- a/tests/lua_const.go +++ b/tests/lua_const.go @@ -1,7 +1,7 @@ // GENERATED BY textFileToGoConst // GitHub: github.com/logrusorgru/textFileToGoConst // input file: suite.lua -// generated: Wed Nov 16 13:31:37 PST 2022 +// generated: Tue Sep 12 17:34:06 PDT 2023 package tests diff --git a/tests/require_const.go b/tests/require_const.go index e72edfc..4482d81 100644 --- a/tests/require_const.go +++ b/tests/require_const.go @@ -1,7 +1,7 @@ // GENERATED BY textFileToGoConst // GitHub: github.com/logrusorgru/textFileToGoConst // input file: require.lua -// generated: Wed Nov 16 13:31:39 PST 2022 +// generated: Tue Sep 12 17:34:10 PDT 2023 package tests diff --git a/time/example_test.go b/time/example_test.go index d469b10..3a53645 100644 --- a/time/example_test.go +++ b/time/example_test.go @@ -24,7 +24,7 @@ func ExampleSleep() { log.Fatal(err.Error()) } // Output: - // 1 + // 1.2 } // time.parse(value, layout)