From e584bdb7eed4077e63bbc80c2b39c0548699117a Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Tue, 12 Sep 2023 18:02:28 -0700 Subject: [PATCH 1/6] Add more filepath functions. --- filepath/api.go | 150 ++++++++++++++++++++++++++++---- filepath/api_test.go | 7 +- filepath/loader.go | 18 +++- filepath/test/test_api.lua | 169 ++++++++++++++++++++++++++++++++++++- tests/assert_const.go | 2 +- tests/assertions.lua | 1 + tests/assertions_const.go | 3 +- tests/lua_const.go | 2 +- tests/require_const.go | 2 +- 9 files changed, 329 insertions(+), 25 deletions(-) diff --git a/filepath/api.go b/filepath/api.go index 06975e2..8f298ce 100644 --- a/filepath/api.go +++ b/filepath/api.go @@ -7,6 +7,17 @@ import ( lua "github.com/yuin/gopher-lua" ) +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 +25,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 +39,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,6 +67,37 @@ func Ext(L *lua.LState) int { return 1 } +// 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 + } + result := L.CreateTable(len(files), 0) + for _, file := range files { + result.Append(lua.LString(file)) + } + L.Push(result) + return 1 +} + +// 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 +} + +// IsLocal reports whether path, using lexical analysis only, is local +func IsLocal(L *lua.LState) int { + path := L.CheckString(1) + L.Push(lua.LBool(filepath.IsLocal(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 { path := L.CheckString(1) @@ -39,30 +109,80 @@ func Join(L *lua.LState) int { return 1 } -// Separator lua filepath.separator() OS-specific path separator -func Separator(L *lua.LState) int { - L.Push(lua.LString(filepath.Separator)) - return 1 -} - // ListSeparator lua filepath.list_separator() OS-specific path list separator func ListSeparator(L *lua.LState) int { L.Push(lua.LString(filepath.ListSeparator)) 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..e448db6 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,23 @@ 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, + "is_local": IsLocal, "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..d5c4f22 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" @@ -15,4 +18,166 @@ 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_is_local(t) + t:Skip('TODO') +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 From 279aa8f4fdc9f4b5fa8e369547065b2c7d592883 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Wed, 13 Sep 2023 13:44:28 -0700 Subject: [PATCH 2/6] Ditch filepath.IsLocal - it's not in the older go --- filepath/api.go | 7 ------- filepath/loader.go | 1 - filepath/test/test_api.lua | 4 ---- 3 files changed, 12 deletions(-) diff --git a/filepath/api.go b/filepath/api.go index 8f298ce..05ebe76 100644 --- a/filepath/api.go +++ b/filepath/api.go @@ -91,13 +91,6 @@ func IsAbs(L *lua.LState) int { return 1 } -// IsLocal reports whether path, using lexical analysis only, is local -func IsLocal(L *lua.LState) int { - path := L.CheckString(1) - L.Push(lua.LBool(filepath.IsLocal(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 { path := L.CheckString(1) diff --git a/filepath/loader.go b/filepath/loader.go index e448db6..de204ae 100644 --- a/filepath/loader.go +++ b/filepath/loader.go @@ -30,7 +30,6 @@ var api = map[string]lua.LGFunction{ "ext": Ext, "glob": Glob, "is_abs": IsAbs, - "is_local": IsLocal, "join": Join, "list_separator": ListSeparator, "match": Match, diff --git a/filepath/test/test_api.lua b/filepath/test/test_api.lua index d5c4f22..86546fd 100644 --- a/filepath/test/test_api.lua +++ b/filepath/test/test_api.lua @@ -74,10 +74,6 @@ function Test_is_abs(t) end end -function Test_is_local(t) - t:Skip('TODO') -end - function Test_match(t) tests = { { From 29a2c16de1a098581095cc08113b2e33dca5f126 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Wed, 13 Sep 2023 16:03:55 -0700 Subject: [PATCH 3/6] add godoc for Abs --- filepath/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/filepath/api.go b/filepath/api.go index 05ebe76..ab1a2f4 100644 --- a/filepath/api.go +++ b/filepath/api.go @@ -7,6 +7,7 @@ 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) From caec365ce4d50303b0a7f28290e3d681619727d8 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Wed, 13 Sep 2023 16:17:32 -0700 Subject: [PATCH 4/6] Allo filepath.Join to take zero args as its underlying go implementation does and add tests for it --- filepath/api.go | 9 +++++---- filepath/test/test_api.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/filepath/api.go b/filepath/api.go index ab1a2f4..403887e 100644 --- a/filepath/api.go +++ b/filepath/api.go @@ -94,11 +94,12 @@ func IsAbs(L *lua.LState) int { // 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) + 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 } diff --git a/filepath/test/test_api.lua b/filepath/test/test_api.lua index 86546fd..63d8356 100644 --- a/filepath/test/test_api.lua +++ b/filepath/test/test_api.lua @@ -10,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)) From bd9a02e020d5b45fcd46aa883f03c329af498e98 Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Wed, 13 Sep 2023 16:30:47 -0700 Subject: [PATCH 5/6] Fix the broken sleep test from #61 --- time/example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 41d9f85ade4b53d973901f483f484ffacd88ed2d Mon Sep 17 00:00:00 2001 From: Sheridan C Rawlins Date: Wed, 13 Sep 2023 16:33:11 -0700 Subject: [PATCH 6/6] Add newer go versions --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: