diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4841aec..c93c241 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,5 @@ --- - [ ] I have reviewed CONTRIBUTING.md. - [ ] My commits and title use the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) format. -- [ ] I have documented any breaking changes according to [SemVer](https://semver.org/). +- [ ] I have documented changes and additions in the CHANGELOG.md. --- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b357b64..08c69ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: with: go-version: '1.17.7' - name: Build - run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build + run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} make - uses: actions/upload-artifact@v2 if: matrix.goos == 'windows' with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1d377..3768eff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ includes git commit, branch, and (new!!) release name. - Added `fg` and `bg` builtins - `job.foreground()` and `job.background()`, when `job` is a job object, foreground and backgrounds a job respectively. +- Friendlier functions to the `hilbish.runner` interface, which also allow +having and using multiple runners. ### Changed - **Breaking Change:** Upgraded to Lua 5.4. @@ -56,6 +58,9 @@ This is probably one of (if not the) biggest things in this release. user input, exit code, and error. User input has been added to the return to account for runners wanting to prompt for continued input, and to add it properly to history. +- **Breaking Change:** Job objects and timers are now Lua userdata instead +of a table, so their functions require you to call them with a colon instead +of a dot. (ie. `job.stop()` -> `job:stop()`) - All `fs` module functions which take paths now implicitly expand ~ to home. ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f841d98..0bf5a37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,8 +29,9 @@ For any code contributions (Lua and/or Go), you should follow these rules: 1. Ensure that any new install or build dependencies are documented in the README.md and pull request. -2. Mention any and all changes, this includes useful file locations and -breaking changes. +2. Mention any and all changes, feature additons, removals, etc. This includes +useful file locations and breaking changes. Document them in the [changelog](CHANGELOG.md) +in the pull request. 3. We use [Semver](http://semver.org/) for versioning and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) diff --git a/api.go b/api.go index 9289305..3aca037 100644 --- a/api.go +++ b/api.go @@ -476,7 +476,7 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { timer := timers.create(timerTimeout, interval, cb) timer.start() - return c.PushingNext1(t.Runtime, timer.lua()), nil + return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil } // interval(cb, time) @@ -502,7 +502,7 @@ func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { timer := timers.create(timerInterval, interval, cb) timer.start() - return c.PushingNext1(t.Runtime, timer.lua()), nil + return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil } // complete(scope, cb) diff --git a/docs/jobs.txt b/docs/jobs.txt index 1dc8009..a5fee9c 100644 --- a/docs/jobs.txt +++ b/docs/jobs.txt @@ -20,8 +20,11 @@ and `execPath` is an absolute path for the command executable. - `disown(id)`: Removes a job by ID from the job table. # Job Object -A job object on the Lua side is a table with some functions. -On the under side it represents a job in the job table. +A job object is a piece of `userdata`. All the functions of a job require +you to call them with a colon, since they are *methods* for the job object. +Example: hilbish.jobs.last():foreground() +Which will foreground the last job. + You can still have a job object for a disowned job, it just won't be *working* anywhere. :^) diff --git a/docs/runner-mode.txt b/docs/runner-mode.txt index 9df1095..5765f18 100644 --- a/docs/runner-mode.txt +++ b/docs/runner-mode.txt @@ -35,8 +35,21 @@ The exit code has to be a number, it will be 0 otherwise and the error can be `nil` to indicate no error. ## Functions -These are the functions for the `hilbish.runner` interface +These are the "low level" functions for the `hilbish.runner` interface. + setMode(mode) > The same as `hilbish.runnerMode` + sh(input) -> input, code, err > Runs `input` in Hilbish's sh interpreter + lua(input) -> input, code, err > Evals `input` as Lua code + +The others here are defined in Lua and have EmmyLua documentation. +These functions should be preferred over the previous ones. ++ setCurrent(mode) > The same as `setMode`, but works with runners managed +via the functions below. ++ add(name, runner) > Adds a runner to a table of available runners. The `runner` +argument is either a function or a table with a run callback. ++ set(name, runner) > The same as `add` but requires passing a table and +overwrites if the `name`d runner already exists. ++ get(name) > runner > Gets a runner by name. It is a table with at least a +run function, to run input. ++ exec(cmd, runnerName) > Runs `cmd` with a runner. If `runnerName` isn't passed, +the current runner mode is used. diff --git a/docs/timers.txt b/docs/timers.txt index c5a456b..0f89718 100644 --- a/docs/timers.txt +++ b/docs/timers.txt @@ -16,9 +16,17 @@ a timer via ID. when the timer is triggered. # Timer Object -Those previously mentioned functions return a `timer` object, to which you can -stop and start a timer again. The functions of the timers interface also -return a timer object. +All those previously mentioned functions return a `timer` object, to which +you can stop and start a timer again. + +An example of usage: +local t = hilbish.timers.create(1, 5000, function() + print 'hello!' +end) + +t:stop() +print(t.running, t.duration, t.type) +t:start() ## Properties - `duration`: amount of time the timer runs for in milliseconds diff --git a/init_windows.go b/init_windows.go new file mode 100644 index 0000000..ee17a6b --- /dev/null +++ b/init_windows.go @@ -0,0 +1,11 @@ +// +build windows + +package main + +import "golang.org/x/sys/windows" + +func init() { + var mode uint32 + windows.GetConsoleMode(windows.Stdout, &mode) + windows.SetConsoleMode(windows.Stdout, mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) +} diff --git a/job.go b/job.go index 5ffb61f..7dd07d3 100644 --- a/job.go +++ b/job.go @@ -3,6 +3,7 @@ package main import ( "bytes" "errors" + "fmt" "io" "os" "os/exec" @@ -15,6 +16,7 @@ import ( ) var jobs *jobHandler +var jobMetaKey = rt.StringValue("hshjob") type job struct { cmd string @@ -32,6 +34,7 @@ type job struct { cmderr io.Writer stdout *bytes.Buffer stderr *bytes.Buffer + ud *rt.UserData } func (j *job) start() error { @@ -64,7 +67,7 @@ func (j *job) start() error { j.pid = proc.Pid j.running = true - hooks.Em.Emit("job.start", j.lua()) + hooks.Em.Emit("job.start", rt.UserDataValue(j.ud)) return err } @@ -79,7 +82,7 @@ func (j *job) stop() { func (j *job) finish() { j.running = false - hooks.Em.Emit("job.done", j.lua()) + hooks.Em.Emit("job.done", rt.UserDataValue(j.ud)) } func (j *job) wait() { @@ -107,28 +110,16 @@ func (j *job) getProc() *os.Process { return nil } -func (j *job) lua() rt.Value { - jobFuncs := map[string]util.LuaExport{ - "stop": {j.luaStop, 0, false}, - "start": {j.luaStart, 0, false}, - "foreground": {j.luaForeground, 0, false}, - "background": {j.luaBackground, 0, false}, +func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err } - luaJob := rt.NewTable() - util.SetExports(l, luaJob, jobFuncs) - luaJob.Set(rt.StringValue("cmd"), rt.StringValue(j.cmd)) - luaJob.Set(rt.StringValue("running"), rt.BoolValue(j.running)) - luaJob.Set(rt.StringValue("id"), rt.IntValue(int64(j.id))) - luaJob.Set(rt.StringValue("pid"), rt.IntValue(int64(j.pid))) - luaJob.Set(rt.StringValue("exitCode"), rt.IntValue(int64(j.exitCode))) - luaJob.Set(rt.StringValue("stdout"), rt.StringValue(string(j.stdout.Bytes()))) - luaJob.Set(rt.StringValue("stderr"), rt.StringValue(string(j.stderr.Bytes()))) + j, err := jobArg(c, 0) + if err != nil { + return nil, err + } - return rt.TableValue(luaJob) -} - -func (j *job) luaStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if !j.running { err := j.start() exit := handleExecErr(err) @@ -139,7 +130,16 @@ func (j *job) luaStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { +func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + j, err := jobArg(c, 0) + if err != nil { + return nil, err + } + if j.running { j.stop() j.finish() @@ -148,7 +148,16 @@ func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { +func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + j, err := jobArg(c, 0) + if err != nil { + return nil, err + } + if !j.running { return nil, errors.New("job not running") } @@ -157,7 +166,7 @@ func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { jobs.foreground = true // this is kinda funny // background continues the process incase it got suspended - err := j.background() + err = j.background() if err != nil { return nil, err } @@ -171,12 +180,21 @@ func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -func (j *job) luaBackground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { +func luaBackgroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + j, err := jobArg(c, 0) + if err != nil { + return nil, err + } + if !j.running { return nil, errors.New("job not running") } - err := j.background() + err = j.background() if err != nil { return nil, err } @@ -215,8 +233,10 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job { stdout: &bytes.Buffer{}, stderr: &bytes.Buffer{}, } + jb.ud = jobUserData(jb) + j.jobs[j.latestID] = jb - hooks.Em.Emit("job.add", jb.lua()) + hooks.Em.Emit("job.add", rt.UserDataValue(jb.ud)) return jb } @@ -257,6 +277,44 @@ func (j *jobHandler) stopAll() { } func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table { + jobMethods := rt.NewTable() + jFuncs := map[string]util.LuaExport{ + "stop": {luaStopJob, 1, false}, + "start": {luaStartJob, 1, false}, + "foreground": {luaForegroundJob, 1, false}, + "background": {luaBackgroundJob, 1, false}, + } + util.SetExports(l, jobMethods, jFuncs) + + jobMeta := rt.NewTable() + jobIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + j, _ := jobArg(c, 0) + + arg := c.Arg(1) + val := jobMethods.Get(arg) + + if val != rt.NilValue { + return c.PushingNext1(t.Runtime, val), nil + } + + keyStr, _ := arg.TryString() + + switch keyStr { + case "cmd": val = rt.StringValue(j.cmd) + case "running": val = rt.BoolValue(j.running) + case "id": val = rt.IntValue(int64(j.id)) + case "pid": val = rt.IntValue(int64(j.pid)) + case "exitCode": val = rt.IntValue(int64(j.exitCode)) + case "stdout": val = rt.StringValue(string(j.stdout.Bytes())) + case "stderr": val = rt.StringValue(string(j.stderr.Bytes())) + } + + return c.PushingNext1(t.Runtime, val), nil + } + + jobMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(jobIndex, "__index", 2, false))) + l.SetRegistry(jobMetaKey, rt.TableValue(jobMeta)) + jobFuncs := map[string]util.LuaExport{ "all": {j.luaAllJobs, 0, false}, "last": {j.luaLastJob, 0, false}, @@ -271,6 +329,30 @@ func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table { return luaJob } +func jobArg(c *rt.GoCont, arg int) (*job, error) { + j, ok := valueToJob(c.Arg(arg)) + if !ok { + return nil, fmt.Errorf("#%d must be a job", arg + 1) + } + + return j, nil +} + +func valueToJob(val rt.Value) (*job, bool) { + u, ok := val.TryUserData() + if !ok { + return nil, false + } + + j, ok := u.Value().(*job) + return j, ok +} + +func jobUserData(j *job) *rt.UserData { + jobMeta := l.Registry(jobMetaKey) + return rt.NewUserData(j, jobMeta.AsTable()) +} + func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { j.mu.RLock() defer j.mu.RUnlock() @@ -288,7 +370,7 @@ func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } - return c.PushingNext1(t.Runtime, job.lua()), nil + return c.PushingNext(t.Runtime, rt.UserDataValue(job.ud)), nil } func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { @@ -317,7 +399,7 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { jb := j.add(cmd, args, execPath) - return c.PushingNext1(t.Runtime, jb.lua()), nil + return c.PushingNext1(t.Runtime, rt.UserDataValue(jb.ud)), nil } func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { @@ -326,7 +408,7 @@ func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { jobTbl := rt.NewTable() for id, job := range j.jobs { - jobTbl.Set(rt.IntValue(int64(id)), job.lua()) + jobTbl.Set(rt.IntValue(int64(id)), rt.UserDataValue(job.ud)) } return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil @@ -358,5 +440,5 @@ func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } - return c.PushingNext1(t.Runtime, job.lua()), nil + return c.PushingNext1(t.Runtime, rt.UserDataValue(job.ud)), nil } diff --git a/nature/init.lua b/nature/init.lua index 7dd8953..8671087 100644 --- a/nature/init.lua +++ b/nature/init.lua @@ -8,6 +8,7 @@ require 'nature.commands' require 'nature.completions' require 'nature.opts' require 'nature.vim' +require 'nature.runner' local shlvl = tonumber(os.getenv 'SHLVL') if shlvl ~= nil then diff --git a/nature/runner.lua b/nature/runner.lua new file mode 100644 index 0000000..ed1b7dd --- /dev/null +++ b/nature/runner.lua @@ -0,0 +1,106 @@ +local currentRunner = 'hybrid' +local runners = {} + +-- lsp shut up +hilbish = hilbish + +--- Get a runner by name. +--- @param name string +--- @return table +function hilbish.runner.get(name) + local r = runners[name] + + if not r then + error(string.format('runner %s does not exist', name)) + end + + return r +end + +--- Adds a runner to the table of available runners. If runner is a table, +--- it must have the run function in it. +--- @param name string +--- @param runner function | table +function hilbish.runner.add(name, runner) + if type(name) ~= 'string' then + error 'expected runner name to be a table' + end + + if type(runner) == 'function' then + runner = {run = runner} -- this probably looks confusing + end + + if type(runner) ~= 'table' then + error 'expected runner to be a table or function' + end + + if runners[name] then + error(string.format('runner %s already exists', name)) + end + + hilbish.runner.set(name, runner) +end + +--- Sets a runner by name. The runner table must have the run function in it. +--- @param name string +--- @param runner table +function hilbish.runner.set(name, runner) + if not runner.run or type(runner.run) ~= 'function' then + error 'run function in runner missing' + end + + runners[name] = runner +end + +--- Executes cmd with a runner. If runnerName isn't passed, it uses +--- the user's current runner. +--- @param cmd string +--- @param runnerName string? +--- @return string, number, string +function hilbish.runner.exec(cmd, runnerName) + if not runnerName then runnerName = currentRunner end + + local r = hilbish.runner.get(runnerName) + + return r.run(cmd) +end + +--- Sets the current interactive/command line runner mode. +--- @param name string +function hilbish.runner.setCurrent(name) + local r = hilbish.runner.get(name) + currentRunner = name + + hilbish.runner.setMode(r.run) +end + +hilbish.runner.add('hybrid', function(input) + local cmdStr = hilbish.aliases.resolve(input) + + local _, _, err = hilbish.runner.lua(cmdStr) + if not err then + return input, 0, nil + end + + return hilbish.runner.sh(input) +end) + +hilbish.runner.add('hybridRev', function(input) + local _, _, err = hilbish.runner.sh(input) + if not err then + return input, 0, nil + end + + local cmdStr = hilbish.aliases.resolve(input) + return hilbish.runner.lua(cmdStr) +end) + +hilbish.runner.add('lua', function(input) + local cmdStr = hilbish.aliases.resolve(input) + return hilbish.runner.lua(cmdStr) +end) + +hilbish.runner.add('sh', function(input) + return hilbish.runner.sh(input) +end) + diff --git a/readline/comp-group.go b/readline/comp-group.go index ecf59b4..6a6e7bc 100644 --- a/readline/comp-group.go +++ b/readline/comp-group.go @@ -73,6 +73,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) { // We perform filter right here, so we create a new completion group, and populate it with our results. for i := range g.Suggestions { + if rl.regexSearch == nil { continue } if rl.regexSearch.MatchString(g.Suggestions[i]) { suggs = append(suggs, g.Suggestions[i]) } else if g.DisplayType == TabDisplayList && rl.regexSearch.MatchString(g.Descriptions[g.Suggestions[i]]) { diff --git a/readline/tabfind.go b/readline/tabfind.go index 5abe307..aa38259 100644 --- a/readline/tabfind.go +++ b/readline/tabfind.go @@ -33,6 +33,7 @@ func (rl *Instance) updateTabFind(r []rune) { var err error rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine)) if err != nil { + rl.RefreshPromptLog(err.Error()) rl.infoText = []rune(Red("Failed to match search regexp")) } diff --git a/timer.go b/timer.go index a502087..74d13c4 100644 --- a/timer.go +++ b/timer.go @@ -6,8 +6,6 @@ import ( "os" "time" - "hilbish/util" - rt "github.com/arnodel/golua/runtime" ) @@ -25,6 +23,7 @@ type timer struct{ fun *rt.Closure th *timerHandler ticker *time.Ticker + ud *rt.UserData channel chan struct{} } @@ -74,8 +73,17 @@ func (t *timer) stop() error { return nil } -func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { - err := t.start() +func timerStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + t, err := timerArg(c, 0) + if err != nil { + return nil, err + } + + err = t.start() if err != nil { return nil, err } @@ -83,26 +91,20 @@ func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -func (t *timer) luaStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { - err := t.stop() +func timerStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + t, err := timerArg(c, 0) if err != nil { return nil, err } - + + err = t.stop() + if err != nil { + return nil, err + } + return c.Next(), nil } - -func (t *timer) lua() rt.Value { - tExports := map[string]util.LuaExport{ - "start": {t.luaStart, 0, false}, - "stop": {t.luaStop, 0, false}, - } - luaTimer := rt.NewTable() - util.SetExports(l, luaTimer, tExports) - - luaTimer.Set(rt.StringValue("type"), rt.IntValue(int64(t.typ))) - luaTimer.Set(rt.StringValue("running"), rt.BoolValue(t.running)) - luaTimer.Set(rt.StringValue("duration"), rt.IntValue(int64(t.dur / time.Millisecond))) - - return rt.TableValue(luaTimer) -} diff --git a/timerhandler.go b/timerhandler.go index 2f2e878..64caff8 100644 --- a/timerhandler.go +++ b/timerhandler.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "sync" "time" @@ -10,6 +11,8 @@ import ( ) var timers *timerHandler +var timerMetaKey = rt.StringValue("hshtimer") + type timerHandler struct { mu *sync.RWMutex wg *sync.WaitGroup @@ -44,6 +47,8 @@ func (th *timerHandler) create(typ timerType, dur time.Duration, fun *rt.Closure th: th, id: th.latestID, } + t.ud = timerUserData(t) + th.timers[th.latestID] = t return t @@ -75,7 +80,7 @@ func (th *timerHandler) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { timerTyp := timerType(timerTypInt) tmr := th.create(timerTyp, time.Duration(ms) * time.Millisecond, cb) - return c.PushingNext1(t.Runtime, tmr.lua()), nil + return c.PushingNext1(t.Runtime, rt.UserDataValue(tmr.ud)), nil } func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { @@ -89,13 +94,45 @@ func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { t := th.get(int(id)) if t != nil { - return c.PushingNext1(thr.Runtime, t.lua()), nil + return c.PushingNext1(thr.Runtime, rt.UserDataValue(t.ud)), nil } return c.Next(), nil } func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table { + timerMethods := rt.NewTable() + timerFuncs := map[string]util.LuaExport{ + "start": {timerStart, 1, false}, + "stop": {timerStop, 1, false}, + } + util.SetExports(rtm, timerMethods, timerFuncs) + + timerMeta := rt.NewTable() + timerIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + ti, _ := timerArg(c, 0) + + arg := c.Arg(1) + val := timerMethods.Get(arg) + + if val != rt.NilValue { + return c.PushingNext1(t.Runtime, val), nil + } + + keyStr, _ := arg.TryString() + + switch keyStr { + case "type": val = rt.IntValue(int64(ti.typ)) + case "running": val = rt.BoolValue(ti.running) + case "duration": val = rt.IntValue(int64(ti.dur / time.Millisecond)) + } + + return c.PushingNext1(t.Runtime, val), nil + } + + timerMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(timerIndex, "__index", 2, false))) + l.SetRegistry(timerMetaKey, rt.TableValue(timerMeta)) + thExports := map[string]util.LuaExport{ "create": {th.luaCreate, 3, false}, "get": {th.luaGet, 1, false}, @@ -106,3 +143,27 @@ func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table { return luaTh } + +func timerArg(c *rt.GoCont, arg int) (*timer, error) { + j, ok := valueToTimer(c.Arg(arg)) + if !ok { + return nil, fmt.Errorf("#%d must be a timer", arg + 1) + } + + return j, nil +} + +func valueToTimer(val rt.Value) (*timer, bool) { + u, ok := val.TryUserData() + if !ok { + return nil, false + } + + j, ok := u.Value().(*timer) + return j, ok +} + +func timerUserData(j *timer) *rt.UserData { + timerMeta := l.Registry(timerMetaKey) + return rt.NewUserData(j, timerMeta.AsTable()) +}