diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a06ed29 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = tab diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 371d284..4aab838 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: '1.17.7' + go-version: '1.18.8' - name: Download Task run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d' - name: Build diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e378376..6515d25 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,13 +2,14 @@ name: Generate docs on: push: - branches: [master] + branches: + - master jobs: gen: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-go@v2 - name: Run docgen run: go run cmd/docgen/docgen.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a3a2840..416c97e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,10 +33,14 @@ jobs: - uses: actions/checkout@v3 with: submodules: true + fetch-depth: 0 + - name: Download Task + run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d' - uses: wangyoucao577/go-release-action@v1.25 with: github_token: ${{ secrets.GITHUB_TOKEN }} goos: ${{ matrix.goos }} goarch: ${{ matrix.goarch }} + ldflags: '-s -w' binary_name: hilbish extra_files: LICENSE README.md CHANGELOG.md .hilbishrc.lua nature libs docs emmyLuaDocs diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 0000000..547673b --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,31 @@ +name: Build website + +on: + push: + branches: + - master + - docs-refactor + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + fetch-depth: 0 + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: 'latest' + extended: true + + - name: Build + run: 'cd website && hugo --minify' + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./website/public diff --git a/.gitignore b/.gitignore index 338ef97..1abf82c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ *.exe hilbish +!docs/api/hilbish docgen +!cmd/docgen .vim petals/ +.hugo_build.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 49904ad..ec8db20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,44 @@ # 🎀 Changelog ## Unreleased -**NOTE:** Hilbish now uses [Task] insead of Make for builds. +### Fixed +- Replaced `sed` in-place editing with `grep` and `mv` for compatibility with BSD utils + +## [2.1.0] - 2022-02-10 +### Added +- Documented custom userdata types (Job and Timer Objects) + - Coming with this fix is also adding the return types for some functions that were missing it +- Added a dedicated input and dedicated outputs for commanders (sinks - info at `doc api commander`). +- Local docs is used if one of Hilbish's branches is found +- Return 1 exit code on doc not found +- `hilbish.runner.getCurrent()` to get the current runner +- Initialize Hilbish Lua API before handling signals + +### Fixed +- `index` or `_index` subdocs should not show up anymore +- `hilbish.which` not working correctly with aliases +- Commanders not being able to pipe with commands or any related operator. +- Resolve symlinks in completions +- Updated `runner-mode` docs +- Fix `hilbish.completion` functions panicking when empty input is provided + +## [2.0.1] - 2022-12-28 +### Fixed +- Corrected documentation for hooks, removing outdated `command.no-perm` +- Fixed an issue where `cd` with no args would not update the old pwd +- Tiny documentation enhancements for the `hilbish.timer` interface + +## [2.0.0] - 2022-12-20 +**NOTES FOR USERS/PACKAGERS UPDATING:** +- Hilbish now uses [Task] insead of Make for builds. +- The doc format has been changed from plain text to markdown. +**YOU MUST reinstall Hilbish to remove the duplicate, old docs.** +- Hilbish will by default install to **`/usr/local`** instead of just `/usr/` +when building via Task. This is mainly to avoid conflict of distro packages +and local installs, and is the correct place when building from git either way. +To keep Hilbish in `/usr`, you must have `PREFIX="/usr/"` when running `task build` or `task install` +- Windows is no longer supported. It will build and run, but **will** have problems. +If you want to help fix the situation, start a discussion or open an issue and contribute. [Task]: https://taskfile.dev/#/ @@ -39,7 +76,7 @@ without arguments will disown the last job. fields on a job object. - Documentation for jobs is now available via `doc jobs`. - `hilbish.alias.resolve(cmdstr)` to resolve a command alias. -- `hilbish.opts` for shell options. Currently, the only opt is `autocd`. +- `hilbish.opts` for shell options. - `hilbish.editor` interface for interacting with the line editor that Hilbish uses. - `hilbish.vim` interface to dynamically get/set vim registers. @@ -73,12 +110,20 @@ disables commands being added to history. - A new and "safer" event emitter has been added. This causes a performance deficit, but avoids a lot of random errors introduced with the new Lua runtime (see [#197]) - `bait.release(name, catcher)` removes `handler` for the named `event` +- `exec`, `clear` and `cat` builtin commands +- `hilbish.cancel` hook thrown when user cancels input with Ctrl-C +- 1st item on history is now inserted when history search menu is opened ([#148]) +- Documentation has been improved vastly! +[#148]: https://github.com/Rosettea/Hilbish/issues/148 [#197]: https://github.com/Rosettea/Hilbish/issues/197 ### Changed - **Breaking Change:** Upgraded to Lua 5.4. This is probably one of (if not the) biggest things in this release. +To recap quickly on what matters (mostly): + - `os.execute` returns 3 values instead of 1 (but you should be using `hilbish.run`) + - I/O operations must be flushed (`io.flush()`) - **Breaking Change:** MacOS config paths now match Linux. - Overrides on the `hilbish` table are no longer permitted. - **Breaking Change:** Runner functions are now required to return a table. @@ -97,6 +142,7 @@ of a dot. (ie. `job.stop()` -> `job:stop()`) - All `fs` module functions which take paths now implicitly expand ~ to home. - **Breaking Change:** `hilbish.greeting` has been moved to an opt (`hilbish.opts.greeting`) and is always printed by default. To disable it, set the opt to false. +- **Breaking Change:** `command.no-perm` hook has been replaced with `command.not-executable` - History is now fetched from Lua, which means users can override `hilbish.history` methods to make it act how they want. - `guide` has been removed. See the [website](https://rosettea.github.io/Hilbish/) @@ -112,7 +158,7 @@ replacing the last character. - `hilbish.login` being the wrong value. - Put full input in history if prompted for continued input - Don't put alias expanded command in history (sound familiar?) -- Handle cases of stdin being nonblocking (in the case of [#130](https://github.com/Rosettea/Hilbish/issues/130)) +- Handle cases of stdin being nonblocking (in the case of [#136](https://github.com/Rosettea/Hilbish/issues/136)) - Don't prompt for continued input if non interactive - Don't insert unhandled control keys. - Handle sh syntax error in alias @@ -122,11 +168,12 @@ certain color rules. - Home/End keys now go to the actual start/end of the input. - Input getting cut off on enter in certain cases. - Go to the next line properly if input reaches end of terminal width. -- Cursor position with CJK characters. ([#145](https://github.com/Rosettea/Hilbish/pull/145)) -- Files with same name as parent folder in completions getting cut off [#136](https://github.com/Rosettea/Hilbish/issues/136)) +- Cursor position with CJK characters has been corrected ([#145](https://github.com/Rosettea/Hilbish/pull/145)) +- Files with same name as parent folder in completions getting cut off [#130](https://github.com/Rosettea/Hilbish/issues/130)) - `hilbish.which` now works with commanders and aliases. - Background jobs no longer take stdin so they do not interfere with shell input. +- Full name of completion entry is used instead of being cut off - Completions are fixed in cases where the query/line is an alias alone where it can also resolve to the beginning of command names. (reference [this commit](https://github.com/Rosettea/Hilbish/commit/2790982ad123115c6ddbc5764677fdca27668cea)) @@ -145,6 +192,29 @@ menu is open. - Escape codes now work. - Escape percentage symbols in completion entries, so you will no longer see an error of missing format variable +- Fix an error with sh syntax in aliases +- Prompt now works with east asian characters (CJK) +- Set back the prompt to normal after exiting the continue prompt with ctrl-d +- Take into account newline in input when calculating input width. Prevents +extra reprinting of the prompt, but input with newlines inserted is still a problem +- Put cursor at the end of input when exiting $EDITOR with Vim mode bind +- Calculate width of virtual input properly (completion candidates) +- Users can now tab complete files with spaces while quoted or with escaped spaces. +This means a query of `Files\ to\ ` with file names of `Files to tab complete` and `Files to complete` +will result in the files being completed. +- Fixed grid menu display if cell width ends up being the width of the terminal +- Cut off item names in grid menu if its longer than cell width +- Fix completion search menu disappearing +- Make binary completion work with bins that have spaces in the name +- Completion paths having duplicated characters if it's escaped +- Get custom completion command properly to call from Lua +- Put proper command on the line when using up and down arrow keys to go through command history +- Don't do anything if length of input rune slice is 0 ([commit for explanation](https://github.com/Rosettea/Hilbish/commit/8d40179a73fe5942707cd43f9c0463dee53eedd8)) + +## [2.0.0-rc1] - 2022-09-14 +This is a pre-release version of Hilbish for testing. To see the changelog, +refer to the `Unreleased` section of the [full changelog](CHANGELOG.md) +(version 2.0.0 for future reference). ## [1.2.0] - 2022-03-17 ### Added @@ -569,6 +639,11 @@ This input for example will prompt for more input to complete: First "stable" release of Hilbish. +[2.1.0]: https://github.com/Rosettea/Hilbish/compare/v2.0.1...v2.1.0 +[2.0.1]: https://github.com/Rosettea/Hilbish/compare/v2.0.0...v2.0.1 +[2.0.0]: https://github.com/Rosettea/Hilbish/compare/v1.2.0...v2.0.0 +[2.0.0-rc1]: https://github.com/Rosettea/Hilbish/compare/v1.2.0...v2.0.0-rc1 +[1.2.0]: https://github.com/Rosettea/Hilbish/compare/v1.1.4...v1.2.0 [1.1.0]: https://github.com/Rosettea/Hilbish/compare/v1.0.4...v1.1.0 [1.0.4]: https://github.com/Rosettea/Hilbish/compare/v1.0.3...v1.0.4 [1.0.3]: https://github.com/Rosettea/Hilbish/compare/v1.0.2...v1.0.3 diff --git a/LICENSE b/LICENSE index da3c8c1..3d8f013 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Rosettea +Copyright (c) 2021-2023 Rosettea Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 86879d1..3a3dbbb 100644 --- a/README.md +++ b/README.md @@ -26,49 +26,30 @@ and aims to be infinitely configurable. If something isn't, open an issue! # Table of Contents - [Screenshots](#Screenshots) -- [Installation](#Installation) - - [Prebuilt Bins](#Prebuilt-binaries) - - [AUR](#AUR) - - [Nixpkgs](#Nixpkgs) - - [Manual Build](#Manual-Build) -- [Getting Started](#Getting-Started) +- [Getting Hilbish](#Getting-Hilbish) - [Contributing](#Contributing) # Screenshots
-



+

