diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1d377..ab8a2a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,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/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/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/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/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()) +}