-# Installation -## Prebuilt binaries -Go [here](https://nightly.link/Rosettea/Hilbish/workflows/build/master) for -builds on the master branch. +# Getting Hilbish +**NOTE:** Hilbish is not guaranteed to work properly on Windows, starting +from the 2.0 version. It will still be able to compile, but functionality +may be lacking. -## AUR -[![AUR maintainer](https://img.shields.io/aur/maintainer/hilbish?logo=arch-linux&style=flat-square)](https://aur.archlinux.org/packages/hilbish) -Arch Linux users can install Hilbish from the AUR with the following command: -```sh -yay -S hilbish -``` +You can check the [install page](https://rosettea.github.io/Hilbish/install/) +on the website for distributed binaries from GitHub or other package repositories. +Otherwise, continue reading for steps on compiling. -[![AUR maintainer](https://img.shields.io/aur/maintainer/hilbish?logo=arch-linux&style=flat-square)](https://aur.archlinux.org/packages/hilbish-git) -Or from the latest `master` commit with: -```sh -yay -S hilbish-git -``` - -## Nixpkgs -Nix/NixOS users can install Hilbish from the central repository, nixpkgs, through the usual ways. -If you're new to nix you should probably read up on how to do that [here](https://nixos.wiki/wiki/Cheatsheet). - -## Manual Build -### Prerequisites +## Prerequisites - [Go 1.17+](https://go.dev) -- [Task](https://taskfile.dev/#/) +- [Task](https://taskfile.dev/installation/) (**Go on the hyperlink here to see Task's install method for your OS.**) -### Build +## Build First, clone Hilbish. The recursive is required, as some Lua libraries are submodules. ```sh diff --git a/Taskfile.yaml b/Taskfile.yaml index 067f2ba..5c5caae 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -3,23 +3,24 @@ version: '3' vars: - PREFIX: '{{default "/usr" .PREFIX}}' + PREFIX: '{{default "/usr/local" .PREFIX}}' bindir__: '{{.PREFIX}}/bin' BINDIR: '{{default .bindir__ .BINDIR}}' libdir__: '{{.PREFIX}}/share/hilbish' LIBDIR: '{{default .libdir__ .LIBDIR}}' - GOFLAGS: '-ldflags "-s -w"' + goflags__: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}}"' + GOFLAGS: '{{default .goflags__ .GOFLAGS}}' tasks: default: cmds: - - go build {{.GOFLAGS}} + - CGO_ENABLED=0 go build {{.GOFLAGS}} vars: - GOFLAGS: '-ldflags "-s -w -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"' + GOFLAGS: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}} -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"' build: cmds: - - go build {{.GOFLAGS}} + - CGO_ENABLED=0 go build {{.GOFLAGS}} install: cmds: @@ -33,4 +34,4 @@ tasks: - rm -vrf "{{.DESTDIR}}{{.BINDIR}}/hilbish" "{{.DESTDIR}}{{.LIBDIR}}" - - sed -i '/hilbish/d' /etc/shells + - grep -v 'hilbish' /etc/shells > /tmp/shells.hilbish_uninstall && mv /tmp/shells.hilbish_uninstall /etc/shells diff --git a/aliases.go b/aliases.go index 3007cc3..bfacc43 100644 --- a/aliases.go +++ b/aliases.go @@ -9,40 +9,40 @@ import ( rt "github.com/arnodel/golua/runtime" ) -var aliases *aliasHandler +var aliases *aliasModule -type aliasHandler struct { +type aliasModule struct { aliases map[string]string mu *sync.RWMutex } // initialize aliases map -func newAliases() *aliasHandler { - return &aliasHandler{ +func newAliases() *aliasModule { + return &aliasModule{ aliases: make(map[string]string), mu: &sync.RWMutex{}, } } -func (a *aliasHandler) Add(alias, cmd string) { +func (a *aliasModule) Add(alias, cmd string) { a.mu.Lock() defer a.mu.Unlock() a.aliases[alias] = cmd } -func (a *aliasHandler) All() map[string]string { +func (a *aliasModule) All() map[string]string { return a.aliases } -func (a *aliasHandler) Delete(alias string) { +func (a *aliasModule) Delete(alias string) { a.mu.Lock() defer a.mu.Unlock() delete(a.aliases, alias) } -func (a *aliasHandler) Resolve(cmdstr string) string { +func (a *aliasModule) Resolve(cmdstr string) string { a.mu.RLock() defer a.mu.RUnlock() @@ -66,7 +66,10 @@ func (a *aliasHandler) Resolve(cmdstr string) string { // lua section -func (a *aliasHandler) Loader(rtm *rt.Runtime) *rt.Table { +// #interface aliases +// command aliasing +// The alias interface deals with all command aliases in Hilbish. +func (a *aliasModule) Loader(rtm *rt.Runtime) *rt.Table { // create a lua module with our functions hshaliasesLua := map[string]util.LuaExport{ "add": util.LuaExport{hlalias, 2, false}, @@ -81,7 +84,18 @@ func (a *aliasHandler) Loader(rtm *rt.Runtime) *rt.Table { return mod } -func (a *aliasHandler) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { +// #interface aliases +// add(alias, cmd) +// This is an alias (ha) for the `hilbish.alias` function. +// --- @param alias string +// --- @param cmd string +func _hlalias() {} + +// #interface aliases +// list() -> table +// Get a table of all aliases, with string keys as the alias and the value as the command. +// --- @returns table +func (a *aliasModule) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { aliasesList := rt.NewTable() for k, v := range a.All() { aliasesList.Set(rt.StringValue(k), rt.StringValue(v)) @@ -90,7 +104,11 @@ func (a *aliasHandler) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.TableValue(aliasesList)), nil } -func (a *aliasHandler) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { +// #interface aliases +// delete(name) +// Removes an alias. +// --- @param name string +func (a *aliasModule) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err } @@ -103,7 +121,12 @@ func (a *aliasHandler) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -func (a *aliasHandler) luaResolve(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { +// #interface aliases +// resolve(alias) -> command (string) +// Tries to resolve an alias to its command. +// --- @param alias string +// --- @returns string +func (a *aliasModule) luaResolve(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err } diff --git a/api.go b/api.go index cfa3d06..3d3c887 100644 --- a/api.go +++ b/api.go @@ -1,6 +1,14 @@ -// Here is the core api for the hilbi shell itself -// Basically, stuff about the shell itself and other functions -// go here. +// the core Hilbish API +// The Hilbish module includes the core API, containing +// interfaces and functions which directly relate to shell functionality. +// #field ver The version of Hilbish +// #field user Username of the user +// #field host Hostname of the machine +// #field dataDir Directory for Hilbish data files, including the docs and default modules +// #field interactive Is Hilbish in an interactive shell? +// #field login Is Hilbish the login shell? +// #field vimMode Current Vim input mode of Hilbish (will be nil if not in Vim input mode) +// #field exitCode xit code of the last executed command package main import ( @@ -19,7 +27,6 @@ import ( rt "github.com/arnodel/golua/runtime" "github.com/arnodel/golua/lib/packagelib" "github.com/maxlandon/readline" - "github.com/blackfireio/osinfo" "mvdan.cc/sh/v3/interp" ) @@ -102,78 +109,59 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) { username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows } - util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(getVersion()), "Hilbish version") - util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username), "Username of user") - util.SetFieldProtected(fakeMod, mod, "host", rt.StringValue(host), "Host name of the machine") - util.SetFieldProtected(fakeMod, mod, "home", rt.StringValue(curuser.HomeDir), "Home directory of the user") - util.SetFieldProtected(fakeMod, mod, "dataDir", rt.StringValue(dataDir), "Directory for Hilbish's data files") - util.SetFieldProtected(fakeMod, mod, "interactive", rt.BoolValue(interactive), "If this is an interactive shell") - util.SetFieldProtected(fakeMod, mod, "login", rt.BoolValue(login), "Whether this is a login shell") - util.SetFieldProtected(fakeMod, mod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)") - util.SetFieldProtected(fakeMod, mod, "exitCode", rt.IntValue(0), "Exit code of last exected command") - util.Document(fakeMod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.") + util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(getVersion())) + util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username)) + util.SetFieldProtected(fakeMod, mod, "host", rt.StringValue(host)) + util.SetFieldProtected(fakeMod, mod, "home", rt.StringValue(curuser.HomeDir)) + util.SetFieldProtected(fakeMod, mod, "dataDir", rt.StringValue(dataDir)) + util.SetFieldProtected(fakeMod, mod, "interactive", rt.BoolValue(interactive)) + util.SetFieldProtected(fakeMod, mod, "login", rt.BoolValue(login)) + util.SetFieldProtected(fakeMod, mod, "vimMode", rt.NilValue) + util.SetFieldProtected(fakeMod, mod, "exitCode", rt.IntValue(0)) // hilbish.userDir table - hshuser := rt.NewTable() - - util.SetField(rtm, hshuser, "config", rt.StringValue(confDir), "User's config directory") - util.SetField(rtm, hshuser, "data", rt.StringValue(userDataDir), "XDG data directory") - util.Document(hshuser, "User directories to store configs and/or modules.") + hshuser := userDirLoader(rtm) mod.Set(rt.StringValue("userDir"), rt.TableValue(hshuser)) // hilbish.os table - hshos := rt.NewTable() - info, _ := osinfo.GetOSInfo() - - util.SetField(rtm, hshos, "family", rt.StringValue(info.Family), "Family name of the current OS") - util.SetField(rtm, hshos, "name", rt.StringValue(info.Name), "Pretty name of the current OS") - util.SetField(rtm, hshos, "version", rt.StringValue(info.Version), "Version of the current OS") - util.Document(hshos, "OS info interface") + hshos := hshosLoader(rtm) mod.Set(rt.StringValue("os"), rt.TableValue(hshos)) // hilbish.aliases table aliases = newAliases() aliasesModule := aliases.Loader(rtm) - util.Document(aliasesModule, "Alias inferface for Hilbish.") mod.Set(rt.StringValue("aliases"), rt.TableValue(aliasesModule)) // hilbish.history table historyModule := lr.Loader(rtm) mod.Set(rt.StringValue("history"), rt.TableValue(historyModule)) - util.Document(historyModule, "History interface for Hilbish.") // hilbish.completion table hshcomp := completionLoader(rtm) - util.Document(hshcomp, "Completions interface for Hilbish.") mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp)) // hilbish.runner table runnerModule := runnerModeLoader(rtm) - util.Document(runnerModule, "Runner/exec interface for Hilbish.") mod.Set(rt.StringValue("runner"), rt.TableValue(runnerModule)) // hilbish.jobs table jobs = newJobHandler() jobModule := jobs.loader(rtm) - util.Document(jobModule, "(Background) job interface.") mod.Set(rt.StringValue("jobs"), rt.TableValue(jobModule)) // hilbish.timers table - timers = newTimerHandler() - timerModule := timers.loader(rtm) - util.Document(timerModule, "Timer interface, for control of all intervals and timeouts.") - mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule)) + timers = newTimersModule() + timersModule := timers.loader(rtm) + mod.Set(rt.StringValue("timers"), rt.TableValue(timersModule)) editorModule := editorLoader(rtm) - util.Document(editorModule, "") mod.Set(rt.StringValue("editor"), rt.TableValue(editorModule)) versionModule := rt.NewTable() - util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch), "Git branch Hilbish was compiled from") - util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion()), "Full version info, including release name") - util.SetField(rtm, versionModule, "commit", rt.StringValue(gitCommit), "Git commit Hilbish was compiled from") - util.SetField(rtm, versionModule, "release", rt.StringValue(releaseName), "Release name") - util.Document(versionModule, "Version info interface.") + util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch)) + util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion())) + util.SetField(rtm, versionModule, "commit", rt.StringValue(gitCommit)) + util.SetField(rtm, versionModule, "release", rt.StringValue(releaseName)) mod.Set(rt.StringValue("version"), rt.TableValue(versionModule)) pluginModule := moduleLoader(rtm) @@ -192,19 +180,21 @@ func getenv(key, fallback string) string { } func setVimMode(mode string) { - util.SetField(l, hshMod, "vimMode", rt.StringValue(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)") + util.SetField(l, hshMod, "vimMode", rt.StringValue(mode)) hooks.Emit("hilbish.vimMode", mode) } func unsetVimMode() { - util.SetField(l, hshMod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)") + util.SetField(l, hshMod, "vimMode", rt.NilValue) } -// run(cmd, returnOut) -> exitCode, stdout, stderr +// run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string) // Runs `cmd` in Hilbish's sh interpreter. // If returnOut is true, the outputs of `cmd` will be returned as the 2nd and // 3rd values instead of being outputted to the terminal. // --- @param cmd string +// --- @param returnOut boolean +// --- @returns number, string, string func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -245,8 +235,9 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil } -// cwd() +// cwd() -> string // Returns the current directory of the shell +// --- @returns string func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { cwd, _ := os.Getwd() @@ -254,21 +245,28 @@ func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } -// read(prompt) -> input? +// read(prompt) -> input (string) // Read input from the user, using Hilbish's line editor/input reader. // This is a separate instance from the one Hilbish actually uses. // Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen) -// --- @param prompt string +// --- @param prompt? string +// --- @returns string|nil func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { - if err := c.Check1Arg(); err != nil { - return nil, err + luaprompt := c.Arg(0) + if typ := luaprompt.Type(); typ != rt.StringType && typ != rt.NilType { + return nil, errors.New("expected #1 to be a string") } - luaprompt, err := c.StringArg(0) - if err != nil { - return nil, err + prompt, ok := luaprompt.TryString() + if !ok { + // if we are here and `luaprompt` is not a string, it's nil + // substitute with an empty string + prompt = "" } - lualr := newLineReader("", true) - lualr.SetPrompt(luaprompt) + + lualr := &lineReader{ + rl: readline.NewInstance(), + } + lualr.SetPrompt(prompt) input, err := lualr.Read() if err != nil { @@ -279,7 +277,7 @@ func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } /* -prompt(str, typ?) +prompt(str, typ) Changes the shell prompt to `str` There are a few verbs that can be used in the prompt text. These will be formatted and replaced with the appropriate values. @@ -287,7 +285,7 @@ These will be formatted and replaced with the appropriate values. `%u` - Name of current user `%h` - Hostname of device --- @param str string ---- @param typ string Type of prompt, being left or right. Left by default. +--- @param typ? string Type of prompt, being left or right. Left by default. */ func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { err := c.Check1Arg() @@ -451,12 +449,12 @@ func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -// timeout(cb, time) -// Runs the `cb` function after `time` in milliseconds -// Returns a `timer` object (see `doc timers`). +// timeout(cb, time) -> @Timer +// Runs the `cb` function after `time` in milliseconds. +// This creates a timer that starts immediately. // --- @param cb function // --- @param time number -// --- @return table +// --- @returns Timer func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.CheckNArgs(2); err != nil { return nil, err @@ -477,12 +475,12 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil } -// interval(cb, time) +// interval(cb, time) -> @Timer // Runs the `cb` function every `time` milliseconds. -// Returns a `timer` object (see `doc timers`). +// This creates a timer that starts immediately. // --- @param cb function // --- @param time number -// --- @return table +// --- @return Timer func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.CheckNArgs(2); err != nil { return nil, err @@ -543,9 +541,11 @@ func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -// which(name) -// Checks if `name` is a valid command -// --- @param binName string +// which(name) -> string +// Checks if `name` is a valid command. +// Will return the path of the binary, or a basename if it's a commander. +// --- @param name string +// --- @returns string func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -555,7 +555,10 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return nil, err } - cmd := aliases.Resolve(name) + // itll return either the original command or what was passed + // if name isnt empty its not an issue + alias := aliases.Resolve(name) + cmd := strings.Split(alias, " ")[0] // check for commander if commands[cmd] != nil { @@ -630,7 +633,7 @@ func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { // as the text for the hint. This is by default a shim. To set hints, // override this function with your custom handler. // --- @param line string -// --- @param pos int +// --- @param pos number func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go index 39a2a76..aae6202 100644 --- a/cmd/docgen/docgen.go +++ b/cmd/docgen/docgen.go @@ -7,28 +7,266 @@ import ( "go/doc" "go/parser" "go/token" + "regexp" "strings" "os" + "sync" ) -type EmmyPiece struct { - FuncName string - Docs []string +var header = `--- +title: %s %s +description: %s +layout: doc +menu: + docs: + parent: "API" +--- + +` + +type emmyPiece struct { + DocPiece *docPiece + Annotations []string Params []string // we only need to know param name to put in function + FuncName string } -type DocPiece struct { + +type module struct { + Types []docPiece + Docs []docPiece + Fields []docPiece + Properties []docPiece + ShortDescription string + Description string + ParentModule string + HasInterfaces bool + HasTypes bool +} + +type docPiece struct { Doc []string FuncSig string FuncName string + Interfacing string + ParentModule string + GoFuncName string + IsInterface bool + IsMember bool + IsType bool + Fields []docPiece + Properties []docPiece +} + +type tag struct { + id string + fields []string +} + +var docs = make(map[string]module) +var interfaceDocs = make(map[string]module) +var emmyDocs = make(map[string][]emmyPiece) +var typeTable = make(map[string][]string) // [0] = parentMod, [1] = interfaces +var prefix = map[string]string{ + "main": "hl", + "hilbish": "hl", + "fs": "f", + "commander": "c", + "bait": "b", + "terminal": "term", +} + +func getTagsAndDocs(docs string) (map[string][]tag, []string) { + pts := strings.Split(docs, "\n") + parts := []string{} + tags := make(map[string][]tag) + + for _, part := range pts { + if strings.HasPrefix(part, "#") { + tagParts := strings.Split(strings.TrimPrefix(part, "#"), " ") + if tags[tagParts[0]] == nil { + var id string + if len(tagParts) > 1 { + id = tagParts[1] + } + tags[tagParts[0]] = []tag{ + {id: id}, + } + if len(tagParts) >= 2 { + tags[tagParts[0]][0].fields = tagParts[2:] + } + } else { + fleds := []string{} + if len(tagParts) >= 2 { + fleds = tagParts[2:] + } + tags[tagParts[0]] = append(tags[tagParts[0]], tag{ + id: tagParts[1], + fields: fleds, + }) + } + } else { + parts = append(parts, part) + } + } + + return tags, parts +} + +func docPieceTag(tagName string, tags map[string][]tag) []docPiece { + dps := []docPiece{} + for _, tag := range tags[tagName] { + dps = append(dps, docPiece{ + FuncName: tag.id, + Doc: tag.fields, + }) + } + + return dps +} + +func setupDocType(mod string, typ *doc.Type) *docPiece { + docs := strings.TrimSpace(typ.Doc) + tags, doc := getTagsAndDocs(docs) + + if tags["type"] == nil { + return nil + } + inInterface := tags["interface"] != nil + + var interfaces string + typeName := strings.ToUpper(string(typ.Name[0])) + typ.Name[1:] + typeDoc := []string{} + + if inInterface { + interfaces = tags["interface"][0].id + } + + fields := docPieceTag("field", tags) + properties := docPieceTag("property", tags) + + for _, d := range doc { + if strings.HasPrefix(d, "---") { + // TODO: document types in lua + /* + emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---")) + emmyLinePieces := strings.Split(emmyLine, " ") + emmyType := emmyLinePieces[0] + if emmyType == "@param" { + em.Params = append(em.Params, emmyLinePieces[1]) + } + if emmyType == "@vararg" { + em.Params = append(em.Params, "...") // add vararg + } + em.Annotations = append(em.Annotations, d) + */ + } else { + typeDoc = append(typeDoc, d) + } + } + + var isMember bool + if tags["member"] != nil { + isMember = true + } + parentMod := mod + dps := &docPiece{ + Doc: typeDoc, + FuncName: typeName, + Interfacing: interfaces, + IsInterface: inInterface, + IsMember: isMember, + IsType: true, + ParentModule: parentMod, + Fields: fields, + Properties: properties, + } + + typeTable[strings.ToLower(typeName)] = []string{parentMod, interfaces} + + return dps +} + +func setupDoc(mod string, fun *doc.Func) *docPiece { + docs := strings.TrimSpace(fun.Doc) + tags, parts := getTagsAndDocs(docs) + + // i couldnt fit this into the condition below for some reason so here's a goto! + if tags["member"] != nil { + goto start + } + + if (!strings.HasPrefix(fun.Name, prefix[mod]) && tags["interface"] == nil) || (strings.ToLower(fun.Name) == "loader" && tags["interface"] == nil) { + return nil + } + +start: + inInterface := tags["interface"] != nil + var interfaces string + funcsig := parts[0] + doc := parts[1:] + funcName := strings.TrimPrefix(fun.Name, prefix[mod]) + funcdoc := []string{} + + if inInterface { + interfaces = tags["interface"][0].id + funcName = interfaces + "." + strings.Split(funcsig, "(")[0] + } + em := emmyPiece{FuncName: funcName} + + fields := docPieceTag("field", tags) + properties := docPieceTag("property", tags) + + for _, d := range doc { + if strings.HasPrefix(d, "---") { + emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---")) + emmyLinePieces := strings.Split(emmyLine, " ") + emmyType := emmyLinePieces[0] + if emmyType == "@param" { + em.Params = append(em.Params, emmyLinePieces[1]) + } + if emmyType == "@vararg" { + em.Params = append(em.Params, "...") // add vararg + } + em.Annotations = append(em.Annotations, d) + } else { + funcdoc = append(funcdoc, d) + } + } + + var isMember bool + if tags["member"] != nil { + isMember = true + } + var parentMod string + if inInterface { + parentMod = mod + } + dps := &docPiece{ + Doc: funcdoc, + FuncSig: funcsig, + FuncName: funcName, + Interfacing: interfaces, + GoFuncName: strings.ToLower(fun.Name), + IsInterface: inInterface, + IsMember: isMember, + ParentModule: parentMod, + Fields: fields, + Properties: properties, + } + if strings.HasSuffix(dps.GoFuncName, strings.ToLower("loader")) { + dps.Doc = parts + } + em.DocPiece = dps + + emmyDocs[mod] = append(emmyDocs[mod], em) + return dps } -// feel free to clean this up -// it works, dont really care about the code func main() { fset := token.NewFileSet() os.Mkdir("docs", 0777) + os.Mkdir("docs/api", 0777) os.Mkdir("emmyLuaDocs", 0777) - dirs := []string{"./"} filepath.Walk("golibs/", func (path string, info os.FileInfo, err error) error { @@ -51,120 +289,266 @@ func main() { } } - prefix := map[string]string{ - "hilbish": "hl", - "fs": "f", - "commander": "c", - "bait": "b", - "terminal": "term", - } - docs := make(map[string][]DocPiece) - emmyDocs := make(map[string][]EmmyPiece) - + interfaceModules := make(map[string]*module) for l, f := range pkgs { p := doc.New(f, "./", doc.AllDecls) + pieces := []docPiece{} + typePieces := []docPiece{} + mod := l + if mod == "main" { + mod = "hilbish" + } + var hasInterfaces bool for _, t := range p.Funcs { - mod := l - if strings.HasPrefix(t.Name, "hl") { mod = "hilbish" } - if !strings.HasPrefix(t.Name, prefix[mod]) || t.Name == "Loader" { continue } - parts := strings.Split(strings.TrimSpace(t.Doc), "\n") - funcsig := parts[0] - doc := parts[1:] - funcdoc := []string{} - em := EmmyPiece{FuncName: strings.TrimPrefix(t.Name, prefix[mod])} - for _, d := range doc { - if strings.HasPrefix(d, "---") { - emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---")) - emmyLinePieces := strings.Split(emmyLine, " ") - emmyType := emmyLinePieces[0] - if emmyType == "@param" { - em.Params = append(em.Params, emmyLinePieces[1]) - } - if emmyType == "@vararg" { - em.Params = append(em.Params, "...") // add vararg - } - em.Docs = append(em.Docs, d) - } else { - funcdoc = append(funcdoc, d) - } + piece := setupDoc(mod, t) + if piece == nil { + continue } - - dps := DocPiece{ - Doc: funcdoc, - FuncSig: funcsig, - FuncName: strings.TrimPrefix(t.Name, prefix[mod]), + + pieces = append(pieces, *piece) + if piece.IsInterface { + hasInterfaces = true } - - docs[mod] = append(docs[mod], dps) - emmyDocs[mod] = append(emmyDocs[mod], em) } for _, t := range p.Types { - for _, m := range t.Methods { - if !strings.HasPrefix(m.Name, prefix[l]) || m.Name == "Loader" { continue } - parts := strings.Split(strings.TrimSpace(m.Doc), "\n") - funcsig := parts[0] - doc := parts[1:] - funcdoc := []string{} - em := EmmyPiece{FuncName: strings.TrimPrefix(m.Name, prefix[l])} - for _, d := range doc { - if strings.HasPrefix(d, "---") { - emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---")) - emmyLinePieces := strings.Split(emmyLine, " ") - emmyType := emmyLinePieces[0] - if emmyType == "@param" { - em.Params = append(em.Params, emmyLinePieces[1]) - } - if emmyType == "@vararg" { - em.Params = append(em.Params, "...") // add vararg - } - em.Docs = append(em.Docs, d) - } else { - funcdoc = append(funcdoc, d) - } + typePiece := setupDocType(mod, t) + if typePiece != nil { + typePieces = append(typePieces, *typePiece) + if typePiece.IsInterface { + hasInterfaces = true } - dps := DocPiece{ - Doc: funcdoc, - FuncSig: funcsig, - FuncName: strings.TrimPrefix(m.Name, prefix[l]), + } + + for _, m := range t.Methods { + piece := setupDoc(mod, m) + if piece == nil { + continue } - docs[l] = append(docs[l], dps) - emmyDocs[l] = append(emmyDocs[l], em) + pieces = append(pieces, *piece) + if piece.IsInterface { + hasInterfaces = true + } } } + + tags, descParts := getTagsAndDocs(strings.TrimSpace(p.Doc)) + shortDesc := descParts[0] + desc := descParts[1:] + filteredPieces := []docPiece{} + filteredTypePieces := []docPiece{} + for _, piece := range pieces { + if !piece.IsInterface { + filteredPieces = append(filteredPieces, piece) + continue + } + + modname := piece.ParentModule + "." + piece.Interfacing + if interfaceModules[modname] == nil { + interfaceModules[modname] = &module{ + ParentModule: piece.ParentModule, + } + } + + if strings.HasSuffix(piece.GoFuncName, strings.ToLower("loader")) { + shortDesc := piece.Doc[0] + desc := piece.Doc[1:] + interfaceModules[modname].ShortDescription = shortDesc + interfaceModules[modname].Description = strings.Join(desc, "\n") + interfaceModules[modname].Fields = piece.Fields + interfaceModules[modname].Properties = piece.Properties + continue + } + + interfaceModules[modname].Docs = append(interfaceModules[modname].Docs, piece) + } + + for _, piece := range typePieces { + if !piece.IsInterface { + filteredTypePieces = append(filteredTypePieces, piece) + continue + } + + modname := piece.ParentModule + "." + piece.Interfacing + if interfaceModules[modname] == nil { + interfaceModules[modname] = &module{ + ParentModule: piece.ParentModule, + } + } + + interfaceModules[modname].Types = append(interfaceModules[modname].Types, piece) + } + + docs[mod] = module{ + Types: filteredTypePieces, + Docs: filteredPieces, + ShortDescription: shortDesc, + Description: strings.Join(desc, "\n"), + HasInterfaces: hasInterfaces, + Properties: docPieceTag("property", tags), + Fields: docPieceTag("field", tags), + } } + for key, mod := range interfaceModules { + docs[key] = *mod + } + + var wg sync.WaitGroup + wg.Add(len(docs) * 2) + for mod, v := range docs { - if mod == "main" { continue } - f, _ := os.Create("docs/" + mod + ".txt") - for _, dps := range v { - f.WriteString(dps.FuncSig + " > ") - for _, doc := range dps.Doc { - if !strings.HasPrefix(doc, "---") { - f.WriteString(doc + "\n") + docPath := "docs/api/" + mod + ".md" + if v.HasInterfaces { + os.Mkdir("docs/api/" + mod, 0777) + os.Remove(docPath) // remove old doc path if it exists + docPath = "docs/api/" + mod + "/_index.md" + } + if v.ParentModule != "" { + docPath = "docs/api/" + v.ParentModule + "/" + mod + ".md" + } + + go func(modname, docPath string, modu module) { + defer wg.Done() + modOrIface := "Module" + if modu.ParentModule != "" { + modOrIface = "Interface" + } + + f, _ := os.Create(docPath) + f.WriteString(fmt.Sprintf(header, modOrIface, modname, modu.ShortDescription)) + typeTag, _ := regexp.Compile(`@\w+`) + modDescription := typeTag.ReplaceAllStringFunc(strings.Replace(modu.Description, "<", `\<`, -1), func(typ string) string { + typName := typ[1:] + typLookup := typeTable[strings.ToLower(typName)] + ifaces := typLookup[0] + "." + typLookup[1] + "/" + if typLookup[1] == "" { + ifaces = "" + } + linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s#%s", typLookup[0], ifaces, strings.ToLower(typName)) + return fmt.Sprintf(`%s`, linkedTyp, typName) + }) + f.WriteString(fmt.Sprintf("## Introduction\n%s\n\n", modDescription)) + if len(modu.Fields) != 0 { + f.WriteString("## Interface fields\n") + for _, dps := range modu.Fields { + f.WriteString(fmt.Sprintf("- `%s`: ", dps.FuncName)) + f.WriteString(strings.Join(dps.Doc, " ")) + f.WriteString("\n") + } + f.WriteString("\n") + } + if len(modu.Properties) != 0 { + f.WriteString("## Object properties\n") + for _, dps := range modu.Properties { + f.WriteString(fmt.Sprintf("- `%s`: ", dps.FuncName)) + f.WriteString(strings.Join(dps.Doc, " ")) + f.WriteString("\n") + } + f.WriteString("\n") + } + + if len(modu.Docs) != 0 { + f.WriteString("## Functions\n") + for _, dps := range modu.Docs { + if dps.IsMember { + continue + } + htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(dps.FuncSig, "<", `\<`, -1), func(typ string) string { + typName := typ[1:] + typLookup := typeTable[strings.ToLower(typName)] + ifaces := typLookup[0] + "." + typLookup[1] + "/" + if typLookup[1] == "" { + ifaces = "" + } + linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s#%s", typLookup[0], ifaces, strings.ToLower(typName)) + return fmt.Sprintf(`%s`, linkedTyp, typName) + }) + f.WriteString(fmt.Sprintf("### %s\n", htmlSig)) + for _, doc := range dps.Doc { + if !strings.HasPrefix(doc, "---") { + f.WriteString(doc + "\n") + } + } + f.WriteString("\n") } } - f.WriteString("\n") - } - } - - for mod, v := range emmyDocs { - if mod == "main" { continue } - f, _ := os.Create("emmyLuaDocs/" + mod + ".lua") - f.WriteString("--- @meta\n\nlocal " + mod + " = {}\n\n") - for _, em := range v { - var funcdocs []string - for _, dps := range docs[mod] { - if dps.FuncName == em.FuncName { - funcdocs = dps.Doc + + if len(modu.Types) != 0 { + f.WriteString("## Types\n") + for _, dps := range modu.Types { + f.WriteString(fmt.Sprintf("## %s\n", dps.FuncName)) + for _, doc := range dps.Doc { + if !strings.HasPrefix(doc, "---") { + f.WriteString(doc + "\n") + } + } + if len(dps.Properties) != 0 { + f.WriteString("### Properties\n") + for _, dps := range dps.Properties { + f.WriteString(fmt.Sprintf("- `%s`: ", dps.FuncName)) + f.WriteString(strings.Join(dps.Doc, " ")) + f.WriteString("\n") + } + } + f.WriteString("\n") + f.WriteString("### Methods\n") + for _, dps := range modu.Docs { + if !dps.IsMember { + continue + } + htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(dps.FuncSig, "<", `\<`, -1), func(typ string) string { + typName := regexp.MustCompile(`\w+`).FindString(typ[1:]) + typLookup := typeTable[strings.ToLower(typName)] + fmt.Printf("%+q, \n", typLookup) + linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s/#%s", typLookup[0], typLookup[0] + "." + typLookup[1], strings.ToLower(typName)) + return fmt.Sprintf(`%s`, linkedTyp, typName) + }) + f.WriteString(fmt.Sprintf("#### %s\n", htmlSig)) + for _, doc := range dps.Doc { + if !strings.HasPrefix(doc, "---") { + f.WriteString(doc + "\n") + } + } + f.WriteString("\n") + } } } - f.WriteString("--- " + strings.Join(funcdocs, "\n--- ") + "\n") - if len(em.Docs) != 0 { - f.WriteString(strings.Join(em.Docs, "\n") + "\n") + }(mod, docPath, v) + + go func(md, modname string, modu module) { + defer wg.Done() + + if modu.ParentModule != "" { + return } - f.WriteString("function " + mod + "." + em.FuncName + "(" + strings.Join(em.Params, ", ") + ") end\n\n") - } - f.WriteString("return " + mod + "\n") + + ff, _ := os.Create("emmyLuaDocs/" + modname + ".lua") + ff.WriteString("--- @meta\n\nlocal " + modname + " = {}\n\n") + for _, em := range emmyDocs[modname] { + if strings.HasSuffix(em.DocPiece.GoFuncName, strings.ToLower("loader")) { + continue + } + + dps := em.DocPiece + funcdocs := dps.Doc + ff.WriteString("--- " + strings.Join(funcdocs, "\n--- ") + "\n") + if len(em.Annotations) != 0 { + ff.WriteString(strings.Join(em.Annotations, "\n") + "\n") + } + accessor := "." + if dps.IsMember { + accessor = ":" + } + signature := strings.Split(dps.FuncSig, " ->")[0] + var intrface string + if dps.IsInterface { + intrface = "." + dps.Interfacing + } + ff.WriteString("function " + modname + intrface + accessor + signature + " end\n\n") + } + ff.WriteString("return " + modname + "\n") + }(mod, mod, v) } + wg.Wait() } diff --git a/complete.go b/complete.go index 76d65f7..51b426f 100644 --- a/complete.go +++ b/complete.go @@ -11,15 +11,49 @@ import ( rt "github.com/arnodel/golua/runtime" ) -func splitQuote(str string) []string { +var charEscapeMap = []string{ + "\"", "\\\"", + "'", "\\'", + "`", "\\`", + " ", "\\ ", + "(", "\\(", + ")", "\\)", + "[", "\\[", + "]", "\\]", + "$", "\\$", + "&", "\\&", + "*", "\\*", + ">", "\\>", + "<", "\\<", + "|", "\\|", +} +var charEscapeMapInvert = invert(charEscapeMap) +var escapeReplaer = strings.NewReplacer(charEscapeMap...) +var escapeInvertReplaer = strings.NewReplacer(charEscapeMapInvert...) + +func invert(m []string) []string { + newM := make([]string, len(charEscapeMap)) + for i := range m { + if (i + 1) % 2 == 0 { + newM[i] = m[i - 1] + newM[i - 1] = m[i] + } + } + + return newM +} + +func splitForFile(str string) []string { split := []string{} sb := &strings.Builder{} quoted := false - for _, r := range str { + for i, r := range str { if r == '"' { quoted = !quoted sb.WriteRune(r) + } else if r == ' ' && str[i - 1] == '\\' { + sb.WriteRune(r) } else if !quoted && r == ' ' { split = append(split, sb.String()) sb.Reset() @@ -39,12 +73,22 @@ func splitQuote(str string) []string { } func fileComplete(query, ctx string, fields []string) ([]string, string) { - q := splitQuote(ctx) + q := splitForFile(ctx) + path := "" + if len(q) != 0 { + path = q[len(q) - 1] + } - return matchPath(q[len(q) - 1]) + return matchPath(path) } func binaryComplete(query, ctx string, fields []string) ([]string, string) { + q := splitForFile(ctx) + query = "" + if len(q) != 0 { + query = q[len(q) - 1] + } + var completions []string prefixes := []string{"./", "../", "/", "~/"} @@ -54,7 +98,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) { if len(fileCompletions) != 0 { for _, f := range fileCompletions { fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref))) - if err := findExecutable(fullPath, false, true); err != nil { + if err := findExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil { continue } completions = append(completions, f) @@ -66,7 +110,6 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) { // filter out executables, but in path for _, dir := range filepath.SplitList(os.Getenv("PATH")) { - // print dir to stderr for debugging // search for an executable which matches our query string if matches, err := filepath.Glob(filepath.Join(dir, query + "*")); err == nil { // get basename from matches @@ -102,6 +145,7 @@ func matchPath(query string) ([]string, string) { var entries []string var baseName string + query = escapeInvertReplaer.Replace(query) path, _ := filepath.Abs(util.ExpandHome(filepath.Dir(query))) if string(query) == "" { // filepath base below would give us "." @@ -112,7 +156,16 @@ func matchPath(query string) ([]string, string) { } files, _ := os.ReadDir(path) - for _, file := range files { + for _, entry := range files { + // should we handle errors here? + file, err := entry.Info() + if err == nil && file.Mode() & os.ModeSymlink != 0 { + path, err := filepath.EvalSymlinks(filepath.Join(path, file.Name())) + if err == nil { + file, err = os.Lstat(path) + } + } + if strings.HasPrefix(file.Name(), baseName) { entry := file.Name() if file.IsDir() { @@ -124,32 +177,20 @@ func matchPath(query string) ([]string, string) { entries = append(entries, entry) } } + if !strings.HasPrefix(oldQuery, "\"") { + baseName = escapeFilename(baseName) + } return entries, baseName } func escapeFilename(fname string) string { - args := []string{ - "\"", "\\\"", - "'", "\\'", - "`", "\\`", - " ", "\\ ", - "(", "\\(", - ")", "\\)", - "[", "\\[", - "]", "\\]", - "$", "\\$", - "&", "\\&", - "*", "\\*", - ">", "\\>", - "<", "\\<", - "|", "\\|", - } - - r := strings.NewReplacer(args...) - return r.Replace(fname) + return escapeReplaer.Replace(fname) } +// #interface completions +// tab completions +// The completions interface deals with tab completions. func completionLoader(rtm *rt.Runtime) *rt.Table { exports := map[string]util.LuaExport{ "files": {luaFileComplete, 3, false}, @@ -164,11 +205,26 @@ func completionLoader(rtm *rt.Runtime) *rt.Table { return mod } -// left as a shim, might doc in the same way as hilbish functions +// #interface completions +// handler(line, pos) +// The handler function is the callback for tab completion in Hilbish. +// You can check the completions doc for more info. +// --- @param line string +// --- @param pos string func completionHandler(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #interface completions +// call(name, query, ctx, fields) -> completionGroups (table), prefix (string) +// Calls a completer function. This is mainly used to call +// a command completer, which will have a `name` in the form +// of `command.name`, example: `command.git`. +// You can check `doc completions` for info on the `completionGroups` return value. +// --- @param name string +// --- @param query string +// --- @param ctx string +// --- @param fields table func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.CheckNArgs(4); err != nil { return nil, err @@ -208,6 +264,12 @@ func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, completerReturn), nil } +// #interface completions +// files(query, ctx, fields) -> entries (table), prefix (string) +// Returns file completion candidates based on the provided query. +// --- @param query string +// --- @param ctx string +// --- @param fields table func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { query, ctx, fds, err := getCompleteParams(t, c) if err != nil { @@ -224,6 +286,12 @@ func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil } +// #interface completions +// bins(query, ctx, fields) -> entries (table), prefix (string) +// Returns binary/executale completion candidates based on the provided query. +// --- @param query string +// --- @param ctx string +// --- @param fields table func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { query, ctx, fds, err := getCompleteParams(t, c) if err != nil { diff --git a/docs/api/_index.md b/docs/api/_index.md new file mode 100644 index 0000000..8c9f722 --- /dev/null +++ b/docs/api/_index.md @@ -0,0 +1,9 @@ +--- +title: API +layout: doc +weight: -50 +menu: docs +--- + +Welcome to the API documentation for Hilbish. This documents Lua functions +provided by Hilbish. diff --git a/docs/api/bait.md b/docs/api/bait.md new file mode 100644 index 0000000..a70eb17 --- /dev/null +++ b/docs/api/bait.md @@ -0,0 +1,34 @@ +--- +title: Module bait +description: the event emitter +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +Bait is the event emitter for Hilbish. Why name it bait? Why not. +It throws hooks that you can catch. This is what you will use if +you want to listen in on hooks to know when certain things have +happened, like when you've changed directory, a command has failed, +etc. To find all available hooks thrown by Hilbish, see doc hooks. + +## Functions +### catch(name, cb) +Catches a hook with `name`. Runs the `cb` when it is thrown + +### catchOnce(name, cb) +Same as catch, but only runs the `cb` once and then removes the hook + +### hooks(name) -> table +Returns a table with hooks (callback functions) on the event with `name`. + +### release(name, catcher) +Removes the `catcher` for the event with `name`. +For this to work, `catcher` has to be the same function used to catch +an event, like one saved to a variable. + +### throw(name, ...args) +Throws a hook with `name` with the provided `args` + diff --git a/docs/api/commander.md b/docs/api/commander.md new file mode 100644 index 0000000..341eeda --- /dev/null +++ b/docs/api/commander.md @@ -0,0 +1,46 @@ +--- +title: Module commander +description: library for custom commands +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction + +Commander is a library for writing custom commands in Lua. +In order to make it easier to write commands for Hilbish, +not require separate scripts and to be able to use in a config, +the Commander library exists. This is like a very simple wrapper +that works with Hilbish for writing commands. Example: + +```lua +local commander = require 'commander' + +commander.register('hello', function(args, sinks) + sinks.out:writeln 'Hello world!' +end) +``` + +In this example, a command with the name of `hello` is created +that will print `Hello world!` to output. One question you may +have is: What is the `sinks` parameter? + +The `sinks` parameter is a table with 3 keys: `in`, `out`, +and `err`. The values of these is a Sink. + +- `in` is the standard input. You can read from this sink +to get user input. (**This is currently unimplemented.**) +- `out` is standard output. This is usually where text meant for +output should go. +- `err` is standard error. This sink is for writing errors, as the +name would suggest. + +## Functions +### deregister(name) +Deregisters any command registered with `name` + +### register(name, cb) +Register a command with `name` that runs `cb` when ran + diff --git a/docs/api/fs.md b/docs/api/fs.md new file mode 100644 index 0000000..ee6949f --- /dev/null +++ b/docs/api/fs.md @@ -0,0 +1,51 @@ +--- +title: Module fs +description: filesystem interaction and functionality library +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The fs module provides easy and simple access to filesystem functions +and other things, and acts an addition to the Lua standard library's +I/O and filesystem functions. + +## Functions +### abs(path) -> string +Gives an absolute version of `path`. + +### basename(path) -> string +Gives the basename of `path`. For the rules, +see Go's filepath.Base + +### cd(dir) +Changes directory to `dir` + +### dir(path) -> string +Returns the directory part of `path`. For the rules, see Go's +filepath.Dir + +### glob(pattern) -> matches (table) +Glob all files and directories that match the pattern. +For the rules, see Go's filepath.Glob + +### join(...) -> string +Takes paths and joins them together with the OS's +directory separator (forward or backward slash). + +### mkdir(name, recursive) +Makes a directory called `name`. If `recursive` is true, it will create its parent directories. + +### readdir(dir) -> {} +Returns a table of files in `dir`. + +### stat(path) -> {} +Returns a table of info about the `path`. +It contains the following keys: +name (string) - Name of the path +size (number) - Size of the path +mode (string) - Permission mode in an octal format string (with leading 0) +isDir (boolean) - If the path is a directory + diff --git a/docs/api/hilbish/_index.md b/docs/api/hilbish/_index.md new file mode 100644 index 0000000..81ca993 --- /dev/null +++ b/docs/api/hilbish/_index.md @@ -0,0 +1,117 @@ +--- +title: Module hilbish +description: the core Hilbish API +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The Hilbish module includes the core API, containing +interfaces and functions which directly relate to shell functionality. + +## Interface fields +- `ver`: The version of Hilbish +- `user`: Username of the user +- `host`: Hostname of the machine +- `dataDir`: Directory for Hilbish data files, including the docs and default modules +- `interactive`: Is Hilbish in an interactive shell? +- `login`: Is Hilbish the login shell? +- `vimMode`: Current Vim input mode of Hilbish (will be nil if not in Vim input mode) +- `exitCode`: xit code of the last executed command + +## Functions +### alias(cmd, orig) +Sets an alias of `cmd` to `orig` + +### appendPath(dir) +Appends `dir` to $PATH + +### complete(scope, cb) +Registers a completion handler for `scope`. +A `scope` is currently only expected to be `command.`, +replacing with the name of the command (for example `command.git`). +`cb` must be a function that returns a table of "completion groups." +Check `doc completions` for more information. + +### cwd() -> string +Returns the current directory of the shell + +### exec(cmd) +Replaces running hilbish with `cmd` + +### goro(fn) +Puts `fn` in a goroutine + +### highlighter(line) +Line highlighter handler. This is mainly for syntax highlighting, but in +reality could set the input of the prompt to *display* anything. The +callback is passed the current line and is expected to return a line that +will be used as the input display. + +### hinter(line, pos) +The command line hint handler. It gets called on every key insert to +determine what text to use as an inline hint. It is passed the current +line and cursor position. It is expected to return a string which is used +as the text for the hint. This is by default a shim. To set hints, +override this function with your custom handler. + +### inputMode(mode) +Sets the input mode for Hilbish's line reader. Accepts either emacs or vim + +### interval(cb, time) -> Timer +Runs the `cb` function every `time` milliseconds. +This creates a timer that starts immediately. + +### multiprompt(str) +Changes the continued line prompt to `str` + +### prependPath(dir) +Prepends `dir` to $PATH + +### prompt(str, typ) +Changes the shell prompt to `str` +There are a few verbs that can be used in the prompt text. +These will be formatted and replaced with the appropriate values. +`%d` - Current working directory +`%u` - Name of current user +`%h` - Hostname of device + +### read(prompt) -> input (string) +Read input from the user, using Hilbish's line editor/input reader. +This is a separate instance from the one Hilbish actually uses. +Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen) + +### run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string) +Runs `cmd` in Hilbish's sh interpreter. +If returnOut is true, the outputs of `cmd` will be returned as the 2nd and +3rd values instead of being outputted to the terminal. + +### runnerMode(mode) +Sets the execution/runner mode for interactive Hilbish. This determines whether +Hilbish wll try to run input as Lua and/or sh or only do one of either. +Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua), +sh, and lua. It also accepts a function, to which if it is passed one +will call it to execute user input instead. + +### timeout(cb, time) -> Timer +Runs the `cb` function after `time` in milliseconds. +This creates a timer that starts immediately. + +### which(name) -> string +Checks if `name` is a valid command. +Will return the path of the binary, or a basename if it's a commander. + +## Types +## Sink +A sink is a structure that has input and/or output to/from +a desination. + +### Methods +#### write(str) +Writes data to a sink. + +#### writeln(str) +Writes data to a sink with a newline at the end. + diff --git a/docs/api/hilbish/hilbish.aliases.md b/docs/api/hilbish/hilbish.aliases.md new file mode 100644 index 0000000..bae5bfc --- /dev/null +++ b/docs/api/hilbish/hilbish.aliases.md @@ -0,0 +1,25 @@ +--- +title: Interface hilbish.aliases +description: command aliasing +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The alias interface deals with all command aliases in Hilbish. + +## Functions +### add(alias, cmd) +This is an alias (ha) for the `hilbish.alias` function. + +### delete(name) +Removes an alias. + +### list() -> table\ +Get a table of all aliases, with string keys as the alias and the value as the command. + +### resolve(alias) -> command (string) +Tries to resolve an alias to its command. + diff --git a/docs/api/hilbish/hilbish.completions.md b/docs/api/hilbish/hilbish.completions.md new file mode 100644 index 0000000..6f8740f --- /dev/null +++ b/docs/api/hilbish/hilbish.completions.md @@ -0,0 +1,29 @@ +--- +title: Interface hilbish.completions +description: tab completions +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The completions interface deals with tab completions. + +## Functions +### call(name, query, ctx, fields) -> completionGroups (table), prefix (string) +Calls a completer function. This is mainly used to call +a command completer, which will have a `name` in the form +of `command.name`, example: `command.git`. +You can check `doc completions` for info on the `completionGroups` return value. + +### handler(line, pos) +The handler function is the callback for tab completion in Hilbish. +You can check the completions doc for more info. + +### bins(query, ctx, fields) -> entries (table), prefix (string) +Returns binary/executale completion candidates based on the provided query. + +### files(query, ctx, fields) -> entries (table), prefix (string) +Returns file completion candidates based on the provided query. + diff --git a/docs/api/hilbish/hilbish.editor.md b/docs/api/hilbish/hilbish.editor.md new file mode 100644 index 0000000..30a3842 --- /dev/null +++ b/docs/api/hilbish/hilbish.editor.md @@ -0,0 +1,26 @@ +--- +title: Interface hilbish.editor +description: interactions for Hilbish's line reader +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The hilbish.editor interface provides functions to +directly interact with the line editor in use. + +## Functions +### getLine() -> string +Returns the current input line. + +### getVimRegister(register) -> string +Returns the text that is at the register. + +### insert(text) +Inserts text into the line. + +### setVimRegister(register, text) +Sets the vim register at `register` to hold the passed text. + diff --git a/docs/api/hilbish/hilbish.history.md b/docs/api/hilbish/hilbish.history.md new file mode 100644 index 0000000..9fa9b01 --- /dev/null +++ b/docs/api/hilbish/hilbish.history.md @@ -0,0 +1,30 @@ +--- +title: Interface hilbish.history +description: command history +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The history interface deals with command history. +This includes the ability to override functions to change the main +method of saving history. + +## Functions +### add(cmd) +Adds a command to the history. + +### all() -> table +Retrieves all history. + +### clear() +Deletes all commands from the history. + +### get(idx) +Retrieves a command from the history based on the `idx`. + +### size() -> number +Returns the amount of commands in the history. + diff --git a/docs/api/hilbish/hilbish.jobs.md b/docs/api/hilbish/hilbish.jobs.md new file mode 100644 index 0000000..e41be2c --- /dev/null +++ b/docs/api/hilbish/hilbish.jobs.md @@ -0,0 +1,58 @@ +--- +title: Interface hilbish.jobs +description: background job management +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction + +Manage interactive jobs in Hilbish via Lua. + +Jobs are the name of background tasks/commands. A job can be started via +interactive usage or with the functions defined below for use in external runners. + +## Functions +### add(cmdstr, args, execPath) +Adds a new job to the job table. Note that this does not immediately run it. + +### all() -> table\<Job> +Returns a table of all job objects. + +### disown(id) +Disowns a job. This deletes it from the job table. + +### get(id) -> Job +Get a job object via its ID. + +### last() -> Job +Returns the last added job from the table. + +## Types +## Job +The Job type describes a Hilbish job. +### Properties +- `cmd`: The user entered command string for the job. +- `running`: Whether the job is running or not. +- `id`: The ID of the job in the job table +- `pid`: The Process ID +- `exitCode`: The last exit code of the job. +- `stdout`: The standard output of the job. This just means the normal logs of the process. +- `stderr`: The standard error stream of the process. This (usually) includes error messages of the job. + +### Methods +#### background() +Puts a job in the background. This acts the same as initially running a job. + +#### foreground() +Puts a job in the foreground. This will cause it to run like it was +executed normally and wait for it to complete. + +#### start() +Starts running the job. + +#### stop() +Stops the job from running. + diff --git a/docs/api/hilbish/hilbish.os.md b/docs/api/hilbish/hilbish.os.md new file mode 100644 index 0000000..aa2198e --- /dev/null +++ b/docs/api/hilbish/hilbish.os.md @@ -0,0 +1,19 @@ +--- +title: Interface hilbish.os +description: OS Info +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The `os` interface provides simple text information properties about +the current OS on the systen. This mainly includes the name and +version. + +## Interface fields +- `family`: Family name of the current OS +- `name`: Pretty name of the current OS +- `version`: Version of the current OS + diff --git a/docs/api/hilbish/hilbish.runner.md b/docs/api/hilbish/hilbish.runner.md new file mode 100644 index 0000000..68ffdc6 --- /dev/null +++ b/docs/api/hilbish/hilbish.runner.md @@ -0,0 +1,31 @@ +--- +title: Interface hilbish.runner +description: interactive command runner customization +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The runner interface contains functions that allow the user to change +how Hilbish interprets interactive input. +Users can add and change the default runner for interactive input to any +language or script of their choosing. A good example is using it to +write command in Fennel. + +## Functions +### setMode(cb) +This is the same as the `hilbish.runnerMode` function. It takes a callback, +which will be used to execute all interactive input. +In normal cases, neither callbacks should be overrided by the user, +as the higher level functions listed below this will handle it. + +### lua(cmd) +Evaluates `cmd` as Lua input. This is the same as using `dofile` +or `load`, but is appropriated for the runner interface. + +### sh(cmd) +Runs a command in Hilbish's shell script interpreter. +This is the equivalent of using `source`. + diff --git a/docs/api/hilbish/hilbish.timers.md b/docs/api/hilbish/hilbish.timers.md new file mode 100644 index 0000000..e899d1d --- /dev/null +++ b/docs/api/hilbish/hilbish.timers.md @@ -0,0 +1,59 @@ +--- +title: Interface hilbish.timers +description: timeout and interval API +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction + +If you ever want to run a piece of code on a timed interval, or want to wait +a few seconds, you don't have to rely on timing tricks, as Hilbish has a +timer API to set intervals and timeouts. + +These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc +accessible with `doc hilbish`). But if you want slightly more control over +them, there is the `hilbish.timers` interface. It allows you to get +a timer via ID and control them. + +All functions documented with the `Timer` type refer to a Timer object. + +An example of usage: +``` +local t = hilbish.timers.create(hilbish.timers.TIMEOUT, 5000, function() + print 'hello!' +end) + +t:start() +print(t.running) // true +``` + +## Interface fields +- `INTERVAL`: Constant for an interval timer type +- `TIMEOUT`: Constant for a timeout timer type + +## Functions +### create(type, time, callback) -> Timer +Creates a timer that runs based on the specified `time` in milliseconds. +The `type` can either be `hilbish.timers.INTERVAL` or `hilbish.timers.TIMEOUT` + +### get(id) -> Timer +Retrieves a timer via its ID. + +## Types +## Timer +The Job type describes a Hilbish timer. +### Properties +- `type`: What type of timer it is +- `running`: If the timer is running +- `duration`: The duration in milliseconds that the timer will run + +### Methods +#### start() +Starts a timer. + +#### stop() +Stops a timer. + diff --git a/docs/api/hilbish/hilbish.userDir.md b/docs/api/hilbish/hilbish.userDir.md new file mode 100644 index 0000000..0b95057 --- /dev/null +++ b/docs/api/hilbish/hilbish.userDir.md @@ -0,0 +1,18 @@ +--- +title: Interface hilbish.userDir +description: user-related directories +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +This interface just contains properties to know about certain user directories. +It is equivalent to XDG on Linux and gets the user's preferred directories +for configs and data. + +## Interface fields +- `config`: The user's config directory +- `data`: The user's directory for program data + diff --git a/docs/api/terminal.md b/docs/api/terminal.md new file mode 100644 index 0000000..99d4b49 --- /dev/null +++ b/docs/api/terminal.md @@ -0,0 +1,26 @@ +--- +title: Module terminal +description: low level terminal library +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The terminal library is a simple and lower level library for certain terminal interactions. + +## Functions +### restoreState() +Restores the last saved state of the terminal + +### saveState() +Saves the current state of the terminal + +### setRaw() +Puts the terminal in raw mode + +### size() +Gets the dimensions of the terminal. Returns a table with `width` and `height` +Note: this is not the size in relation to the dimensions of the display + diff --git a/docs/bait.txt b/docs/bait.txt deleted file mode 100644 index fdc712f..0000000 --- a/docs/bait.txt +++ /dev/null @@ -1,10 +0,0 @@ -catch(name, cb) > Catches a hook with `name`. Runs the `cb` when it is thrown - -catchOnce(name, cb) > Same as catch, but only runs the `cb` once and then removes the hook - -release(name, catcher) > Removes the `catcher` for the event with `name` -For this to work, `catcher` has to be the same function used to catch -an event, like one saved to a variable. - -throw(name, ...args) > Throws a hook with `name` with the provided `args` - diff --git a/docs/commander.txt b/docs/commander.txt deleted file mode 100644 index 8b4b329..0000000 --- a/docs/commander.txt +++ /dev/null @@ -1,4 +0,0 @@ -deregister(name) > Deregisters any command registered with `name` - -register(name, cb) > Register a command with `name` that runs `cb` when ran - diff --git a/docs/completions.md b/docs/completions.md new file mode 100644 index 0000000..c2de27a --- /dev/null +++ b/docs/completions.md @@ -0,0 +1,56 @@ +Hilbish has a pretty good completion system. It has a nice looking +menu, with 2 types of menus: grid (like file completions) or +list. + +Like most parts of Hilbish, it's made to be extensible and +customizable. The default handler for completions in general can +be overwritten to provide more advanced completions if needed. + +# Completion Handler +By default, it provides 3 things: for the first argument, +binaries (with a plain name requested to complete, those in +$PATH), files, or command completions. With the default +completion handler, it will try to run a handler for the +command or fallback to file completions. + +To overwrite it, just assign a function to +`hilbish.completion.handler` like so: +function hilbish.completion.handler(line, pos) + -- do things +end + +It is passed 2 arguments, the entire line, and the current +cursor position. The functions in the completion interface +take 3 arguments: query, ctx, and fields. + +- The `query`, which what the user is currently trying to complete +- `ctx`, being just the entire line +- `fields` being a table of arguments. It's just `ctx` split up, +delimited by spaces. + +It's expected to return 2 things: a table of completion groups, and +a prefix. A completion group is defined as a table with 2 keys: +`items` and `type`. + +- The `items` field is just a table of items to use for completions. +- The `type` is for the completion menu type, being either `grid` or +`list`. + +The prefix is what all the completions start with. It should be empty +if the user doesn't have a query. If the beginning of the completion +item does not match the prefix, it will be replaced and fixed +properly in the line. It is case sensitive. + +If you want to overwrite the functionality of the general completion +handler, or make your command completion have files as well +(and filter them), then there is the `files` function, which is +mentioned below. + +# Completion Interface +## Functions +- `files(query, ctx, fields)` -> table, prefix: get file completions, +based on the user's query. +- `bins(query, ctx, fields)` -> table, prefix: get binary/executable +completions, based on user query. +- `call(scope, query, ctx, fields)` -> table, prefix: call a completion +handler with `scope`, usually being in the form of `command.` diff --git a/docs/completions.txt b/docs/completions.txt deleted file mode 100644 index 1354dc0..0000000 --- a/docs/completions.txt +++ /dev/null @@ -1,44 +0,0 @@ -Hilbish has a pretty good completion system. It has a nice looking menu, -with 2 types of menus: grid (like file completions) or list. - -Like most parts of Hilbish, it's made to be extensible and customizable. -The default handler for completions in general can be overwritten to provide -more advanced completions if needed. - -# Completion Handler -By default, it provides 3 things: for the first argument, binaries (with a -plain name requested to complete, those in $PATH), files, or command -completions. With the default completion handler, it will try to run a -handler for the command or fallback to file completions. - -To overwrite it, just assign a function to `hilbish.completion.handler` -like so: -function hilbish.completion.handler(line, pos) - -- do things -end -It is passed 2 arguments, the entire line, and the current cursor position. -The functions in the completion interface take 3 arguments: query, ctx, -and fields. The `query`, which what the user is currently trying to complete, -`ctx`, being just the entire line, and `fields` being a table of arguments. -It's just `ctx` split up, delimited by spaces. -It's expected to return 2 things: a table of completion groups, and a prefix. -A completion group is defined as a table with 2 keys: `items` and `type`. -The `items` field is just a table of items to use for completions. -The `type` is for the completion menu type, being either `grid` or `list`. -The prefix is what all the completions start with. It should be empty -if the user doesn't have a query. If the beginning of the completion -item does not match the prefix, it will be replaced and fixed properly -in the line. It is case sensitive. - -If you want to overwrite the functionality of the general completion handler, -or make your command completion have files as well (and filter them), -then there is the `files` function, which is mentioned below. - -# Completion Interface -## Functions -- `files(query, ctx, fields)` -> table, prefix: get file completions, based -on the user's query. -- `bins(query, ctx, fields)` -> table, prefix: get binary/executable -completions, based on user query. -- `call(scope, query, ctx, fields)` -> table, prefix: call a completion handler -with `scope`, usually being in the form of `command.` diff --git a/docs/fs.txt b/docs/fs.txt deleted file mode 100644 index 8372afd..0000000 --- a/docs/fs.txt +++ /dev/null @@ -1,22 +0,0 @@ -abs(path) > Gives an absolute version of `path`. - -basename(path) > Gives the basename of `path`. For the rules, -see Go's filepath.Base - -cd(dir) > Changes directory to `dir` - -dir(path) > Returns the directory part of `path`. For the rules, see Go's -filepath.Dir - -glob(pattern) > Glob all files and directories that match the pattern. -For the rules, see Go's filepath.Glob - -join(paths...) > Takes paths and joins them together with the OS's -directory separator (forward or backward slash). - -mkdir(name, recursive) > Makes a directory called `name`. If `recursive` is true, it will create its parent directories. - -readdir(dir) > Returns a table of files in `dir` - -stat(path) > Returns info about `path` - diff --git a/docs/hilbish.txt b/docs/hilbish.txt deleted file mode 100644 index d9763a0..0000000 --- a/docs/hilbish.txt +++ /dev/null @@ -1,62 +0,0 @@ -alias(cmd, orig) > Sets an alias of `cmd` to `orig` - -appendPath(dir) > Appends `dir` to $PATH - -complete(scope, cb) > Registers a completion handler for `scope`. -A `scope` is currently only expected to be `command.`, -replacing with the name of the command (for example `command.git`). -`cb` must be a function that returns a table of "completion groups." -Check `doc completions` for more information. - -cwd() > Returns the current directory of the shell - -exec(cmd) > Replaces running hilbish with `cmd` - -goro(fn) > Puts `fn` in a goroutine - -highlighter(line) > Line highlighter handler. This is mainly for syntax highlighting, but in -reality could set the input of the prompt to *display* anything. The -callback is passed the current line and is expected to return a line that -will be used as the input display. - -hinter(line, pos) > The command line hint handler. It gets called on every key insert to -determine what text to use as an inline hint. It is passed the current -line and cursor position. It is expected to return a string which is used -as the text for the hint. This is by default a shim. To set hints, -override this function with your custom handler. - -inputMode(mode) > Sets the input mode for Hilbish's line reader. Accepts either emacs or vim - -interval(cb, time) > Runs the `cb` function every `time` milliseconds. -Returns a `timer` object (see `doc timers`). - -multiprompt(str) > Changes the continued line prompt to `str` - -prependPath(dir) > Prepends `dir` to $PATH - -prompt(str, typ?) > Changes the shell prompt to `str` -There are a few verbs that can be used in the prompt text. -These will be formatted and replaced with the appropriate values. -`%d` - Current working directory -`%u` - Name of current user -`%h` - Hostname of device - -read(prompt) -> input? > Read input from the user, using Hilbish's line editor/input reader. -This is a separate instance from the one Hilbish actually uses. -Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen) - -run(cmd, returnOut) -> exitCode, stdout, stderr > Runs `cmd` in Hilbish's sh interpreter. -If returnOut is true, the outputs of `cmd` will be returned as the 2nd and -3rd values instead of being outputted to the terminal. - -runnerMode(mode) > Sets the execution/runner mode for interactive Hilbish. This determines whether -Hilbish wll try to run input as Lua and/or sh or only do one of either. -Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua), -sh, and lua. It also accepts a function, to which if it is passed one -will call it to execute user input instead. - -timeout(cb, time) > Runs the `cb` function after `time` in milliseconds -Returns a `timer` object (see `doc timers`). - -which(name) > Checks if `name` is a valid command - diff --git a/docs/hooks/index.txt b/docs/hooks/_index.md similarity index 62% rename from docs/hooks/index.txt rename to docs/hooks/_index.md index f771543..6616b05 100644 --- a/docs/hooks/index.txt +++ b/docs/hooks/_index.md @@ -6,3 +6,8 @@ Here is the format for a doc for a hook: `` just means the arguments of the hook. If a hook doc has the format of `arg...`, it means the hook can take/recieve any number of `arg`. + ++ error -> eventName, handler, err > Emitted when there is an error in +an event handler. The `eventName` is the name of the event the handler +is for, the `handler` is the callback function, and `err` is the error +message. diff --git a/docs/hooks/command.txt b/docs/hooks/command.md similarity index 67% rename from docs/hooks/command.txt rename to docs/hooks/command.md index f97f7e3..cd1ae3c 100644 --- a/docs/hooks/command.txt +++ b/docs/hooks/command.md @@ -3,5 +3,5 @@ + `command.not-found` -> cmdStr > Thrown when a command is not found. -+ `command.no-perm` -> cmdStr > Thrown when Hilbish attempts to execute a file but -has no permission. ++ `command.not-executable` -> cmdStr > Thrown when Hilbish attempts to run a file +that is not executable. diff --git a/docs/hooks/hilbish.txt b/docs/hooks/hilbish.md similarity index 85% rename from docs/hooks/hilbish.txt rename to docs/hooks/hilbish.md index d6d5542..3d6d2ea 100644 --- a/docs/hooks/hilbish.txt +++ b/docs/hooks/hilbish.md @@ -5,3 +5,5 @@ + `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something like yanking or pasting text. See `doc vim-mode actions` for more info. + ++ `hilbish.cancel` > Sent when the user cancels their input with Ctrl-C. diff --git a/docs/hooks/job.txt b/docs/hooks/job.md similarity index 100% rename from docs/hooks/job.txt rename to docs/hooks/job.md diff --git a/docs/hooks/signal.txt b/docs/hooks/signal.md similarity index 100% rename from docs/hooks/signal.txt rename to docs/hooks/signal.md diff --git a/docs/jobs.txt b/docs/jobs.md similarity index 100% rename from docs/jobs.txt rename to docs/jobs.md diff --git a/docs/lunacolors.txt b/docs/lunacolors.md similarity index 100% rename from docs/lunacolors.txt rename to docs/lunacolors.md diff --git a/docs/nature/index.txt b/docs/nature/_index.md similarity index 100% rename from docs/nature/index.txt rename to docs/nature/_index.md diff --git a/docs/runner-mode.txt b/docs/runner-mode.md similarity index 75% rename from docs/runner-mode.txt rename to docs/runner-mode.md index 5765f18..0b5ce24 100644 --- a/docs/runner-mode.txt +++ b/docs/runner-mode.md @@ -38,8 +38,22 @@ The exit code has to be a number, it will be 0 otherwise and the error can be 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 ++ sh(input) -> table > Runs `input` in Hilbish's sh interpreter ++ lua(input) -> table > Evals `input` as Lua code + +The table value that runners return can have at least 4 values: ++ input (string): The full input text. ++ exitCode (number): Exit code (usually from a command) ++ continue (boolean): Whether to prompt the user for more input +(in the case of incomplete syntax) ++ err (string): A string that represents an error from the runner. +This should only be set when, for example, there is a syntax error. +It can be set to a few special values for Hilbish to throw the right +hooks and have a better looking message. + ++ `: not-found` will throw a `command.not-found` hook +based on what `` is. ++ `: not-executable` will throw a `command.not-executable` hook. The others here are defined in Lua and have EmmyLua documentation. These functions should be preferred over the previous ones. diff --git a/docs/terminal.txt b/docs/terminal.txt deleted file mode 100644 index 7683bbb..0000000 --- a/docs/terminal.txt +++ /dev/null @@ -1,9 +0,0 @@ -restoreState() > Restores the last saved state of the terminal - -saveState() > Saves the current state of the terminal - -setRaw() > Puts the terminal in raw mode - -size() > Gets the dimensions of the terminal. Returns a table with `width` and `height` -Note: this is not the size in relation to the dimensions of the display - diff --git a/docs/timers.md b/docs/timers.md new file mode 100644 index 0000000..1b9c602 --- /dev/null +++ b/docs/timers.md @@ -0,0 +1 @@ +This has been moved to the `hilbish.timers` API doc (accessible by `doc api hilbish.timers`) diff --git a/docs/timers.txt b/docs/timers.txt deleted file mode 100644 index 0f89718..0000000 --- a/docs/timers.txt +++ /dev/null @@ -1,38 +0,0 @@ -If you ever want to run a piece of code on a timed interval, or want to wait -a few seconds, you don't have to rely on timing tricks, as Hilbish has a -timer API to set intervals and timeouts. - -These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc -accessible with `doc hilbish`). But if you want slightly more control over -them, there is the `hilbish.timers` interface. It allows you to get -a timer via ID. - -# Timer Interface -## Functions -- `get(id)` -> timer: get a timer via its id -- `create(type, ms, callback)` -> timer: creates a timer, adding it to the timer pool. -`type` is the type of timer it will be. 0 is an interval, 1 is a timeout. -`ms` is the time it will run for in seconds. callback is the function called -when the timer is triggered. - -# 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 -- `running`: whether the timer is running or not -- `type`: the type of timer (0 is interval, 1 is timeout) - -## Functions -- `stop()`: stops the timer. returns an error if it's already stopped -- `start()`: starts the timer. returns an error if it's already started diff --git a/docs/vim-mode/index.txt b/docs/vim-mode/_index.md similarity index 100% rename from docs/vim-mode/index.txt rename to docs/vim-mode/_index.md diff --git a/docs/vim-mode/actions.txt b/docs/vim-mode/actions.md similarity index 100% rename from docs/vim-mode/actions.txt rename to docs/vim-mode/actions.md diff --git a/editor.go b/editor.go index 868f458..3038f07 100644 --- a/editor.go +++ b/editor.go @@ -6,6 +6,10 @@ import ( rt "github.com/arnodel/golua/runtime" ) +// #interface editor +// interactions for Hilbish's line reader +// The hilbish.editor interface provides functions to +// directly interact with the line editor in use. func editorLoader(rtm *rt.Runtime) *rt.Table { exports := map[string]util.LuaExport{ "insert": {editorInsert, 1, false}, @@ -20,6 +24,9 @@ func editorLoader(rtm *rt.Runtime) *rt.Table { return mod } +// #interface editor +// insert(text) +// Inserts text into the line. func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -35,6 +42,11 @@ func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #interface editor +// setVimRegister(register, text) +// Sets the vim register at `register` to hold the passed text. +// --- @param register string +// --- @param text string func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -55,6 +67,10 @@ func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #interface editor +// getVimRegister(register) -> string +// Returns the text that is at the register. +// --- @param register string func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -70,6 +86,9 @@ func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil } +// #interface editor +// getLine() -> string +// Returns the current input line. func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { buf := lr.rl.GetLine() diff --git a/emmyLuaDocs/bait.lua b/emmyLuaDocs/bait.lua index a5ecebd..35a37ed 100644 --- a/emmyLuaDocs/bait.lua +++ b/emmyLuaDocs/bait.lua @@ -12,14 +12,21 @@ function bait.catch(name, cb) end --- @param cb function function bait.catchOnce(name, cb) end ---- Removes the `catcher` for the event with `name` +--- Returns a table with hooks (callback functions) on the event with `name`. +--- @param name string +--- @returns table +function bait.hooks(name) end + +--- Removes the `catcher` for the event with `name`. --- For this to work, `catcher` has to be the same function used to catch --- an event, like one saved to a variable. -function bait.release() end +--- @param name string +--- @param catcher function +function bait.release(name, catcher) end --- Throws a hook with `name` with the provided `args` --- @param name string --- @vararg any -function bait.throw(name, ...) end +function bait.throw(name, ...args) end return bait diff --git a/emmyLuaDocs/fs.lua b/emmyLuaDocs/fs.lua index 14e7be4..e974ab9 100644 --- a/emmyLuaDocs/fs.lua +++ b/emmyLuaDocs/fs.lua @@ -4,11 +4,13 @@ local fs = {} --- Gives an absolute version of `path`. --- @param path string +--- @returns string function fs.abs(path) end --- Gives the basename of `path`. For the rules, --- see Go's filepath.Base -function fs.basename() end +--- @returns string +function fs.basename(path) end --- Changes directory to `dir` --- @param dir string @@ -16,28 +18,40 @@ function fs.cd(dir) end --- Returns the directory part of `path`. For the rules, see Go's --- filepath.Dir -function fs.dir() end +--- @param path string +--- @returns string +function fs.dir(path) end --- Glob all files and directories that match the pattern. --- For the rules, see Go's filepath.Glob -function fs.glob() end +--- @param pattern string +--- @returns table +function fs.glob(pattern) end --- Takes paths and joins them together with the OS's --- directory separator (forward or backward slash). -function fs.join() end +--- @vararg string +--- @returns string +function fs.join(...) end --- Makes a directory called `name`. If `recursive` is true, it will create its parent directories. --- @param name string --- @param recursive boolean function fs.mkdir(name, recursive) end ---- Returns a table of files in `dir` +--- Returns a table of files in `dir`. --- @param dir string --- @return table function fs.readdir(dir) end ---- Returns info about `path` +--- Returns a table of info about the `path`. +--- It contains the following keys: +--- name (string) - Name of the path +--- size (number) - Size of the path +--- mode (string) - Permission mode in an octal format string (with leading 0) +--- isDir (boolean) - If the path is a directory --- @param path string +--- @returns table function fs.stat(path) end return fs diff --git a/emmyLuaDocs/hilbish.lua b/emmyLuaDocs/hilbish.lua index ca34425..8b20583 100644 --- a/emmyLuaDocs/hilbish.lua +++ b/emmyLuaDocs/hilbish.lua @@ -2,6 +2,49 @@ local hilbish = {} +--- This is an alias (ha) for the `hilbish.alias` function. +--- @param alias string +--- @param cmd string +function hilbish.aliases.add(alias, cmd) end + +--- This is the same as the `hilbish.runnerMode` function. It takes a callback, +--- which will be used to execute all interactive input. +--- In normal cases, neither callbacks should be overrided by the user, +--- as the higher level functions listed below this will handle it. +--- @param cb function +function hilbish.runner.setMode(cb) end + +--- Calls a completer function. This is mainly used to call +--- a command completer, which will have a `name` in the form +--- of `command.name`, example: `command.git`. +--- You can check `doc completions` for info on the `completionGroups` return value. +--- @param name string +--- @param query string +--- @param ctx string +--- @param fields table +function hilbish.completions.call(name, query, ctx, fields) end + +--- The handler function is the callback for tab completion in Hilbish. +--- You can check the completions doc for more info. +--- @param line string +--- @param pos string +function hilbish.completions.handler(line, pos) end + +--- Returns the current input line. +function hilbish.editor.getLine() end + +--- Returns the text that is at the register. +--- @param register string +function hilbish.editor.getVimRegister(register) end + +--- Inserts text into the line. +function hilbish.editor.insert(text) end + +--- Sets the vim register at `register` to hold the passed text. +--- @param register string +--- @param text string +function hilbish.editor.setVimRegister(register, text) end + --- Sets an alias of `cmd` to `orig` --- @param cmd string --- @param orig string @@ -21,6 +64,7 @@ function hilbish.appendPath(dir) end function hilbish.complete(scope, cb) end --- Returns the current directory of the shell +--- @returns string function hilbish.cwd() end --- Replaces running hilbish with `cmd` @@ -44,7 +88,7 @@ function hilbish.highlighter(line) end --- as the text for the hint. This is by default a shim. To set hints, --- override this function with your custom handler. --- @param line string ---- @param pos int +--- @param pos number function hilbish.hinter(line, pos) end --- Sets the input mode for Hilbish's line reader. Accepts either emacs or vim @@ -52,10 +96,10 @@ function hilbish.hinter(line, pos) end function hilbish.inputMode(mode) end --- Runs the `cb` function every `time` milliseconds. ---- Returns a `timer` object (see `doc timers`). +--- This creates a timer that starts immediately. --- @param cb function --- @param time number ---- @return table +--- @return Timer function hilbish.interval(cb, time) end --- Changes the continued line prompt to `str` @@ -73,20 +117,23 @@ function hilbish.prependPath(dir) end --- `%u` - Name of current user --- `%h` - Hostname of device --- @param str string ---- @param typ string Type of prompt, being left or right. Left by default. +--- @param typ? string Type of prompt, being left or right. Left by default. function hilbish.prompt(str, typ) end --- Read input from the user, using Hilbish's line editor/input reader. --- This is a separate instance from the one Hilbish actually uses. --- Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen) ---- @param prompt string +--- @param prompt? string +--- @returns string|nil function hilbish.read(prompt) end --- Runs `cmd` in Hilbish's sh interpreter. --- If returnOut is true, the outputs of `cmd` will be returned as the 2nd and --- 3rd values instead of being outputted to the terminal. --- @param cmd string -function hilbish.run(cmd) end +--- @param returnOut boolean +--- @returns number, string, string +function hilbish.run(cmd, returnOut) end --- Sets the execution/runner mode for interactive Hilbish. This determines whether --- Hilbish wll try to run input as Lua and/or sh or only do one of either. @@ -96,15 +143,131 @@ function hilbish.run(cmd) end --- @param mode string|function function hilbish.runnerMode(mode) end ---- Runs the `cb` function after `time` in milliseconds ---- Returns a `timer` object (see `doc timers`). +--- Runs the `cb` function after `time` in milliseconds. +--- This creates a timer that starts immediately. --- @param cb function --- @param time number ---- @return table +--- @returns Timer function hilbish.timeout(cb, time) end ---- Checks if `name` is a valid command ---- @param binName string -function hilbish.which(binName) end +--- Checks if `name` is a valid command. +--- Will return the path of the binary, or a basename if it's a commander. +--- @param name string +--- @returns string +function hilbish.which(name) end + +--- Puts a job in the background. This acts the same as initially running a job. +function hilbish.jobs:background() end + +--- Returns binary/executale completion candidates based on the provided query. +--- @param query string +--- @param ctx string +--- @param fields table +function hilbish.completions.bins(query, ctx, fields) end + +--- Returns file completion candidates based on the provided query. +--- @param query string +--- @param ctx string +--- @param fields table +function hilbish.completions.files(query, ctx, fields) end + +--- Puts a job in the foreground. This will cause it to run like it was +--- executed normally and wait for it to complete. +function hilbish.jobs:foreground() end + +--- Evaluates `cmd` as Lua input. This is the same as using `dofile` +--- or `load`, but is appropriated for the runner interface. +--- @param cmd string +function hilbish.runner.lua(cmd) end + +--- Writes data to a sink. +function hilbish:write(str) end + +--- Writes data to a sink with a newline at the end. +function hilbish:writeln(str) end + +--- Starts running the job. +function hilbish.jobs:start() end + +--- Stops the job from running. +function hilbish.jobs:stop() end + +--- Runs a command in Hilbish's shell script interpreter. +--- This is the equivalent of using `source`. +--- @param cmd string +function hilbish.runner.sh(cmd) end + +--- Starts a timer. +function hilbish.timers:start() end + +--- Stops a timer. +function hilbish.timers:stop() end + +--- Removes an alias. +--- @param name string +function hilbish.aliases.delete(name) end + +--- Get a table of all aliases, with string keys as the alias and the value as the command. +--- @returns table +function hilbish.aliases.list() end + +--- Tries to resolve an alias to its command. +--- @param alias string +--- @returns string +function hilbish.aliases.resolve(alias) end + +--- Adds a new job to the job table. Note that this does not immediately run it. +--- @param cmdstr string +--- @param args table +--- @param execPath string +function hilbish.jobs.add(cmdstr, args, execPath) end + +--- Returns a table of all job objects. +--- @returns table +function hilbish.jobs.all() end + +--- Disowns a job. This deletes it from the job table. +--- @param id number +function hilbish.jobs.disown(id) end + +--- Get a job object via its ID. +--- @param id number +--- @returns Job +function hilbish.jobs.get(id) end + +--- Returns the last added job from the table. +--- @returns Job +function hilbish.jobs.last() end + +--- Adds a command to the history. +--- @param cmd string +function hilbish.history.add(cmd) end + +--- Retrieves all history. +--- @returns table +function hilbish.history.all() end + +--- Deletes all commands from the history. +function hilbish.history.clear() end + +--- Retrieves a command from the history based on the `idx`. +--- @param idx number +function hilbish.history.get(idx) end + +--- Returns the amount of commands in the history. +--- @returns number +function hilbish.history.size() end + +--- Creates a timer that runs based on the specified `time` in milliseconds. +--- The `type` can either be `hilbish.timers.INTERVAL` or `hilbish.timers.TIMEOUT` +--- @param type number +--- @param time number +--- @param callback function +function hilbish.timers.create(type, time, callback) end + +--- Retrieves a timer via its ID. +--- @param id number +--- @returns Timer +function hilbish.timers.get(id) end return hilbish diff --git a/exec.go b/exec.go index 6e24719..726a986 100644 --- a/exec.go +++ b/exec.go @@ -96,23 +96,23 @@ func runInput(input string, priv bool) { if currentRunner.Type() == rt.StringType { switch currentRunner.AsString() { case "hybrid": - _, _, err = handleLua(cmdString) + _, _, err = handleLua(input) if err == nil { cmdFinish(0, input, priv) return } - input, exitCode, cont, err = handleSh(cmdString) + input, exitCode, cont, err = handleSh(input) case "hybridRev": _, _, _, err = handleSh(input) if err == nil { cmdFinish(0, input, priv) return } - input, exitCode, err = handleLua(cmdString) + input, exitCode, err = handleLua(input) case "lua": - input, exitCode, err = handleLua(cmdString) + input, exitCode, err = handleLua(input) case "sh": - input, exitCode, cont, err = handleSh(cmdString) + input, exitCode, cont, err = handleSh(input) } } else { // can only be a string or function so @@ -141,9 +141,9 @@ func runInput(input string, priv bool) { if err != nil { if exErr, ok := isExecError(err); ok { hooks.Emit("command." + exErr.typ, exErr.cmd) - err = exErr.sprint() + } else { + fmt.Fprintln(os.Stderr, err) } - fmt.Fprintln(os.Stderr, err) } cmdFinish(exitCode, input, priv) } @@ -195,7 +195,8 @@ func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8 return } -func handleLua(cmdString string) (string, uint8, error) { +func handleLua(input string) (string, uint8, error) { + cmdString := aliases.Resolve(input) // First try to load input, essentially compiling to bytecode chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv())) if err != nil && noexecute { @@ -322,8 +323,18 @@ func execHandle(bg bool) interp.ExecHandlerFunc { luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str)) } + hc := interp.HandlerCtx(ctx) if commands[args[0]] != nil { - luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs)) + stdin := newSinkInput(hc.Stdin) + stdout := newSinkOutput(hc.Stdout) + stderr := newSinkOutput(hc.Stderr) + + sinks := rt.NewTable() + sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.ud)) + sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.ud)) + sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.ud)) + + luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs), rt.TableValue(sinks)) if err != nil { fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error()) return interp.NewExitStatus(1) @@ -363,7 +374,6 @@ func execHandle(bg bool) interp.ExecHandlerFunc { killTimeout := 2 * time.Second // from here is basically copy-paste of the default exec handler from // sh/interp but with our job handling - hc := interp.HandlerCtx(ctx) path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0]) if err != nil { fmt.Fprintln(hc.Stderr, err) @@ -551,7 +561,7 @@ func splitInput(input string) ([]string, string) { } func cmdFinish(code uint8, cmdstr string, private bool) { - util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)), "Exit code of last exected command") + util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code))) // using AsValue (to convert to lua type) on an interface which is an int // results in it being unknown in lua .... ???? // so we allow the hook handler to take lua runtime Values diff --git a/gallery/tab.png b/gallery/tab.png new file mode 100644 index 0000000..409d796 Binary files /dev/null and b/gallery/tab.png differ diff --git a/go.mod b/go.mod index b86b410..1573917 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,8 @@ module hilbish go 1.17 require ( - github.com/Rosettea/Malvales v0.0.0-20220707042337-37a9ee7758f9 github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86 github.com/blackfireio/osinfo v1.0.3 - github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 - github.com/hashicorp/go-plugin v1.4.4 github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036 github.com/pborman/getopt v1.1.0 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a @@ -19,21 +16,10 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/arnodel/strftime v0.1.6 // indirect github.com/evilsocket/islazy v1.10.6 // indirect - github.com/fatih/color v1.7.0 // indirect - github.com/golang/protobuf v1.3.4 // indirect - github.com/hashicorp/go-hclog v0.14.1 // indirect - github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect - github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-isatty v0.0.10 // indirect - github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect - github.com/oklog/run v1.0.0 // indirect github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect golang.org/x/text v0.3.7 // indirect - google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect - google.golang.org/grpc v1.27.1 // indirect ) replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b @@ -42,4 +28,4 @@ replace github.com/maxlandon/readline => ./readline replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10 -replace github.com/arnodel/golua => github.com/Rosettea/golua v0.0.0-20220518005949-116371948fe3 +replace github.com/arnodel/golua => github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345 diff --git a/go.sum b/go.sum index bf2960f..53e83d6 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Rosettea/Malvales v0.0.0-20220707042337-37a9ee7758f9 h1:iVIheUFcp/EhUYaCo67eG4fhW5EHxQDUNYq319DPNmc= -github.com/Rosettea/Malvales v0.0.0-20220707042337-37a9ee7758f9/go.mod h1:vZdkshJ9RK/Hu1aUNa6suOC1XfaFOzxqQQ7OZFpNYbw= -github.com/Rosettea/golua v0.0.0-20220518005949-116371948fe3 h1:I/wWr40FFLFF9pbT3wLb1FAEZhKb/hUWE+nJ5uHBK2g= -github.com/Rosettea/golua v0.0.0-20220518005949-116371948fe3/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE= +github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345 h1:QNYjYDogUSiNUkffbhFSrSCtpZhofeiVYGFN2FI4wSs= +github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE= github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b h1:s5eDMhBk6H1BgipgLub/gv9qeyBaTuiHM0k3h2/9TSE= github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -14,44 +10,15 @@ github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4 github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c= github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4= -github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc= github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evilsocket/islazy v1.10.6 h1:MFq000a1ByoumoJWlytqg0qon0KlBeUfPsDjY0hK0bo= github.com/evilsocket/islazy v1.10.6/go.mod h1:OrwQGYg3DuZvXUfmH+KIZDjwTCbrjy48T24TUpGqVVw= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= -github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= -github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -60,56 +27,21 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0= github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0= github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I= github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -126,23 +58,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0= diff --git a/golibs/bait/bait.go b/golibs/bait/bait.go index 89e0c4a..3f3c34e 100644 --- a/golibs/bait/bait.go +++ b/golibs/bait/bait.go @@ -1,6 +1,14 @@ +// the event emitter +// Bait is the event emitter for Hilbish. Why name it bait? Why not. +// It throws hooks that you can catch. This is what you will use if +// you want to listen in on hooks to know when certain things have +// happened, like when you've changed directory, a command has failed, +// etc. To find all available hooks thrown by Hilbish, see doc hooks. package bait import ( + "errors" + "hilbish/util" rt "github.com/arnodel/golua/runtime" @@ -72,8 +80,12 @@ func (b *Bait) Emit(event string, args ...interface{}) { } _, err := rt.Call1(b.rtm.MainThread(), funcVal, luaArgs...) if err != nil { - // panicking here won't actually cause hilbish to panic and instead will - // print the error and remove the hook. reference the recoverer function in lua.go + if event != "error" { + b.Emit("error", event, handle.luaCaller, err.Error()) + return + } + // if there is an error in an error event handler, panic instead + // (calls the go recoverer function) panic(err) } } else { @@ -187,19 +199,11 @@ func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { "catchOnce": util.LuaExport{b.bcatchOnce, 2, false}, "throw": util.LuaExport{b.bthrow, 1, true}, "release": util.LuaExport{b.brelease, 2, false}, + "hooks": util.LuaExport{b.bhooks, 1, false}, } mod := rt.NewTable() util.SetExports(rtm, mod, exports) - util.Document(mod, -`Bait is the event emitter for Hilbish. Why name it bait? -Because it throws hooks that you can catch (emits events -that you can listen to) and because why not, fun naming -is fun. This is what you will use if you want to listen -in on hooks to know when certain things have happened, -like when you've changed directory, a command has -failed, etc. To find all available hooks, see doc hooks.`) - return rt.TableValue(mod), nil } @@ -276,9 +280,11 @@ func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } // release(name, catcher) -// Removes the `catcher` for the event with `name` +// Removes the `catcher` for the event with `name`. // For this to work, `catcher` has to be the same function used to catch // an event, like one saved to a variable. +// --- @param name string +// --- @param catcher function func (b *Bait) brelease(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { name, catcher, err := util.HandleStrCallback(t, c) if err != nil { @@ -289,3 +295,35 @@ func (b *Bait) brelease(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } + +// hooks(name) -> table +// Returns a table with hooks (callback functions) on the event with `name`. +// --- @param name string +// --- @returns table +func (b *Bait) bhooks(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + evName, err := c.StringArg(0) + if err != nil { + return nil, err + } + noHooks := errors.New("no hooks for event " + evName) + + handlers := b.handlers[evName] + if handlers == nil { + return nil, noHooks + } + + luaHandlers := rt.NewTable() + for _, handler := range handlers { + if handler.typ != luaListener { continue } + luaHandlers.Set(rt.IntValue(luaHandlers.Len() + 1), rt.FunctionValue(handler.luaCaller)) + } + + if luaHandlers.Len() == 0 { + return nil, noHooks + } + + return c.PushingNext1(t.Runtime, rt.TableValue(luaHandlers)), nil +} diff --git a/golibs/commander/commander.go b/golibs/commander/commander.go index 24f1c03..c639cf9 100644 --- a/golibs/commander/commander.go +++ b/golibs/commander/commander.go @@ -1,3 +1,33 @@ +// library for custom commands +/* +Commander is a library for writing custom commands in Lua. +In order to make it easier to write commands for Hilbish, +not require separate scripts and to be able to use in a config, +the Commander library exists. This is like a very simple wrapper +that works with Hilbish for writing commands. Example: + +```lua +local commander = require 'commander' + +commander.register('hello', function(args, sinks) + sinks.out:writeln 'Hello world!' +end) +``` + +In this example, a command with the name of `hello` is created +that will print `Hello world!` to output. One question you may +have is: What is the `sinks` parameter? + +The `sinks` parameter is a table with 3 keys: `in`, `out`, +and `err`. The values of these is a @Sink. + +- `in` is the standard input. You can read from this sink +to get user input. (**This is currently unimplemented.**) +- `out` is standard output. This is usually where text meant for +output should go. +- `err` is standard error. This sink is for writing errors, as the +name would suggest. +*/ package commander import ( @@ -32,7 +62,6 @@ func (c *Commander) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { } mod := rt.NewTable() util.SetExports(rtm, mod, exports) - util.Document(mod, "Commander is Hilbish's custom command library, a way to write commands in Lua.") return rt.TableValue(mod), nil } diff --git a/golibs/fs/fs.go b/golibs/fs/fs.go index 5b12e73..1c1a5ca 100644 --- a/golibs/fs/fs.go +++ b/golibs/fs/fs.go @@ -1,3 +1,7 @@ +// filesystem interaction and functionality library +// The fs module provides easy and simple access to filesystem functions +// and other things, and acts an addition to the Lua standard library's +// I/O and filesystem functions. package fs import ( @@ -35,10 +39,6 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { mod.Set(rt.StringValue("pathSep"), rt.StringValue(string(os.PathSeparator))) mod.Set(rt.StringValue("pathListSep"), rt.StringValue(string(os.PathListSeparator))) - util.Document(mod, `The fs module provides easy and simple access to -filesystem functions and other things, and acts an -addition to the Lua standard library's I/O and filesystem functions.`) - return rt.TableValue(mod), nil } @@ -93,9 +93,15 @@ func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), err } -// stat(path) -// Returns info about `path` +// stat(path) -> {} +// Returns a table of info about the `path`. +// It contains the following keys: +// name (string) - Name of the path +// size (number) - Size of the path +// mode (string) - Permission mode in an octal format string (with leading 0) +// isDir (boolean) - If the path is a directory // --- @param path string +// --- @returns table func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -119,8 +125,8 @@ func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), nil } -// readdir(dir) -// Returns a table of files in `dir` +// readdir(dir) -> {} +// Returns a table of files in `dir`. // --- @param dir string // --- @return table func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { @@ -145,9 +151,10 @@ func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.TableValue(names)), nil } -// abs(path) +// abs(path) -> string // Gives an absolute version of `path`. // --- @param path string +// --- @returns string func fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { path, err := c.StringArg(0) if err != nil { @@ -163,9 +170,10 @@ func fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.StringValue(abspath)), nil } -// basename(path) +// basename(path) -> string // Gives the basename of `path`. For the rules, // see Go's filepath.Base +// --- @returns string func fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -178,9 +186,11 @@ func fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.StringValue(filepath.Base(path))), nil } -// dir(path) +// dir(path) -> string // Returns the directory part of `path`. For the rules, see Go's // filepath.Dir +// --- @param path string +// --- @returns string func fdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -193,9 +203,11 @@ func fdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.StringValue(filepath.Dir(path))), nil } -// glob(pattern) +// glob(pattern) -> matches (table) // Glob all files and directories that match the pattern. // For the rules, see Go's filepath.Glob +// --- @param pattern string +// --- @returns table func fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -219,9 +231,11 @@ func fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.TableValue(luaMatches)), nil } -// join(paths...) +// join(...) -> string // Takes paths and joins them together with the OS's // directory separator (forward or backward slash). +// --- @vararg string +// --- @returns string func fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { strs := make([]string, len(c.Etc())) for i, v := range c.Etc() { diff --git a/golibs/terminal/terminal.go b/golibs/terminal/terminal.go index df1755c..2040fac 100644 --- a/golibs/terminal/terminal.go +++ b/golibs/terminal/terminal.go @@ -1,3 +1,5 @@ +// low level terminal library +// The terminal library is a simple and lower level library for certain terminal interactions. package terminal import ( @@ -26,7 +28,6 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { mod := rt.NewTable() util.SetExports(rtm, mod, exports) - util.Document(mod, "The terminal library is a simple and lower level library for certain terminal interactions.") return rt.TableValue(mod), nil } diff --git a/hilbish-git.spec b/hilbish-git.spec new file mode 100644 index 0000000..77d2e13 --- /dev/null +++ b/hilbish-git.spec @@ -0,0 +1,59 @@ +%global _missing_build_ids_terminate_build 0 +%global debug_package %{nil} + +Name: hilbish-git +Version: {{{ git_tag_version }}}.{{{ git_short_hash }}} +Release: 1%{?dist} +Summary: The flower shell. A comfy and nice little shell for Lua fans! +License: MIT + +Source: {{{ git_dir_pack }}} +BuildRequires: git golang go-task +Requires: inspect succulent lunacolors + +Url: https://github.com/Rosettea/Hilbish +VCS: {{{ git_dir_vcs }}} + +%description +Hilbish is a extensible shell (framework). It was made to be very customizable +via the Lua programming language. It aims to be easy to use for the casual +people but powerful for those who want to tinker more with their shell, +the thing used to interface with most of the system. + +The motivation for choosing Lua was that its simpler and better to use +than old shell script. It's fine for basic interactive shell uses, +but that's the only place Hilbish has shell script; everything else is Lua +and aims to be infinitely configurable. If something isn't, open an issue! + +%prep +{{{ git_dir_setup_macro }}} +sed -i '\|/etc/shells|d' Taskfile.yaml + +%build +go-task + +%install +go-task install PREFIX=%{buildroot}/usr BINDIR=%{buildroot}/%{_bindir} + +%post +if [ "$1" = 1 ]; then + if [ ! -f %{_sysconfdir}/shells ] ; then + echo "%{_bindir}/hilbish" > %{_sysconfdir}/shells + echo "/bin/hilbish" >> %{_sysconfdir}/shells + else + grep -q "^%{_bindir}/hilbish$" %{_sysconfdir}/shells || echo "%{_bindir}/hilbish" >> %{_sysconfdir}/shells + grep -q "^/bin/hilbish$" %{_sysconfdir}/shells || echo "/bin/hilbish" >> %{_sysconfdir}/shells + fi +fi + +%postun +if [ "$1" = 0 ] && [ -f %{_sysconfdir}/shells ] ; then + sed -i '\!^%{_bindir}/hilbish$!d' %{_sysconfdir}/shells + sed -i '\!^/bin/hilbish$!d' %{_sysconfdir}/shells +fi + +%files +%doc README.md +%license LICENSE +%{_bindir}/hilbish +%{_datadir}/hilbish diff --git a/history.go b/history.go index a8eb089..51ccf27 100644 --- a/history.go +++ b/history.go @@ -73,13 +73,13 @@ func newFileHistory(path string) *fileHistory { } } - itms := []string{""} lines := strings.Split(string(data), "\n") + itms := make([]string, len(lines) - 1) for i, l := range lines { if i == len(lines) - 1 { continue } - itms = append(itms, l) + itms[i] = l } f, err := os.OpenFile(path, os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0755) if err != nil { diff --git a/job.go b/job.go index 709cc1f..1beba9c 100644 --- a/job.go +++ b/job.go @@ -18,6 +18,16 @@ import ( var jobs *jobHandler var jobMetaKey = rt.StringValue("hshjob") +// #type +// #interface jobs +// #property cmd The user entered command string for the job. +// #property running Whether the job is running or not. +// #property id The ID of the job in the job table +// #property pid The Process ID +// #property exitCode The last exit code of the job. +// #property stdout The standard output of the job. This just means the normal logs of the process. +// #property stderr The standard error stream of the process. This (usually) includes error messages of the job. +// The Job type describes a Hilbish job. type job struct { cmd string running bool @@ -110,6 +120,10 @@ func (j *job) getProc() *os.Process { return nil } +// #interface jobs +// #member +// start() +// Starts running the job. func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -130,6 +144,10 @@ func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #interface jobs +// #member +// stop() +// Stops the job from running. func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -148,6 +166,11 @@ func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #interface jobs +// #member +// foreground() +// Puts a job in the foreground. This will cause it to run like it was +// executed normally and wait for it to complete. func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -180,6 +203,10 @@ func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #interface jobs +// #member +// background() +// Puts a job in the background. This acts the same as initially running a job. func luaBackgroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -276,6 +303,13 @@ func (j *jobHandler) stopAll() { } } +// #interface jobs +// background job management +/* +Manage interactive jobs in Hilbish via Lua. + +Jobs are the name of background tasks/commands. A job can be started via +interactive usage or with the functions defined below for use in external runners. */ func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table { jobMethods := rt.NewTable() jFuncs := map[string]util.LuaExport{ @@ -353,6 +387,11 @@ func jobUserData(j *job) *rt.UserData { return rt.NewUserData(j, jobMeta.AsTable()) } +// #interface jobs +// get(id) -> @Job +// Get a job object via its ID. +// --- @param id number +// --- @returns Job func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { j.mu.RLock() defer j.mu.RUnlock() @@ -373,6 +412,12 @@ func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.UserDataValue(job.ud)), nil } +// #interface jobs +// add(cmdstr, args, execPath) +// Adds a new job to the job table. Note that this does not immediately run it. +// --- @param cmdstr string +// --- @param args table +// --- @param execPath string func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.CheckNArgs(3); err != nil { return nil, err @@ -402,6 +447,10 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.UserDataValue(jb.ud)), nil } +// #interface jobs +// all() -> table<@Job> +// Returns a table of all job objects. +// --- @returns table func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { j.mu.RLock() defer j.mu.RUnlock() @@ -414,6 +463,10 @@ func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil } +// #interface jobs +// disown(id) +// Disowns a job. This deletes it from the job table. +// --- @param id number func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -431,6 +484,10 @@ func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #interface jobs +// last() -> @Job +// Returns the last added job from the table. +// --- @returns Job func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { j.mu.RLock() defer j.mu.RUnlock() diff --git a/libs/lunacolors b/libs/lunacolors index 8467b87..34a57c9 160000 --- a/libs/lunacolors +++ b/libs/lunacolors @@ -1 +1 @@ -Subproject commit 8467b87dd8d49c68b4100b2d129d5f071544b8cf +Subproject commit 34a57c964590f89aa065188a588c7b38aff99c28 diff --git a/lua.go b/lua.go index 419970c..0a7c115 100644 --- a/lua.go +++ b/lua.go @@ -23,6 +23,7 @@ func luaInit() { MessageHandler: debuglib.Traceback, }) lib.LoadAll(l) + setupSinkType(l) lib.LoadLibs(l, hilbishLoader) // yes this is stupid, i know @@ -49,7 +50,7 @@ func luaInit() { hooks = bait.New(l) hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) { - fmt.Println("Error in", event, "event:", err) + fmt.Println("Error in `error` hook handler:", err) hooks.Off(event, handler) }) diff --git a/main.go b/main.go index ee0f584..4fa321c 100644 --- a/main.go +++ b/main.go @@ -115,9 +115,11 @@ func main() { os.Setenv("SHELL", os.Args[0]) } - go handleSignals() lr = newLineReader("", false) luaInit() + + go handleSignals() + // If user's config doesn't exixt, if _, err := os.Stat(defaultConfPath); os.IsNotExist(err) && *configflag == defaultConfPath { // Read default from current directory @@ -181,11 +183,14 @@ input: break } if err != nil { - if err != readline.CtrlC { + if err == readline.CtrlC { + fmt.Println("^C") + hooks.Emit("hilbish.cancel") + } else { // If we get a completely random error, print fmt.Fprintln(os.Stderr, err) } - fmt.Println("^C") + // TODO: Halt if any other error occurs continue } var priv bool diff --git a/nature/commands/bg.lua b/nature/commands/bg.lua index f0aa462..fbb3543 100644 --- a/nature/commands/bg.lua +++ b/nature/commands/bg.lua @@ -1,15 +1,15 @@ local commander = require 'commander' -commander.register('bg', function() +commander.register('bg', function(_, sinks) local job = hilbish.jobs.last() if not job then - print 'bg: no last job' + sinks.out:writeln 'bg: no last job' return 1 end - local err = job.background() + local err = job:background() if err then - print('bg: ' .. err) + sinks.out:writeln('bg: ' .. err) return 2 end end) diff --git a/nature/commands/cat.lua b/nature/commands/cat.lua new file mode 100644 index 0000000..06df507 --- /dev/null +++ b/nature/commands/cat.lua @@ -0,0 +1,25 @@ +local commander = require 'commander' +local fs = require 'fs' + +commander.register('cat', function(args, sinks) + local exit = 0 + + if #args == 0 then + sinks.out:writeln [[ +usage: cat [file]...]] + end + + for _, fName in ipairs(args) do + local f = io.open(fName) + if f == nil then + exit = 1 + sinks.out:writeln(string.format('cat: %s: no such file or directory', fName)) + goto continue + end + + sinks.out:writeln(f:read '*a') + ::continue:: + end + io.flush() + return exit +end) diff --git a/nature/commands/cd.lua b/nature/commands/cd.lua index b4d1041..7cfe4a2 100644 --- a/nature/commands/cd.lua +++ b/nature/commands/cd.lua @@ -4,32 +4,25 @@ local fs = require 'fs' local dirs = require 'nature.dirs' dirs.old = hilbish.cwd() -commander.register('cd', function (args) +commander.register('cd', function (args, sinks) if #args > 1 then - print("cd: too many arguments") + sinks.out:writeln("cd: too many arguments") return 1 - elseif #args > 0 then - local path = args[1]:gsub('$%$','\0'):gsub('${([%w_]+)}', os.getenv) - :gsub('$([%w_]+)', os.getenv):gsub('%z','$'):gsub('^%s*(.-)%s*$', '%1') - - if path == '-' then - path = dirs.old - print(path) - end - dirs.setOld(hilbish.cwd()) - dirs.push(path) - - local ok, err = pcall(function() fs.cd(path) end) - if not ok then - print(err) - return 1 - end - bait.throw('cd', path) - - return end - fs.cd(hilbish.home) - bait.throw('cd', hilbish.home) - dirs.push(hilbish.home) + local path = args[1] and args[1] or hilbish.home + if path == '-' then + path = dirs.old + sinks.out:writeln(path) + end + + dirs.setOld(hilbish.cwd()) + dirs.push(path) + + local ok, err = pcall(function() fs.cd(path) end) + if not ok then + sinks.out:writeln(err) + return 1 + end + bait.throw('cd', path) end) diff --git a/nature/commands/cdr.lua b/nature/commands/cdr.lua index 0438e6f..288ae22 100644 --- a/nature/commands/cdr.lua +++ b/nature/commands/cdr.lua @@ -3,9 +3,9 @@ local fs = require 'fs' local lunacolors = require 'lunacolors' local dirs = require 'nature.dirs' -commander.register('cdr', function(args) +commander.register('cdr', function(args, sinks) if not args[1] then - print(lunacolors.format [[ + sinks.out:writeln(lunacolors.format [[ cdr: change directory to one which has been recently visied usage: cdr @@ -17,21 +17,21 @@ to get a list of recent directories, use {green}{underline}cdr list{reset}]]) if args[1] == 'list' then local recentDirs = dirs.recentDirs if #recentDirs == 0 then - print 'No directories have been visited.' + sinks.out:writeln 'No directories have been visited.' return 1 end - print(table.concat(recentDirs, '\n')) + sinks.out:writeln(table.concat(recentDirs, '\n')) return end local index = tonumber(args[1]) if not index then - print(string.format('Received %s as index, which isn\'t a number.', index)) + sinks.out:writeln(string.format('Received %s as index, which isn\'t a number.', index)) return 1 end if not dirs.recent(index) then - print(string.format('No recent directory found at index %s.', index)) + sinks.out:writeln(string.format('No recent directory found at index %s.', index)) return 1 end diff --git a/nature/commands/clear.lua b/nature/commands/clear.lua new file mode 100644 index 0000000..68aa197 --- /dev/null +++ b/nature/commands/clear.lua @@ -0,0 +1,7 @@ +local ansikit = require 'ansikit' +local commander = require 'commander' + +commander.register('clear', function() + ansikit.clear(true) + ansikit.cursorTo(0, 0) +end) diff --git a/nature/commands/disown.lua b/nature/commands/disown.lua index f8f144f..6645a0f 100644 --- a/nature/commands/disown.lua +++ b/nature/commands/disown.lua @@ -1,8 +1,8 @@ local commander = require 'commander' -commander.register('disown', function(args) +commander.register('disown', function(args, sinks) if #hilbish.jobs.all() == 0 then - print 'disown: no current job' + sinks.out:writeln 'disown: no current job' return 1 end @@ -10,7 +10,7 @@ commander.register('disown', function(args) if #args < 0 then id = tonumber(args[1]) if not id then - print 'disown: invalid id for job' + sinks.out:writeln 'disown: invalid id for job' return 1 end else @@ -19,7 +19,7 @@ commander.register('disown', function(args) local ok = pcall(hilbish.jobs.disown, id) if not ok then - print 'disown: job does not exist' + sinks.out:writeln 'disown: job does not exist' return 2 end end) diff --git a/nature/commands/doc.lua b/nature/commands/doc.lua index a290cd8..d37e677 100644 --- a/nature/commands/doc.lua +++ b/nature/commands/doc.lua @@ -2,94 +2,99 @@ local commander = require 'commander' local fs = require 'fs' local lunacolors = require 'lunacolors' -commander.register('doc', function(args) +commander.register('doc', function(args, sinks) local moddocPath = hilbish.dataDir .. '/docs/' - local modDocFormat = [[ -%s -%s -# Functions + local stat = pcall(fs.stat, '.git/refs/heads/extended-job-api') + if stat then + -- hilbish git + moddocPath = './docs/' + end + local apidocHeader = [[ +# %s +{grayBg} {white}{italic}%s {reset} + ]] + local modules = table.map(fs.readdir(moddocPath), function(f) + return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', ''))) + end) + local doc = [[ +Welcome to Hilbish's documentation viewer! Here you can find +documentation for builtin functions and other things related +to Hilbish. + +Usage: doc
[subdoc] +Available sections: ]] .. table.concat(modules, ', ') if #args > 0 then local mod = args[1] - local f = io.open(moddocPath .. mod .. '.txt', 'rb') + local f = io.open(moddocPath .. mod .. '.md', 'rb') local funcdocs = nil + local subdocName = args[2] if not f then -- assume subdir - -- dataDir/docs//.txt + -- dataDir/docs//.md moddocPath = moddocPath .. mod .. '/' - local subdocName = args[2] if not subdocName then - subdocName = 'index' + subdocName = '_index' end - f = io.open(moddocPath .. subdocName .. '.txt', 'rb') + f = io.open(moddocPath .. subdocName .. '.md', 'rb') if not f then - print('No documentation found for ' .. mod .. '.') - return + f = io.open(moddocPath .. subdocName:match '%w+' .. '/' .. subdocName .. '.md', 'rb') end - funcdocs = f:read '*a' - local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= 'index.txt' end) - local subdocs = table.map(moddocs, function(fname) - return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.txt', ''))) - end) - if subdocName == 'index' then - funcdocs = funcdocs .. '\nSubdocs: ' .. table.concat(subdocs, ', ') + if not f then + moddocPath = moddocPath .. subdocName .. '/' + subdocName = args[3] or '_index' + f = io.open(moddocPath .. subdocName .. '.md', 'rb') end + if not f then + sinks.out:writeln('No documentation found for ' .. mod .. '.') + return 1 + end + end + funcdocs = f:read '*a':gsub('-([%d]+)', '%1') + local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= '_index.md' and f ~= 'index.md' end) + local subdocs = table.map(moddocs, function(fname) + return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.md', ''))) + end) + if #moddocs ~= 0 then + funcdocs = funcdocs .. '\nSubdocs: ' .. table.concat(subdocs, ', ') end - if not funcdocs then - funcdocs = f:read '*a' - end - local desc = '' - local ok = pcall(require, mod) - local backtickOccurence = 0 - local formattedFuncs = lunacolors.format(funcdocs:sub(1, #funcdocs - 1):gsub('`', function() - backtickOccurence = backtickOccurence + 1 - if backtickOccurence % 2 == 0 then - return '{reset}' - else - return '{underline}{green}' - end - end)) + local valsStr = funcdocs:match '%-%-%-\n([^%-%-%-]+)\n' + local vals = {} + if valsStr then + local _, endpos = funcdocs:find('---\n' .. valsStr .. '\n---\n\n', 1, true) + funcdocs = funcdocs:sub(endpos + 1, #funcdocs) - if ok then - local props = {} - local propstr = '' - local modDesc = '' - local modmt = getmetatable(require(mod)) - if modmt then - modDesc = modmt.__doc - if modmt.__docProp then - -- not all modules have docs for properties - props = table.map(modmt.__docProp, function(v, k) - return lunacolors.underline(lunacolors.blue(k)) .. ' > ' .. v - end) + -- parse vals + local lines = string.split(valsStr, '\n') + for _, line in ipairs(lines) do + local key = line:match '(%w+): ' + local val = line:match '^%w+: (.-)$' + + if key then + vals[key] = val end - if #props > 0 then - propstr = '\n# Properties\n' .. table.concat(props, '\n') .. '\n' - end - desc = string.format(modDocFormat, modDesc, propstr) end end - print(desc .. formattedFuncs) + if mod == 'api' then + funcdocs = string.format(apidocHeader, vals.title, vals.description or 'no description.') .. funcdocs + end + doc = funcdocs:sub(1, #funcdocs - 1) f:close() - - return end - local modules = table.map(fs.readdir(moddocPath), function(f) - return lunacolors.underline(lunacolors.blue(string.gsub(f, '.txt', ''))) - end) - io.write [[ -Welcome to Hilbish's doc tool! Here you can find documentation for builtin -functions and other things. - -Usage: doc
[subdoc] -A section is a module or a literal section and a subdoc is a subsection for it. - -Available sections: ]] - io.flush() - - print(table.concat(modules, ', ')) + local backtickOccurence = 0 + sinks.out:writeln(lunacolors.format(doc:gsub('`', function() + backtickOccurence = backtickOccurence + 1 + if backtickOccurence % 2 == 0 then + return '{reset}' + else + return '{underline}{green}' + end + end):gsub('\n#+.-\n', function(t) + local signature = t:gsub('<.->(.-)', '{underline}%1'):gsub('\\', '<') + return '{bold}{yellow}' .. signature .. '{reset}' + end))) end) diff --git a/nature/commands/exec.lua b/nature/commands/exec.lua new file mode 100644 index 0000000..d279e31 --- /dev/null +++ b/nature/commands/exec.lua @@ -0,0 +1,5 @@ +local commander = require 'commander' + +commander.register('exec', function(args) + hilbish.exec(args[1]) +end) diff --git a/nature/commands/fg.lua b/nature/commands/fg.lua index a3f1451..c5b6738 100644 --- a/nature/commands/fg.lua +++ b/nature/commands/fg.lua @@ -1,15 +1,15 @@ local commander = require 'commander' -commander.register('fg', function() +commander.register('fg', function(_, sinks) local job = hilbish.jobs.last() if not job then - print 'fg: no last job' + sinks.out:writeln 'fg: no last job' return 1 end - local err = job.foreground() -- waits for job; blocks + local err = job:foreground() -- waits for job; blocks if err then - print('fg: ' .. err) + sinks.out:writeln('fg: ' .. err) return 2 end end) diff --git a/nature/completions.lua b/nature/completions.lua index d20cc59..f8127a1 100644 --- a/nature/completions.lua +++ b/nature/completions.lua @@ -24,7 +24,7 @@ function hilbish.completion.handler(line, pos) return {compGroup}, pfx else local ok, compGroups, pfx = pcall(hilbish.completion.call, - 'command.' .. #fields[1], query, ctx, fields) + 'command.' .. fields[1], query, ctx, fields) if ok then return compGroups, pfx end diff --git a/nature/init.lua b/nature/init.lua index df31d8d..d1f919c 100644 --- a/nature/init.lua +++ b/nature/init.lua @@ -1,5 +1,6 @@ -- Prelude initializes everything else for our shell local _ = require 'succulent' -- Function additions +local bait = require 'bait' local fs = require 'fs' package.path = package.path .. ';' .. hilbish.dataDir .. '/?/init.lua' @@ -28,7 +29,9 @@ do return got_virt end - virt_G[key] = os.getenv(key) + if type(key) == 'string' then + virt_G[key] = os.getenv(key) + end return virt_G[key] end, @@ -54,7 +57,6 @@ do if ok then for _, module in ipairs(modules) do local entry = package.searchpath(module, startSearchPath) - print(entry) if entry then dofile(entry) end @@ -63,3 +65,15 @@ do package.path = package.path .. ';' .. startSearchPath end + +bait.catch('error', function(event, handler, err) + print(string.format('Encountered an error in %s handler\n%s', event, err:sub(8))) +end) + +bait.catch('command.not-found', function(cmd) + print(string.format('hilbish: %s not found', cmd)) +end) + +bait.catch('command.not-executable', function(cmd) + print(string.format('hilbish: %s: not executable', cmd)) +end) diff --git a/nature/opts/history.lua b/nature/opts/history.lua index f7ab1d7..016b22d 100644 --- a/nature/opts/history.lua +++ b/nature/opts/history.lua @@ -1,5 +1,6 @@ local bait = require 'bait' bait.catch('command.exit', function(_, cmd, priv) + if not cmd then return end if not priv and hilbish.opts.history then hilbish.history.add(cmd) end end) diff --git a/nature/opts/motd.lua b/nature/opts/motd.lua index 5f30a6c..79954d6 100644 --- a/nature/opts/motd.lua +++ b/nature/opts/motd.lua @@ -2,8 +2,8 @@ local bait = require 'bait' local lunacolors = require 'lunacolors' hilbish.motd = [[ -Hilbish 2.0 is a {red}major{reset} update! If your config doesn't work -anymore, that will definitely be why! A MOTD, very message, much day. +1000 commits on the Hilbish repository brings us to {cyan}Version 2.1!{reset} +Docs, docs, docs... At least builtins work with pipes now. ]] bait.catch('hilbish.init', function() diff --git a/nature/runner.lua b/nature/runner.lua index e155f63..235ab77 100644 --- a/nature/runner.lua +++ b/nature/runner.lua @@ -1,3 +1,4 @@ +--- hilbish.runner local currentRunner = 'hybrid' local runners = {} @@ -74,6 +75,12 @@ function hilbish.runner.setCurrent(name) hilbish.runner.setMode(r.run) end +--- Returns the current runner by name. +--- @returns string +function hilbish.runner.getCurrent() + return currentRunner +end + hilbish.runner.add('hybrid', function(input) local cmdStr = hilbish.aliases.resolve(input) diff --git a/os.go b/os.go new file mode 100644 index 0000000..da9eadd --- /dev/null +++ b/os.go @@ -0,0 +1,27 @@ +package main + +import ( + "hilbish/util" + + rt "github.com/arnodel/golua/runtime" + "github.com/blackfireio/osinfo" +) + +// #interface os +// OS Info +// The `os` interface provides simple text information properties about +// the current OS on the systen. This mainly includes the name and +// version. +// #field family Family name of the current OS +// #field name Pretty name of the current OS +// #field version Version of the current OS +func hshosLoader(rtm *rt.Runtime) *rt.Table { + info, _ := osinfo.GetOSInfo() + mod := rt.NewTable() + + util.SetField(rtm, mod, "family", rt.StringValue(info.Family)) + util.SetField(rtm, mod, "name", rt.StringValue(info.Name)) + util.SetField(rtm, mod, "version", rt.StringValue(info.Version)) + + return mod +} diff --git a/readline/comp-grid.go b/readline/comp-grid.go index 48a2039..c198bdb 100644 --- a/readline/comp-grid.go +++ b/readline/comp-grid.go @@ -4,7 +4,8 @@ import ( "fmt" "strconv" "strings" -) + "github.com/rivo/uniseg" +) // initGrid - Grid display details. Called each time we want to be sure to have // a working completion group either immediately, or later on. Generally defered. @@ -13,8 +14,8 @@ func (g *CompletionGroup) initGrid(rl *Instance) { // Compute size of each completion item box tcMaxLength := 1 for i := range g.Suggestions { - if len(g.Suggestions[i]) > tcMaxLength { - tcMaxLength = len([]rune(g.Suggestions[i])) + if uniseg.GraphemeClusterCount(g.Suggestions[i]) > tcMaxLength { + tcMaxLength = uniseg.GraphemeClusterCount(g.Suggestions[i]) } } @@ -103,7 +104,7 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) { rl.tcUsedY++ } - cellWidth := strconv.Itoa((GetTermWidth() / g.tcMaxX) - 2) + cellWidth := strconv.Itoa((GetTermWidth() / g.tcMaxX) - 4) x := 0 y := 1 @@ -124,7 +125,15 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) { comp += seqInvert } - comp += fmt.Sprintf("%-"+cellWidth+"s %s", fmtEscape(g.Suggestions[i]), seqReset) + sugg := g.Suggestions[i] + if len(sugg) > GetTermWidth() { + sugg = sugg[:GetTermWidth() - 4] + "..." + } + formatStr := "%-"+cellWidth+"s%s " + if g.tcMaxX == 1 { + formatStr = "%s%s" + } + comp += fmt.Sprintf(formatStr, fmtEscape(sugg), seqReset) } // Always add a newline to the group if the end if not punctuated with one diff --git a/readline/history.go b/readline/history.go index 41200c6..f772813 100644 --- a/readline/history.go +++ b/readline/history.go @@ -123,23 +123,20 @@ func (rl *Instance) walkHistory(i int) { // When we are exiting the current line buffer to move around // the history, we make buffer the current line - if rl.histPos == 0 && (rl.histPos+i) == 1 { + if rl.histOffset == 0 && rl.histOffset + i == 1 { rl.lineBuf = string(rl.line) } - switch rl.histPos + i { - case 0, history.Len() + 1: - rl.histPos = 0 + rl.histOffset += i + if rl.histOffset == 0 { rl.line = []rune(rl.lineBuf) rl.pos = len(rl.lineBuf) - return - case -1: - rl.histPos = 0 - rl.lineBuf = string(rl.line) - default: + } else if rl.histOffset <= -1 { + rl.histOffset = 0 + } else { dedup = true old = string(rl.line) - new, err = history.GetLine(history.Len() - rl.histPos - 1) + new, err = history.GetLine(history.Len() - rl.histOffset) if err != nil { rl.resetHelpers() print("\r\n" + err.Error() + "\r\n") @@ -148,7 +145,6 @@ func (rl *Instance) walkHistory(i int) { } rl.clearLine() - rl.histPos += i rl.line = []rune(new) rl.pos = len(rl.line) if rl.pos > 0 { diff --git a/readline/instance.go b/readline/instance.go index fcd8379..039f040 100644 --- a/readline/instance.go +++ b/readline/instance.go @@ -134,6 +134,7 @@ type Instance struct { // history operating params lineBuf string histPos int + histOffset int histNavIdx int // Used for quick history navigation. // diff --git a/readline/readline.go b/readline/readline.go index 731e297..f1d6c96 100644 --- a/readline/readline.go +++ b/readline/readline.go @@ -49,7 +49,7 @@ func (rl *Instance) Readline() (string, error) { // History Init // We need this set to the last command, so that we can access it quickly - rl.histPos = 0 + rl.histOffset = 0 rl.viUndoHistory = []undoItem{{line: "", pos: 0}} // Multisplit @@ -238,7 +238,9 @@ func (rl *Instance) Readline() (string, error) { // Normal completion search does only refresh the search pattern and the comps if rl.modeTabFind || rl.modeAutoFind { + rl.resetVirtualComp(false) rl.backspaceTabFind() + rl.renderHelpers() rl.viUndoSkipAppend = true } else { // Always cancel any virtual completion @@ -331,6 +333,8 @@ func (rl *Instance) Readline() (string, error) { rl.modeTabFind = true rl.updateTabFind([]rune{}) + rl.updateVirtualComp() + rl.renderHelpers() rl.viUndoSkipAppend = true // Tab Completion & Completion Search --------------------------------------------------------------- @@ -484,7 +488,10 @@ func (rl *Instance) Readline() (string, error) { if string(r[:i]) != seqShiftTab && string(r[:i]) != seqForwards && string(r[:i]) != seqBackwards && string(r[:i]) != seqUp && string(r[:i]) != seqDown { - rl.resetVirtualComp(false) + // basically only applies except on 1st ctrl r open + // so if we have not explicitly selected something + // (tabCompletionSelect is false) drop virtual completion + rl.resetVirtualComp(!rl.tabCompletionSelect) } } @@ -517,7 +524,9 @@ func (rl *Instance) Readline() (string, error) { if rl.modeAutoFind || rl.modeTabFind { rl.resetVirtualComp(false) rl.updateTabFind(r[:i]) + rl.renderHelpers() rl.viUndoSkipAppend = true + continue } else { rl.resetVirtualComp(false) rl.editorInput(r[:i]) @@ -537,6 +546,10 @@ func (rl *Instance) Readline() (string, error) { // entry readline is currently configured for and then update the line entries // accordingly. func (rl *Instance) editorInput(r []rune) { + if len(r) == 0 { + return + } + switch rl.modeViMode { case VimKeys: rl.vi(r[0]) @@ -604,6 +617,7 @@ func (rl *Instance) escapeSeq(r []rune) { case string(charEscape): switch { case rl.modeAutoFind: + rl.resetVirtualComp(true) rl.resetTabFind() rl.clearHelpers() rl.resetTabCompletion() @@ -611,6 +625,7 @@ func (rl *Instance) escapeSeq(r []rune) { rl.renderHelpers() case rl.modeTabFind: + rl.resetVirtualComp(true) rl.resetTabFind() rl.resetTabCompletion() diff --git a/readline/tab-virtual.go b/readline/tab-virtual.go index fa7318e..d1e1d76 100644 --- a/readline/tab-virtual.go +++ b/readline/tab-virtual.go @@ -2,6 +2,7 @@ package readline import ( "strings" + "github.com/rivo/uniseg" ) // insertCandidateVirtual - When a completion candidate is selected, we insert it virtually in the input line: @@ -249,10 +250,10 @@ func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, i return case pos >= len(word)-1: word = rTrimWhiteSpace(split[index+1]) - adjust = len(split[index]) - pos - adjust += len(word) - 1 + adjust = uniseg.GraphemeClusterCount(split[index]) - pos + adjust += uniseg.GraphemeClusterCount(word) - 1 default: - adjust = len(word) - pos - 1 + adjust = uniseg.GraphemeClusterCount(word) - pos - 1 } return } diff --git a/readline/update.go b/readline/update.go index 0c2de38..8f85c6d 100644 --- a/readline/update.go +++ b/readline/update.go @@ -1,6 +1,10 @@ package readline -import "golang.org/x/text/width" +import ( + "strings" + + "golang.org/x/text/width" +) // updateHelpers is a key part of the whole refresh process: // it should coordinate reprinting the input line, any Infos and completions @@ -52,19 +56,19 @@ func (rl *Instance) updateReferences() { rl.posY = 0 rl.fullY = 0 - var fullLine, cPosLine int + var curLine []rune if len(rl.currentComp) > 0 { - fullLine = getWidth(rl.lineComp) - cPosLine = getWidth(rl.lineComp[:rl.pos]) + curLine = rl.lineComp } else { - fullLine = getWidth(rl.line) - cPosLine = getWidth(rl.line[:rl.pos]) + curLine = rl.line } + fullLine := getWidth(curLine) + cPosLine := getWidth(curLine[:rl.pos]) // We need the X offset of the whole line toEndLine := rl.promptLen + fullLine fullOffset := toEndLine / GetTermWidth() - rl.fullY = fullOffset + rl.fullY = fullOffset + strings.Count(string(curLine), "\n") fullRest := toEndLine % GetTermWidth() rl.fullX = fullRest diff --git a/readline/vim.go b/readline/vim.go index 886927b..d496705 100644 --- a/readline/vim.go +++ b/readline/vim.go @@ -245,7 +245,7 @@ func (rl *Instance) vi(r rune) { } // Keep the previous cursor position - prev := rl.pos + //prev := rl.pos new, err := rl.StartEditorWithBuffer(multiline, "") if err != nil || len(new) == 0 || string(new) == string(multiline) { @@ -257,11 +257,11 @@ func (rl *Instance) vi(r rune) { // Clean the shell and put the new buffer, with adjusted pos if needed. rl.clearLine() rl.line = new - if prev > len(rl.line) { - rl.pos = len(rl.line) - 1 + rl.pos = len(rl.line) + /*if prev > len(rl.line) { } else { rl.pos = prev - } + }*/ case 'w': // If we were not yanking diff --git a/rl.go b/rl.go index f6cb6cd..96b8451 100644 --- a/rl.go +++ b/rl.go @@ -225,7 +225,11 @@ func (lr *lineReader) Resize() { return } -// lua module +// #interface history +// command history +// The history interface deals with command history. +// This includes the ability to override functions to change the main +// method of saving history. func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table { lrLua := map[string]util.LuaExport{ "add": {lr.luaAddHistory, 1, false}, @@ -241,6 +245,10 @@ func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table { return mod } +// #interface history +// add(cmd) +// Adds a command to the history. +// --- @param cmd string func (lr *lineReader) luaAddHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -254,10 +262,18 @@ func (lr *lineReader) luaAddHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) return c.Next(), nil } +// #interface history +// size() -> number +// Returns the amount of commands in the history. +// --- @returns number func (lr *lineReader) luaSize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.IntValue(int64(lr.fileHist.Len()))), nil } +// #interface history +// get(idx) +// Retrieves a command from the history based on the `idx`. +// --- @param idx number func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -272,6 +288,10 @@ func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil } +// #interface history +// all() -> table +// Retrieves all history. +// --- @returns table func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { tbl := rt.NewTable() size := lr.fileHist.Len() @@ -284,6 +304,9 @@ func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) return c.PushingNext1(t.Runtime, rt.TableValue(tbl)), nil } +// #interface history +// clear() +// Deletes all commands from the history. func (lr *lineReader) luaClearHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { lr.fileHist.clear() return c.Next(), nil diff --git a/rpkg.conf b/rpkg.conf new file mode 100644 index 0000000..957dc05 --- /dev/null +++ b/rpkg.conf @@ -0,0 +1,2 @@ +[rpkg] +user_macros = "${git_props:root}/rpkg.macros" diff --git a/rpkg.macros b/rpkg.macros new file mode 100644 index 0000000..dbcf187 --- /dev/null +++ b/rpkg.macros @@ -0,0 +1,25 @@ +function git_short_hash { + short_hash="$(cached git_short_hash)" + + if [ -z "$short_hash" ]; then + short_hash="$(git rev-parse --short HEAD)" + fi + + output "$short_hash" +} + +function git_tag_version { + tag="$(cached git_tag_version)" + + if [ -z "$tag" ]; then + tag="$(git describe --tags --abbrev=0)" + fi + + # Remove the potential prefix of `v` + if [[ $tag =~ ^v[0-9].* ]]; then + tag="${tag:1}" + fi + + tag="${tag/"-"/"."}" + output "$tag" +} diff --git a/runnermode.go b/runnermode.go index b8995cd..8e9e7b9 100644 --- a/runnermode.go +++ b/runnermode.go @@ -6,6 +6,13 @@ import ( rt "github.com/arnodel/golua/runtime" ) +// #interface runner +// interactive command runner customization +// The runner interface contains functions that allow the user to change +// how Hilbish interprets interactive input. +// Users can add and change the default runner for interactive input to any +// language or script of their choosing. A good example is using it to +// write command in Fennel. func runnerModeLoader(rtm *rt.Runtime) *rt.Table { exports := map[string]util.LuaExport{ "sh": {shRunner, 1, false}, @@ -19,6 +26,20 @@ func runnerModeLoader(rtm *rt.Runtime) *rt.Table { return mod } +// #interface runner +// setMode(cb) +// This is the same as the `hilbish.runnerMode` function. It takes a callback, +// which will be used to execute all interactive input. +// In normal cases, neither callbacks should be overrided by the user, +// as the higher level functions listed below this will handle it. +// --- @param cb function +func _runnerMode() {} + +// #interface runner +// sh(cmd) +// Runs a command in Hilbish's shell script interpreter. +// This is the equivalent of using `source`. +// --- @param cmd string func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -28,13 +49,13 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return nil, err } - input, exitCode, cont, err := execSh(cmd) + _, exitCode, cont, err := execSh(aliases.Resolve(cmd)) var luaErr rt.Value = rt.NilValue if err != nil { luaErr = rt.StringValue(err.Error()) } runnerRet := rt.NewTable() - runnerRet.Set(rt.StringValue("input"), rt.StringValue(input)) + runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd)) runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode))) runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont)) runnerRet.Set(rt.StringValue("err"), luaErr) @@ -42,6 +63,11 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil } +// #interface runner +// lua(cmd) +// Evaluates `cmd` as Lua input. This is the same as using `dofile` +// or `load`, but is appropriated for the runner interface. +// --- @param cmd string func luaRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err diff --git a/sink.go b/sink.go new file mode 100644 index 0000000..9a98856 --- /dev/null +++ b/sink.go @@ -0,0 +1,128 @@ +package main + +import ( + "fmt" + "io" + + "hilbish/util" + + rt "github.com/arnodel/golua/runtime" +) + +var sinkMetaKey = rt.StringValue("hshsink") + +// #type +// A sink is a structure that has input and/or output to/from +// a desination. +type sink struct{ + writer io.Writer + reader io.Reader + ud *rt.UserData +} + +func setupSinkType(rtm *rt.Runtime) { + sinkMeta := rt.NewTable() + + sinkMethods := rt.NewTable() + sinkFuncs := map[string]util.LuaExport{ + "write": {luaSinkWrite, 2, false}, + "writeln": {luaSinkWriteln, 2, false}, + } + util.SetExports(l, sinkMethods, sinkFuncs) + + sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + arg := c.Arg(1) + val := sinkMethods.Get(arg) + + return c.PushingNext1(t.Runtime, val), nil + } + + sinkMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(sinkIndex, "__index", 2, false))) + l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta)) +} + +// #member +// write(str) +// Writes data to a sink. +func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.CheckNArgs(2); err != nil { + return nil, err + } + + s, err := sinkArg(c, 0) + if err != nil { + return nil, err + } + data, err := c.StringArg(1) + if err != nil { + return nil, err + } + + s.writer.Write([]byte(data)) + + return c.Next(), nil +} + +// #member +// writeln(str) +// Writes data to a sink with a newline at the end. +func luaSinkWriteln(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.CheckNArgs(2); err != nil { + return nil, err + } + + s, err := sinkArg(c, 0) + if err != nil { + return nil, err + } + data, err := c.StringArg(1) + if err != nil { + return nil, err + } + + s.writer.Write([]byte(data + "\n")) + + return c.Next(), nil +} + +func newSinkInput(r io.Reader) *sink { + s := &sink{ + reader: r, + } + s.ud = sinkUserData(s) + + return s +} + +func newSinkOutput(w io.Writer) *sink { + s := &sink{ + writer: w, + } + s.ud = sinkUserData(s) + + return s +} + +func sinkArg(c *rt.GoCont, arg int) (*sink, error) { + s, ok := valueToSink(c.Arg(arg)) + if !ok { + return nil, fmt.Errorf("#%d must be a sink", arg + 1) + } + + return s, nil +} + +func valueToSink(val rt.Value) (*sink, bool) { + u, ok := val.TryUserData() + if !ok { + return nil, false + } + + s, ok := u.Value().(*sink) + return s, ok +} + +func sinkUserData(s *sink) *rt.UserData { + sinkMeta := l.Registry(sinkMetaKey) + return rt.NewUserData(s, sinkMeta.AsTable()) +} diff --git a/timer.go b/timer.go index 74d13c4..5d536f5 100644 --- a/timer.go +++ b/timer.go @@ -15,13 +15,19 @@ const ( timerTimeout ) +// #type +// #interface timers +// #property type What type of timer it is +// #property running If the timer is running +// #property duration The duration in milliseconds that the timer will run +// The Job type describes a Hilbish timer. type timer struct{ id int typ timerType running bool dur time.Duration fun *rt.Closure - th *timerHandler + th *timersModule ticker *time.Ticker ud *rt.UserData channel chan struct{} @@ -73,6 +79,10 @@ func (t *timer) stop() error { return nil } +// #interface timers +// #member +// start() +// Starts a timer. func timerStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err @@ -91,6 +101,10 @@ func timerStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #interface timers +// #member +// stop() +// Stops a timer. func timerStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err diff --git a/timerhandler.go b/timerhandler.go index 64caff8..0cb4197 100644 --- a/timerhandler.go +++ b/timerhandler.go @@ -10,10 +10,10 @@ import ( rt "github.com/arnodel/golua/runtime" ) -var timers *timerHandler +var timers *timersModule var timerMetaKey = rt.StringValue("hshtimer") -type timerHandler struct { +type timersModule struct { mu *sync.RWMutex wg *sync.WaitGroup timers map[int]*timer @@ -21,8 +21,8 @@ type timerHandler struct { running int } -func newTimerHandler() *timerHandler { - return &timerHandler{ +func newTimersModule() *timersModule { + return &timersModule{ timers: make(map[int]*timer), latestID: 0, mu: &sync.RWMutex{}, @@ -30,11 +30,11 @@ func newTimerHandler() *timerHandler { } } -func (th *timerHandler) wait() { +func (th *timersModule) wait() { th.wg.Wait() } -func (th *timerHandler) create(typ timerType, dur time.Duration, fun *rt.Closure) *timer { +func (th *timersModule) create(typ timerType, dur time.Duration, fun *rt.Closure) *timer { th.mu.Lock() defer th.mu.Unlock() @@ -54,14 +54,21 @@ func (th *timerHandler) create(typ timerType, dur time.Duration, fun *rt.Closure return t } -func (th *timerHandler) get(id int) *timer { +func (th *timersModule) get(id int) *timer { th.mu.RLock() defer th.mu.RUnlock() return th.timers[id] } -func (th *timerHandler) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { +// #interface timers +// create(type, time, callback) -> @Timer +// Creates a timer that runs based on the specified `time` in milliseconds. +// The `type` can either be `hilbish.timers.INTERVAL` or `hilbish.timers.TIMEOUT` +// --- @param type number +// --- @param time number +// --- @param callback function +func (th *timersModule) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.CheckNArgs(3); err != nil { return nil, err } @@ -83,7 +90,12 @@ func (th *timerHandler) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.UserDataValue(tmr.ud)), nil } -func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { +// #interface timers +// get(id) -> @Timer +// Retrieves a timer via its ID. +// --- @param id number +// --- @returns Timer +func (th *timersModule) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err } @@ -100,7 +112,34 @@ func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table { +// #interface timers +// #field INTERVAL Constant for an interval timer type +// #field TIMEOUT Constant for a timeout timer type +// timeout and interval API +/* +If you ever want to run a piece of code on a timed interval, or want to wait +a few seconds, you don't have to rely on timing tricks, as Hilbish has a +timer API to set intervals and timeouts. + +These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc +accessible with `doc hilbish`). But if you want slightly more control over +them, there is the `hilbish.timers` interface. It allows you to get +a timer via ID and control them. + +## Timer Object +All functions documented with the `Timer` type refer to a Timer object. + +An example of usage: +``` +local t = hilbish.timers.create(hilbish.timers.TIMEOUT, 5000, function() + print 'hello!' +end) + +t:start() +print(t.running) // true +``` +*/ +func (th *timersModule) loader(rtm *rt.Runtime) *rt.Table { timerMethods := rt.NewTable() timerFuncs := map[string]util.LuaExport{ "start": {timerStart, 1, false}, @@ -141,6 +180,9 @@ func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table { luaTh := rt.NewTable() util.SetExports(rtm, luaTh, thExports) + util.SetField(rtm, luaTh, "INTERVAL", rt.IntValue(0)) + util.SetField(rtm, luaTh, "TIMEOUT", rt.IntValue(1)) + return luaTh } diff --git a/userdir.go b/userdir.go new file mode 100644 index 0000000..a6c4852 --- /dev/null +++ b/userdir.go @@ -0,0 +1,23 @@ +package main + +import ( + "hilbish/util" + + rt "github.com/arnodel/golua/runtime" +) + +// #interface userDir +// user-related directories +// This interface just contains properties to know about certain user directories. +// It is equivalent to XDG on Linux and gets the user's preferred directories +// for configs and data. +// #field config The user's config directory +// #field data The user's directory for program data +func userDirLoader(rtm *rt.Runtime) *rt.Table { + mod := rt.NewTable() + + util.SetField(rtm, mod, "config", rt.StringValue(confDir)) + util.SetField(rtm, mod, "data", rt.StringValue(userDataDir)) + + return mod +} diff --git a/util/util.go b/util/util.go index d27cfe1..45e33dc 100644 --- a/util/util.go +++ b/util/util.go @@ -10,53 +10,18 @@ import ( rt "github.com/arnodel/golua/runtime" ) -// Document adds a documentation string to a module. -// It is accessible via the __doc metatable. -func Document(module *rt.Table, doc string) { - mt := module.Metatable() - - if mt == nil { - mt = rt.NewTable() - module.SetMetatable(mt) - } - - mt.Set(rt.StringValue("__doc"), rt.StringValue(doc)) -} - // SetField sets a field in a table, adding docs for it. // It is accessible via the __docProp metatable. It is a table of the names of the fields. -func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value, doc string) { +func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value) { // TODO: ^ rtm isnt needed, i should remove it - SetFieldDoc(module, field, doc) module.Set(rt.StringValue(field), value) } -// SetFieldDoc sets the __docProp metatable for a field on the -// module. -func SetFieldDoc(module *rt.Table, field, doc string) { - mt := module.Metatable() - - if mt == nil { - mt = rt.NewTable() - module.SetMetatable(mt) - } - - docProp := mt.Get(rt.StringValue("__docProp")) - if docProp == rt.NilValue { - docPropTbl := rt.NewTable() - mt.Set(rt.StringValue("__docProp"), rt.TableValue(docPropTbl)) - docProp = mt.Get(rt.StringValue("__docProp")) - } - - docProp.AsTable().Set(rt.StringValue(field), rt.StringValue(doc)) -} - // SetFieldProtected sets a field in a protected table. A protected table // is one which has a metatable proxy to ensure no overrides happen to it. // It sets the field in the table and sets the __docProp metatable on the // user facing table. -func SetFieldProtected(module, realModule *rt.Table, field string, value rt.Value, doc string) { - SetFieldDoc(module, field, doc) +func SetFieldProtected(module, realModule *rt.Table, field string, value rt.Value) { realModule.Set(rt.StringValue(field), value) } diff --git a/vars.go b/vars.go index 810c1ee..7cbc768 100644 --- a/vars.go +++ b/vars.go @@ -11,8 +11,8 @@ var ( // Version info var ( - ver = "v2.0.0" - releaseName = "Hibiscus" + ver = "v2.2.0" + releaseName = "Poppy" gitCommit string gitBranch string ) diff --git a/vars_linux.go b/vars_linux.go index 815ba6a..e1160ba 100644 --- a/vars_linux.go +++ b/vars_linux.go @@ -14,7 +14,7 @@ var ( .. hilbish.userDir.config .. '/hilbish/?/init.lua;' .. hilbish.userDir.config .. '/hilbish/?/?.lua;' .. hilbish.userDir.config .. '/hilbish/?.lua'` - dataDir = "/usr/share/hilbish" + dataDir = "/usr/local/share/hilbish" preloadPath = dataDir + "/nature/init.lua" sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config defaultConfDir = "" diff --git a/website/archetypes/default.md b/website/archetypes/default.md new file mode 100644 index 0000000..00e77bd --- /dev/null +++ b/website/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/website/config.toml b/website/config.toml new file mode 100644 index 0000000..31f42d5 --- /dev/null +++ b/website/config.toml @@ -0,0 +1,35 @@ +baseURL = 'https://rosettea.github.io/Hilbish/' +languageCode = 'en-us' +title = 'Hilbish' +theme = 'hsh' +enableGitInfo = true + +[menu] +[[menu.nav]] + identifier = 'home' + name = 'Home' + pageref = '/' + weight = 1 +[[menu.nav]] + identifier = 'install' + name = 'Install' + pageref = '/install' + weight = 2 +[[menu.nav]] + identifier = 'docs' + name = 'Docs' + pageref = '/docs' + weight = 3 +[[menu.nav]] + identifier = 'blog' + name = 'Blog' + pageref = '/blog' + weight = 4 + +[markup.goldmark.renderer] +unsafe = true + +[author] + [author.sammyette] + name = 'sammyette' + picture = 'https://avatars1.githubusercontent.com/u/38820196?s=460&u=b9f4efb2375bae6cb30656d790c6e0a2939327c0&v=4' diff --git a/website/content/_index.md b/website/content/_index.md new file mode 100644 index 0000000..2b1087b --- /dev/null +++ b/website/content/_index.md @@ -0,0 +1,134 @@ +--- +description: 'Something Unique. Hilbish is the new interactive shell for Lua fans. Extensible, scriptable, configurable: All in Lua.' +--- + +[//]: <> + + +
+

Something Unique.

+

+ Hilbish is the new interactive shell for Lua fans.
+ Extensible, scriptable, configurable: All in Lua. +

+ Install + Github +
+ +
+ +
+
+
+
+
+ + + +
+
+
Simple and Easy Scripting
+
+

+ Hilbish is configured and scripted in the Lua programming language. + This removes all the old, ugly things about Shell script and introduces + everything good about Lua, including other languages (Moonscript & Fennel). +

+
+
+
+
+
+
+
+
+
+ + + +
+
+
History and Completion Menus
+
+

+ Hilbish provides the user with proper menus for completions, + history searching. Want to see your previous commands? Hit Ctrl-R. +

+
+
+
+
+
+
+
+
+
+ + + +
+
+
Tons of Features, and More to Come
+
+

+ Hilbish offers a bunch of features to make your interactive + shell experience rich. Things like syntax highlighting and hinting + available via the Lua API. +

+

* Command hints shown in photo are not default.

+
+
+
+
+
+ + +
+ +
+ +

Why not just Lua?

+

+ Hilbish is your interactive shell as well as a just a Lua interpreter + and enhanced REPL.
+

+
    +
  • Batteries included Lua runtime that's also your user shell!
  • +
  • Hilbish is easily cross platform. It has OS agnostic interfaces for easy cross platform Lua code.
  • +
+ +
+ +

Try It Today!

+

+ Hilbish is known to run on the 3 major platforms (Windows, MacOS, Linux) + but likely builds on other Unixes! +
+ Windows doesn't work as well as it should, so if you're a Windows user, + say something! +

+

diff --git a/website/content/blog/v2.0-release.md b/website/content/blog/v2.0-release.md new file mode 100644 index 0000000..23b8f6f --- /dev/null +++ b/website/content/blog/v2.0-release.md @@ -0,0 +1,114 @@ +--- +title: "Hilbish v2.0 Release" +date: 2022-12-29T01:55:21+00:00 +--- + +Hilbish v2.0 has been released! +Well actually, it was released a week ago, but I only wrote this +Hilbish blog *after* that. + +This is a **big** release, coming 9 months after the previous v1.2.0 and +featuring over 40+ bug fixes and tons of new features and enhancements, so +let's see what is in this release. + +# Documentation +When querying about the problems people have with Hilbish, one of the +issues was its poor documentation. Hilbish had plain text, autogenerated +documentation which only covered the module functions (bait, hilbish, +commander, etc.) and did not include the interfaces (`hilbish.timers`, +`hilbish.jobs` and all that). + +I have tried to improve this by working on documenting all the +interfaces (except for some functions of `hilbish.runner`, that's hard to do) +and made the documentation markdown for use on this website. This means +that users can look at documentation here or with the `doc` command. + +Hopefully this addresses documentation complaints, and if not, please open an issue. + +# Main Bug Fixes +As this is a piece of software with no unit testing that is maintained by me alone, +there is gonna be either some bug or something that I overlooked when +making a change. I make a lot of mistakes. There's also the other fact that +sometimes there's just bugs for any other reasosn. Good thing I fixed +more than 40 of those bugs in this release! + +## Readline Bug Fixes +The pure Go readline library is good in some ways and bad in others. +A good portion of the bug fixes are for the readline library, and also +related to text input with east asian characters and the like (Korean, Japanese, +etc.) + +A few of the fixes (and additions) include: + +- Fixing various crashes, including when there is a "stray" newline at the end of text +- Grid completion menu causing spam and duplicate text when there are items longer than +the terminal and/or contain Japanese or other characters. +- Cursor positioning with CJK characters +- Adding new keybinds and fixing others + +## Other fixes +There are a lot more fixes, even more than the ones listed here, but these are the main ones: + - Don't put alias expanded command in history (I've fixed this 5 times now....) + - Handle stdin being nonblocking + - Completion related fixes, like showing the full name, completing files with spaces + +# Breaking changes +This release is a major version bump not only because there are tons of fixes, but because +there are breaking changes. This means that there are some changes done which would +cause errors with an old user config (breaking). + +## Lua 5.4 +The most important is the use of a new Lua VM library. Previously, Hilbish +used gopher-lua, which implements Lua 5.1. This has been changed to +[golua](https://github.com/arnodel/golua/), which implements Lua 5.4. + +Moving from 5.1 to 5.4 does have breaking changes even if it doesn't seem like it, +and since these are different Lua implementations, there may be some differences there too. + +## Userdata +Previously, objects such as jobs or timers were represented by tables. +This has been changed to userdata to make more sense. + +## Other changes +Runner functions are now required to return a table. +It can (at the moment) have 4 variables: + - `input` (user input) + - `exitCode` (exit code) + - `error` (error message) + - `continue` (whether to prompt for more input) +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. `continue` +got added so that it would be easier for runners to get continued input +without having to actually handle it at all. + +The MacOS config paths now match Linux, since it makes more sense for +a program like Hilbish. + +The Hilbish greeting is now an *opt*, and is printed by default. + +# Feature Additions +Besides fixes and changes, this release also includes a good portion of +new features! Users can now add handlers for syntax highlighting and +inline hinting. + +Some new hooks have been added, like `hilbish.cancel` and `hilbish.init`. +You can look at all the hooks via the `doc hooks` command + +Job management functions have also been added. You can now put jobs in the +foreground/background and disown them via the expected commands and also +via the Lua API. + +The `hilbish.timers` API interface was also added in this release! + +# Closing Off +Hilbish has gone from something small and simple for myself to a slightly +advanced shell with a decent amount of features, and a few users. It +still hasn't reached levels of other alt shells in regards to literally +everything, but the goal is to get there! + +If you want to check the FULL changelog, you can [do so here.](https://github.com/Rosettea/Hilbish/releases/tag/v2.0.0) +This v2.0 release marks an advancement in Hilbish (and also how long +one of my projects hasn't died) and I hope it can advance even further. + +Thanks for reading, and I'll be back for the v2.1 release notes, or maybe +something else in between. diff --git a/website/content/blog/v2.1-release.md b/website/content/blog/v2.1-release.md new file mode 100644 index 0000000..b2e4a17 --- /dev/null +++ b/website/content/blog/v2.1-release.md @@ -0,0 +1,64 @@ +--- +title: "v2.1 Release" +date: 2023-02-07T18:25:38-04:00 +draft: false +--- + +> The release with full changelogs and prebuilt binaries can be +seen at the [v2.1.0](https://github.com/Rosettea/Hilbish/releases/tag/v2.1.0) +tag. + +Oh look! A new release of Hilbish! This time is the v2.1 release, +with a small amount of features and mainly documentation changes and +bug fixes. + +# Documentation +There have been a few documentation enhancements for this release. +This includes: +- Adding the return types for all functions that need them +- Documenting Hilbish types like job objects and timers properly. +They now have a separate heading and listing of properties and methods. +- Fixing outdated documentation + +# Features +## Sinks +A major addition is the new "sink" type for commanders to write +their output to. This was the solution to pipes and other shell +operators not working with builtins. If you wrote a commander +and made it `print`, use `sinks.out:write` instead. + +This is also documented at the [commander docs](./docs/api/commander). + +## `doc` command +Since API documentation has been moved to an API folder and also includes +interfaces, a change has been made to get the module name from the +passed from the requested page. This means that +`doc api hilbish hilbish.jobs` is now shortened to `doc api hilbish.jobs` + +# Bug Fixes +Small release, small amount of bug fixes. Even though, this is the main +part of this release. + +## Completions and Symlinks +Previously Hilbish completions did not work with symlinks properly. +This can be tested in the previous 2.0 release by attempting to +path complete to `/bin`. Since this is (or can be?) a symlink to +`/usr/bin`, it was not marked as a directory and therefore did not +automatically add the ending slash. This has been fixed. + +## Segfaults +I found that when I updated my terminal of choice ([Tym]) for the new +daemon feature, Hilbish would sometimes segfault on startup. This is due +to it getting a resize event on startup while `bait` was not initialized +yet. + +## API Fixes +- The `hilbish.which` function works with aliases. +- `hilbish.completion.files` and `hilbish.completion.bins` will no longer +cause a panic with all empty arguments passed. + +# Next Release +Stay tuned for the v2.2 release, which will have a bigger set of features +and maybe some more bug fixes! + +[Tym]: https://github.com/endaaman/tym diff --git a/website/content/blog/welcome.md b/website/content/blog/welcome.md new file mode 100644 index 0000000..16a878d --- /dev/null +++ b/website/content/blog/welcome.md @@ -0,0 +1,6 @@ +--- +title: "Welcome to the Hilbish blog" +--- + +Hello! Welcome to the Hilbish blog. This will mainly contain release +announcements and some other things relating to Hilbish (development). diff --git a/website/content/docs/_index.md b/website/content/docs/_index.md new file mode 100644 index 0000000..32dbf84 --- /dev/null +++ b/website/content/docs/_index.md @@ -0,0 +1,13 @@ +--- +title: Introduction +layout: doc +weight: -1 +menu: docs +--- + +Hilbish is a hyper-extensible shell mainly intended for interactive use. +To enhance the interactive experience, Hilbish comes with a wide range +of features and sane defaults, including a nice looking prompt, +advanced completion menus and history search. + +Here documents some of the features of Hilbish and the Lua API. diff --git a/website/content/docs/api b/website/content/docs/api new file mode 120000 index 0000000..1c5c360 --- /dev/null +++ b/website/content/docs/api @@ -0,0 +1 @@ +../../../docs/api/ \ No newline at end of file diff --git a/website/content/docs/faq.md b/website/content/docs/faq.md new file mode 100644 index 0000000..997fbaa --- /dev/null +++ b/website/content/docs/faq.md @@ -0,0 +1,25 @@ +--- +title: Frequently Asked Questions +layout: doc +weight: -20 +menu: docs +--- + +# Is Hilbish POSIX compliant? +No, it is not. POSIX compliance is a non-goal. Perhaps in the future, +someone would be able to write a native plugin to support shell scripting +(which would be against it's main goal, but ....) + +# Windows Support? +It compiles for Windows (CI ensures it does), but otherwise it is not +directly supported. If you'd like to improve this situation, +checkout [the discussion](https://github.com/Rosettea/Hilbish/discussions/165). + +# Where is the API documentation? +The builtin `doc` command supplies all documentation of Hilbish provided +APIs. You can also check the sidebar. + +# Why? +Hilbish emerged from the desire of a Lua configured shell. +It was the initial reason that it was created, but now it's more: +to be hyper extensible, simpler and more user friendly. diff --git a/website/content/docs/features/_index.md b/website/content/docs/features/_index.md new file mode 100644 index 0000000..0e14346 --- /dev/null +++ b/website/content/docs/features/_index.md @@ -0,0 +1,11 @@ +--- +title: Features +layout: doc +weight: -40 +menu: docs +--- + +Hilbish has a wide range of features to enhance the user's experience and +is always adding new ones. If there is something missing here or something +you would like to see, please [start a discussion](https://github.com/Rosettea/Hilbish/discussions) +or comment on any existing ones which match your request. diff --git a/website/content/docs/features/runner-mode.md b/website/content/docs/features/runner-mode.md new file mode 100644 index 0000000..87ecc8b --- /dev/null +++ b/website/content/docs/features/runner-mode.md @@ -0,0 +1,17 @@ +--- +title: Runner Mode +description: Customize the interactive script/command runner. +layout: doc +menu: + docs: + parent: "Features" +--- + +Hilbish allows you to change how interactive text can be interpreted. +This is mainly due to the fact that the default method Hilbish uses +is that it runs Lua first and then falls back to shell script. + +In some cases, someone might want to switch to just shell script to avoid +it while interactive but still have a Lua config, or go full Lua to use +Hilbish as a REPL. This also allows users to add alternative languages, +instead of either like Fennel. diff --git a/website/content/docs/getting-started.md b/website/content/docs/getting-started.md new file mode 100644 index 0000000..f0fe56d --- /dev/null +++ b/website/content/docs/getting-started.md @@ -0,0 +1,59 @@ +--- +title: Getting Started +layout: doc +weight: -10 +menu: docs +--- + +To start Hilbish, open a terminal. If Hilbish has been installed and is not the +default shell, you can simply run `hilbish` to start it. This will launch +a normal interactive session. +To exit, you can either run the `exit` command or hit Ctrl+D. + +# Setting as Default +## Login shell +There are a few ways to make Hilbish your default shell. A simple way is +to make it your user/login shell. + +{{< warning `It is not recommended to set Hilbish as your login shell. That is expected to be a +POSIX compliant shell, which Hilbish is not. At most, there will just be a +few variables missing in your environment` >}} + +To do that, simply run `chsh -s /usr/bin/hilbish`. +Some distros (namely Fedora) might have `lchsh` instead, which is used like `lchsh `. +When prompted, you can put the path for Hilbish. + +## Default with terminal +The simpler way is to set the default shell for your terminal. The way of +doing this depends on how your terminal settings are configured. + +## Run after login shell +Some shells (like zsh) have an rc file, like `.zlogin`, which is ran when the shell session +is a login shell. In that file, you can run Hilbish. Example: + +``` +exec hilbish -S -l +``` + +This will replace the shell with Hilbish, set $SHELL to Hilbish and launch it as a login shell. + +# Configuration +Once installation and setup has been done, you can then configure Hilbish. +It is configured and scripted via Lua, so the config file is a Lua file. +You can use any pure Lua library to do whatever you want. + +Hilbish's sample configuration is usually located in `hilbish.dataDir .. '/.hilbishrc.lua'`. +You can print that path via Lua to see what it is: `print(hilbish.dataDir .. '/.hilbishrc.lua')`. +As an example, it will usually will result in `/usr/share/hilbish/.hilbishrc.lua` on Linux. + +To edit your user configuration, you can copy that file to `hilbish.userDir.config .. '/hilbish/init.lua'`, +which follows XDG on Linux and MacOS, and is located in %APPDATA% on Windows. + +As the directory is usually `~/.config` on Linux, you can run this command to copy it: +`cp /usr/share/hilbish/.hilbishrc.lua ~/.config/hilbish/init.lua` + +Now you can get to editing it. Since it's just a Lua file, having basic +knowledge of Lua would help. All of Lua's standard libraries and functions +from Lua 5.4 are available. Hilbish has some custom and modules that are +available. To see them, you can run the `doc` command. This also works as +general documentation for other things. diff --git a/website/content/install.md b/website/content/install.md new file mode 100644 index 0000000..1ae103f --- /dev/null +++ b/website/content/install.md @@ -0,0 +1,55 @@ +--- +title: Install +description: Steps on how to install Hilbish on all the OSes and distros supported. +layout: page +--- + +## Official Binaries +The best way to get Hilbish is to get a build directly from GitHub. +At any time, there are 2 versions of Hilbish recommended for download: +the latest stable release, and development builds from the master branch. + +You can download both at any time, but note that the development builds may +have breaking changes. + +For the latest **stable release**, check here: https://github.com/Rosettea/Hilbish/releases/latest +For a **development build**: https://nightly.link/Rosettea/Hilbish/workflows/build/master + +## Package Repositories +### Fedora (COPR) +An official COPR is offered to install Hilbish easily on Fedora. +Enable the repo: +``` +sudo dnf copr enable sammyette/Hilbish +``` + +And install Hilbish: +``` +sudo dnf install hilbish +``` + +Or for the latest development build from master: +``` +sudo dnf install hilbish-git +``` + +### Arch Linux (AUR) +Hilbish is on the AUR. Setup an AUR helper, and install. +Example with yay: + +``` +yay -S hilbish +``` + +Or, from master branch: +``` +yay -S hilbish-git +``` + +### Alpine Linux +Hilbish is currentlty in the testing/edge repository for Alpine. +Follow the steps [here](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository) +(Using testing repositories) and install: +``` +apk add hilbish +``` diff --git a/website/static/hilbish-flower.png b/website/static/hilbish-flower.png new file mode 100644 index 0000000..b4fb0f7 Binary files /dev/null and b/website/static/hilbish-flower.png differ diff --git a/website/themes/hsh/LICENSE b/website/themes/hsh/LICENSE new file mode 100644 index 0000000..da3c8c1 --- /dev/null +++ b/website/themes/hsh/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Rosettea + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/website/themes/hsh/archetypes/default.md b/website/themes/hsh/archetypes/default.md new file mode 100644 index 0000000..ac36e06 --- /dev/null +++ b/website/themes/hsh/archetypes/default.md @@ -0,0 +1,2 @@ ++++ ++++ diff --git a/website/themes/hsh/layouts/404.html b/website/themes/hsh/layouts/404.html new file mode 100644 index 0000000..06b3561 --- /dev/null +++ b/website/themes/hsh/layouts/404.html @@ -0,0 +1,7 @@ +{{ define "main"}} +
+
+

Go Home

+
+
+{{ end }} diff --git a/website/themes/hsh/layouts/_default/_markup/render-heading.html b/website/themes/hsh/layouts/_default/_markup/render-heading.html new file mode 100644 index 0000000..da71fe1 --- /dev/null +++ b/website/themes/hsh/layouts/_default/_markup/render-heading.html @@ -0,0 +1,11 @@ + + + {{ .Text | safeHTML }} + + + + + +{{ if eq .Text ""}} +
+{{ end }} diff --git a/website/themes/hsh/layouts/_default/_markup/render-link.html b/website/themes/hsh/layouts/_default/_markup/render-link.html new file mode 100644 index 0000000..b0d800e --- /dev/null +++ b/website/themes/hsh/layouts/_default/_markup/render-link.html @@ -0,0 +1 @@ +{{ .Text | safeHTML }} diff --git a/website/themes/hsh/layouts/_default/baseof.html b/website/themes/hsh/layouts/_default/baseof.html new file mode 100644 index 0000000..dad9ef8 --- /dev/null +++ b/website/themes/hsh/layouts/_default/baseof.html @@ -0,0 +1,21 @@ + + + {{- partial "head.html" . -}} + + + + + + + + + + + + + + {{- partial "header.html" . -}} + {{- block "main" . }}{{- end }} + {{- partial "footer.html" . -}} + + diff --git a/website/themes/hsh/layouts/_default/doc.html b/website/themes/hsh/layouts/_default/doc.html new file mode 100644 index 0000000..2748a23 --- /dev/null +++ b/website/themes/hsh/layouts/_default/doc.html @@ -0,0 +1,53 @@ +{{ define "main" }} +
+
+
+ +
+
+
+
+

{{ .Title }}

+

+ {{ $date := .Date.UTC.Format "Jan 2, 2006" }} + {{ $lastmod := .Lastmod.UTC.Format "Jan 2, 2006" }} + {{ if and (ne $lastmod $date) (gt .Lastmod .Date) }} + Last updated {{ $lastmod }}
+ {{ end }} + + {{ if .Description }} + {{ .Description }}
+ {{ end}} +

+ {{.Content}} +
+ + +
+
+{{ end }} + diff --git a/website/themes/hsh/layouts/_default/list.html b/website/themes/hsh/layouts/_default/list.html new file mode 100644 index 0000000..bbb9e8a --- /dev/null +++ b/website/themes/hsh/layouts/_default/list.html @@ -0,0 +1,21 @@ +{{ define "main" }} +
+
+ {{ range where .Site.RegularPages "Section" "in" "blog" }} +
+
+
+
{{ .Title }}
+
+ {{- if isset .Params "date" -}} + + {{- end -}} +
+

{{if .Description}}{{ .Description }}{{ else }}{{ .Summary }}{{ end }}

+
+
+
+ {{- end }} +
+
+{{ end }} diff --git a/website/themes/hsh/layouts/_default/page.html b/website/themes/hsh/layouts/_default/page.html new file mode 100644 index 0000000..69aaf99 --- /dev/null +++ b/website/themes/hsh/layouts/_default/page.html @@ -0,0 +1,7 @@ +{{ define "main" }} +
+
+ {{.Content}} +
+
+{{ end }} diff --git a/website/themes/hsh/layouts/_default/single.html b/website/themes/hsh/layouts/_default/single.html new file mode 100644 index 0000000..bd7e18c --- /dev/null +++ b/website/themes/hsh/layouts/_default/single.html @@ -0,0 +1,17 @@ +{{ define "main" }} +
+
+

{{ .Title }}

+ + + by {{ .Site.Author.sammyette.name }} + {{- if isset .Params "date" -}} + + {{- end -}} + +
+ {{.Content}} +
+
+
+{{ end }} diff --git a/website/themes/hsh/layouts/index.html b/website/themes/hsh/layouts/index.html new file mode 100644 index 0000000..a9f64d2 --- /dev/null +++ b/website/themes/hsh/layouts/index.html @@ -0,0 +1,6 @@ +{{ define "main" }} +
+ {{.Content}} +
+{{ end }} + diff --git a/website/themes/hsh/layouts/partials/footer.html b/website/themes/hsh/layouts/partials/footer.html new file mode 100644 index 0000000..15aa193 --- /dev/null +++ b/website/themes/hsh/layouts/partials/footer.html @@ -0,0 +1,32 @@ + diff --git a/website/themes/hsh/layouts/partials/head.html b/website/themes/hsh/layouts/partials/head.html new file mode 100644 index 0000000..fca4558 --- /dev/null +++ b/website/themes/hsh/layouts/partials/head.html @@ -0,0 +1,38 @@ + + {{ $title := print .Title " — " .Site.Title }} + {{ if .IsHome }}{{ $title = .Site.Title }}{{ end }} + {{ $title }} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/themes/hsh/layouts/partials/header.html b/website/themes/hsh/layouts/partials/header.html new file mode 100644 index 0000000..281b602 --- /dev/null +++ b/website/themes/hsh/layouts/partials/header.html @@ -0,0 +1,25 @@ +
+ +
diff --git a/website/themes/hsh/layouts/shortcodes/warning.html b/website/themes/hsh/layouts/shortcodes/warning.html new file mode 100644 index 0000000..b217b57 --- /dev/null +++ b/website/themes/hsh/layouts/shortcodes/warning.html @@ -0,0 +1,6 @@ + diff --git a/website/themes/hsh/theme.toml b/website/themes/hsh/theme.toml new file mode 100644 index 0000000..a567739 --- /dev/null +++ b/website/themes/hsh/theme.toml @@ -0,0 +1,21 @@ +# theme.toml template for a Hugo theme +# See https://github.com/gohugoio/hugoThemes#themetoml for an example + +name = "Hsh" +license = "MIT" +licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE" +description = "" +homepage = "http://example.com/" +tags = [] +features = [] +min_version = "0.41.0" + +[author] + name = "" + homepage = "" + +# If porting an existing theme +[original] + name = "" + homepage = "" + repo = ""