Compare commits

...

76 Commits

Author SHA1 Message Date
sammyette 4c61c551aa
fix: menu refreshes
now there's no flicker *and* its not bugged with
leaving the text on exit
2024-05-13 22:10:28 -04:00
sammyette ff6e08902f
fix(greenhouse): reset at end and beginning of line to fix colors leaking to other lines 2024-05-03 19:06:17 -04:00
sammyette bbd5637e9b
ci: update all actions (#302) 2024-05-03 12:47:24 -04:00
TorchedSammy d46c079afb docs: [ci] generate new docs 2024-04-28 01:29:33 +00:00
sammyette 42ab856e45
feat(commander): add function to return all commanders (closes #266) 2024-04-27 21:29:06 -04:00
TorchedSammy 478e3020b1 docs: [ci] generate new docs 2024-04-28 01:04:12 +00:00
sammyette a20123fc24
feat: allow hilbish.run to take a table of files to use for output (#291) 2024-04-27 21:03:54 -04:00
sammyette fb9d30520a
fix(greenhouse): draws/updates after exit by resize 2024-04-27 13:52:07 -04:00
sammyette 521298733e
chore: update changelog 2024-04-27 10:52:00 -04:00
sammyette f8a391e24f
chore: bump version 2024-04-27 10:49:26 -04:00
James Dugan aa376f9b14
feat: cat implementation now uses chunk reading (#290) 2024-04-20 19:04:24 -04:00
sammyette 16b39fe157
fix: call highlighter and hinter from global table (closes #289) 2024-04-19 07:54:46 -04:00
sammyette 40c3cecabb
chore: merge branch 2024-04-16 12:09:18 -04:00
sammyette ce99209f0d
chore: update version info 2024-04-16 12:09:14 -04:00
sammyette d1dbb84c16
fix(readline): flickering on line refresh (#288) 2024-04-16 11:56:57 -04:00
sammyette 5b84718021
chore: merge 2024-04-16 10:43:07 -04:00
sammyette 3d727e6411
fix: check for altHistory in sequence handling
fixes a panic
2024-04-16 10:34:24 -04:00
sammyette 7d487bfc98
refactor: remove metatables protecting hilbish api and opts (#287) 2024-04-11 18:16:45 -04:00
sammyette 11fc1edd94
fix: only halt in error clause 2024-04-06 18:52:55 -04:00
sammyette b416264138
fix: tty checks (#284) 2024-04-06 18:37:16 -04:00
sammyette 11323a70aa
feat: add page keys to greenhouse 2023-12-30 16:16:59 -04:00
sammyette db32fd7a87
chore: remove unused label 2023-12-26 12:53:19 -04:00
sammyette 017702ea0d
fix(greenhouse): remove goro usage 2023-12-26 12:52:39 -04:00
sammyette 30830ed6ed
chore: prepare v2.2.1 2023-12-26 12:33:57 -04:00
sammyette 7932fa677f
fix(hilbish.goro): attempt to recover panic 2023-12-26 12:32:32 -04:00
sammyette c1b78a8ea7
fix: remove debug print 2023-12-26 10:13:35 -04:00
sammyette 3128ef7934
feat: merry christmas! 2023-12-26 00:18:30 -04:00
sammyette c3394f7046
ci: update go release action 2023-12-26 00:18:27 -04:00
sammyette b1f957e98f
website: add 2.2 release blog 2023-12-26 00:13:50 -04:00
sammyette f788c9374c
website: fix up home page contents 2023-12-26 00:13:35 -04:00
sammyette b7602b0bbe
chore: update changelog 2023-12-25 23:29:53 -04:00
sammyette 7b84faabf9
chore: merge from upstream 2023-12-25 23:09:31 -04:00
sammyette 8fdae6c1d7
refactor: doc improvements (again) (#260) 2023-12-25 22:08:29 -05:00
sammyette 190ed5b2dc
chore: update motd for 2.2 2023-12-24 19:04:08 -04:00
sammyette 9d5f5abef4
feat: add pprof 2023-12-18 21:31:04 -04:00
sammyette a0513c0a05
chore: merge 2023-12-06 22:21:11 -04:00
sammyette 1d64a57e24
fix: return if runner did not return table 2023-12-06 22:20:41 -04:00
TorchedSammy 1a4585aa7a docs: [ci] generate new docs 2023-12-02 15:17:32 +00:00
sammyette 60fd4c3b4c
fix: correction to completion documentation
i added an s even though there's no s.
to account for others making the same mistake, i added both.
hilbish.completion and hilbish.completions will work, but
hilbish.completion without s will be removed at a major update.
2023-12-02 11:11:28 -04:00
sammyette 29e14c1aee
feat: native modules (#257) 2023-11-11 19:39:31 -05:00
Super ee1ed077b9
fix: infinite loop when navigating history without any history (#268)
fixes #252
2023-11-02 17:50:36 -04:00
sammyette 78eb657897
feat: hilbish native pager (#240) 2023-10-25 00:41:53 -04:00
sammyette a5b6fc8eda
fix: return cmdstr in alias resolve instead of nothing 2023-09-30 23:25:15 -04:00
sammyette 4421869b85
fix: check length of args in aliases resolve
this shouldnt be a problem.. but ... what
2023-09-30 23:21:00 -04:00
sammyette 6d07d8db53
chore: merge from upstream 2023-09-30 19:53:38 -04:00
sammyette 483e5f6dbe
fix(hilbish.completions): return prefix on the `call` function (from #263) 2023-09-30 19:52:33 -04:00
sammyette 83492e4e69
docs: fix odd sentence in runner-mode docs 2023-09-27 20:31:27 -04:00
sammyette 4fcd310048
chore: merge from upstream 2023-09-09 13:53:22 -04:00
sammyette db8e87e5dd
fix: compare lower case strings in contains function
this fixes file completion on windows.
2023-09-09 13:53:09 -04:00
sammyette ba04fb2af7
feat: allow numbered substitutions in aliases (#258) 2023-09-09 13:51:58 -04:00
sammyette caff604d95
ci: stop using deprecated method of setting branch name 2023-07-10 22:42:04 -04:00
sammyette 2b9059f726
ci: add missing quote 2023-07-10 22:38:22 -04:00
sammyette 00c967d7c1
ci: fix branch name 2023-07-10 22:38:13 -04:00
TorchedSammy ab26b82a9f docs: [ci] generate new docs 2023-07-11 01:57:00 +00:00
sammyette f540fc2c04
feat: show go version hilbish was compiled with 2023-07-10 21:56:35 -04:00
sammyette c6c81ddece
feat: add notification/message interface (#239)
details in #219
2023-07-10 19:03:30 -04:00
sammyette 3eae0f07be
feat: add fuzzy searching for completion and history search (#247)
* feat: add fuzzy searching for completion and history search

* feat: add fuzzy opt for fuzzy history searching

* chore: add fuzzy opt to changelog
2023-07-10 00:06:29 -04:00
sammyette cbc5e81c9d
feat: show indexes on cdr list and change home to tilde (#253)
* feat: show indexes on cdr list and change home to tilde

* fix: remove underline in cdr help page
2023-07-09 18:43:21 -04:00
TorchedSammy 6729ecddea docs: [ci] generate new docs 2023-04-15 14:36:56 +00:00
sammyette 485306fbe2
docs: clarify use of highlighter function 2023-04-15 10:36:21 -04:00
sammyette 480d4de750
ci: set baseurl properly 2023-04-14 01:25:14 -04:00
sammyette d7dde44a0c
ci: (attempt to) fix base url on non-master versions 2023-04-14 01:23:24 -04:00
sammyette 237b306eb8
ci: fix branch name var 2023-04-14 00:52:45 -04:00
sammyette bd01b478d5
website: add more info for runner mode 2023-04-14 00:40:41 -04:00
sammyette a37f8e3b30
ci: output website builds from other branches to different dirs 2023-04-14 00:40:13 -04:00
sammyette 9c6d6c4f31
website: fix wording on features main page 2023-04-14 00:24:19 -04:00
sammyette 2dbd201e1e
refactor: website/branding cleanup (#250)
* website: add padding to home page

* docs: slight touch ups to readme

* website: reword/reformat content on home and getting started page

* website: fix docs page layout

- the sidenav for doc pages looks the same as the navbar.
- it will be hidden by default on mobile.
- sidenav looks like the navbar on mobile and is more
seamless in general

* docs: update description, logo, dont center badges

* docs: fix badges

* website: fix padding

* website: add borders, add margins for sidenav

* website: update description on homepage

* website: add margins for blog list

* chore: use new logo

* docs: use logo and text combined for readme

* docs: make logo bigger

* website: use combined logo and text asset

* docs: remove hilbish-text asset

* website: fix navbar expand button

* website: add more padding on doc pages

* website(blog): add post about website improvements

* website(blog): add post about website improvements

* website: add more detail on the install page

* website: add screenshots section

* docs: fix up screenshots section

* docs: remove toc and image alignment
2023-04-14 00:04:52 -04:00
sammyette 3dd834fcdf
fix: merge patch releases with master (#248)
my absolutely terrible april fools release was made on a separate branch. this pr merges the fixes on master so history can be synced up. (and mainly to get the 2.1.2 blog post)
2023-04-10 12:38:30 -04:00
sammyette 659d1e0c4e
website(blog): add v2.1.1 release blog 2023-04-01 18:16:13 -04:00
TorchedSammy 0379e302ac docs: [ci] generate new docs 2023-03-25 21:42:46 +00:00
sammyette d577b3c958
feat: sink enhancements (#241)
* feat: add read method to sinks

* feat: add manual flush method for sinks

* fix: add flush as sink method and fix arg amount for read

* feat: add pipe property to sinks to see if stdin sink is a pipe

* feat: add method autoFlush to toggle/change automatic flushing

* chore: update changelog
2023-03-25 17:42:25 -04:00
sammyette 3d0fffd49a
chore: merge from upstream 2023-02-19 15:12:34 -04:00
sammyette 013fa30658
fix: correct job management commands to call with colon 2023-02-19 15:11:55 -04:00
crumb 4e37e8ca47
fix: replace sed with vgrep (#238) 2023-02-18 20:02:52 -04:00
sammyette 26c8f28034
chore: bump version to 2.2 2023-02-18 12:30:15 -04:00
sammyette 6be65c04a6
website(blog/v2.1): link to release in blog 2023-02-10 17:11:44 -04:00
136 changed files with 5801 additions and 1497 deletions

View File

@ -1,8 +1,12 @@
name: Build name: Build
on: on:
- push push:
- pull_request branches:
- master
pull_request:
branches:
- master
jobs: jobs:
build: build:
@ -19,18 +23,18 @@ jobs:
goos: windows goos: windows
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v5
with: with:
go-version: '1.18.8' go-version: '1.18.8'
- name: Download Task - name: Download Task
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d' run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
- name: Build - name: Build
run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} ./bin/task run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} ./bin/task
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v4
if: matrix.goos == 'windows' if: matrix.goos == 'windows'
with: with:
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }} name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
@ -44,7 +48,7 @@ jobs:
libs libs
docs docs
emmyLuaDocs emmyLuaDocs
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v4
if: matrix.goos != 'windows' if: matrix.goos != 'windows'
with: with:
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }} name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}

View File

@ -26,7 +26,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL

View File

@ -9,8 +9,8 @@ jobs:
gen: gen:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-go@v2 - uses: actions/setup-go@v5
- name: Run docgen - name: Run docgen
run: go run cmd/docgen/docgen.go run: go run cmd/docgen/docgen.go
- name: Commit new docs - name: Commit new docs

View File

@ -9,7 +9,7 @@ jobs:
create-release: create-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: taiki-e/create-gh-release-action@v1 - uses: taiki-e/create-gh-release-action@v1
with: with:
title: Hilbish $tag title: Hilbish $tag
@ -30,13 +30,13 @@ jobs:
- goarch: arm64 - goarch: arm64
goos: windows goos: windows
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
- name: Download Task - name: Download Task
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d' run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
- uses: wangyoucao577/go-release-action@v1.25 - uses: wangyoucao577/go-release-action@v1
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }} goos: ${{ matrix.goos }}

View File

@ -3,29 +3,49 @@ name: Build website
on: on:
push: push:
branches: branches:
- master - master
- docs-refactor pull_request:
branches:
- master
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
- name: Setup Hugo - name: Setup Hugo
uses: peaceiris/actions-hugo@v2 uses: peaceiris/actions-hugo@v3
with: with:
hugo-version: 'latest' hugo-version: '0.111.3'
extended: true extended: true
- name: Set branch name
id: branch
run: echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> "$GITHUB_ENV"
- name: Fix base URL
if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea'
run: sed -i "s%baseURL = 'https://rosettea.github.io/Hilbish/'%baseURL = 'https://rosettea.github.io/Hilbish/versions/${{ env.BRANCH_NAME }}'%" website/config.toml
- name: Build - name: Build
run: 'cd website && hugo --minify' run: 'cd website && hugo --minify'
- name: Deploy - name: Deploy
uses: peaceiris/actions-gh-pages@v3 if: env.BRANCH_NAME == 'master' && github.repository_owner == 'Rosettea'
uses: peaceiris/actions-gh-pages@v4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./website/public publish_dir: ./website/public
keep_files: true
- name: Deploy
if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea'
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./website/public
destination_dir: versions/${{ env.BRANCH_NAME }}
keep_files: true

View File

@ -1,18 +1,39 @@
-- Default Hilbish config -- Default Hilbish config
local hilbish = require 'hilbish'
local lunacolors = require 'lunacolors' local lunacolors = require 'lunacolors'
local bait = require 'bait' local bait = require 'bait'
local ansikit = require 'ansikit' local ansikit = require 'ansikit'
local unreadCount = 0
local running = false
local function doPrompt(fail) local function doPrompt(fail)
hilbish.prompt(lunacolors.format( hilbish.prompt(lunacolors.format(
'{blue}%u {cyan}%d ' .. (fail and '{red}' or '{green}') .. '' '{blue}%u {cyan}%d ' .. (fail and '{red}' or '{green}') .. ''
)) ))
end end
local function doNotifyPrompt()
if running or unreadCount == hilbish.messages.unreadCount() then return end
local notifPrompt = string.format('• %s unread notification%s', hilbish.messages.unreadCount(), hilbish.messages.unreadCount() > 1 and 's' or '')
unreadCount = hilbish.messages.unreadCount()
hilbish.prompt(lunacolors.blue(notifPrompt), 'right')
hilbish.timeout(function()
hilbish.prompt('', 'right')
end, 3000)
end
doPrompt() doPrompt()
bait.catch('command.preexec', function()
running = true
end)
bait.catch('command.exit', function(code) bait.catch('command.exit', function(code)
running = false
doPrompt(code ~= 0) doPrompt(code ~= 0)
doNotifyPrompt()
end) end)
bait.catch('hilbish.vimMode', function(mode) bait.catch('hilbish.vimMode', function(mode)
@ -22,3 +43,7 @@ bait.catch('hilbish.vimMode', function(mode)
ansikit.cursorStyle(ansikit.lineCursor) ansikit.cursorStyle(ansikit.lineCursor)
end end
end) end)
bait.catch('hilbish.notification', function(notif)
doNotifyPrompt()
end)

View File

@ -1,5 +1,117 @@
# 🎀 Changelog # 🎀 Changelog
## Unreleased
### Added
- `fs.pipe` function to get a pair of connected files (a pipe).
- Added an alternative 2nd parameter to `hilbish.run`, which is `streams`.
`streams` is a table of input and output streams to run the command with.
It uses these 3 keys:
- `input` as standard input for the command
- `out` as standard output
- `err` as standard error
Here is a minimal example of the new usage which allows users to now pipe commands
directly via Lua functions:
```lua
local fs = require 'fs'
local pr, pw = fs.pipe()
hilbish.run('ls -l', {
stdout = pw,
stderr = pw,
})
pw:close()
hilbish.run('wc -l', {
stdin = pr
})
```
### Fixed
- Fix ansi attributes causing issues with text when cut off in greenhouse
## [2.2.3] - 2024-04-27
### Fixed
- Highligher and hinter work now, since it was regressed from the previous minor release.
- `cat` command no longer prints extra newline at end of each file
### Added
- `cat` command now reads files in chunks, allowing for reading large files
## [2.2.2] - 2024-04-16
### Fixed
- Line refresh fixes (less flicker)
- Do more checks for a TTY
- Panic if ENOTTY is thrown from readline
- use `x/term` function to check if a terminal
### Added
- Page Up/Down keybinds for Greenhouse will now scroll up and down the size of the region (a page)
### Changed
- Remove usage of `hilbish.goro` in Greenhouse.
- Values in `hilbish` table are no longer protected. This means
they can be overridden. (#287)
## [2.2.1] - 2023-12-26
### Fixed
- Removed a left over debug print
- Recover panic in `hilbish.goro`
## [2.2.0] - 2023-12-25
### Added
- [Native Modules](https://rosettea.github.io/Hilbish/docs/api/hilbish/hilbish.module/)
- Made a few additions to the sink type:
- `read()` method for retrieving input (so now the `in` sink of commanders is useful)
- `flush()` and `autoFlush()` related to flushing outputs
- `pipe` property to check if a sink with input is a pipe (like stdin)
- Add fuzzy search to history search (enable via `hilbish.opts.fuzzy = true`)
- Show indexes on cdr list and use ~ for home directory.
- Fix doc command not displaying correct subdocs when using shorthand api doc access (`doc api hilbish.jobs` as an example)
- `hilbish.messages` interface (details in [#219])
- `hilbish.notification` signal when a message/notification is sent
- `notifyJobFinish` opt to send a notification when background jobs are
- `hilbish.goVersion` for the version of Go used to compile Hilbish.
completed.
- Allow numbered arg substitutions in aliases.
- Example: `hilbish.alias('hello', 'echo %1 says hello')` allows the user to run `hello hilbish`
which will output `hilbish says hello`.
- Greenhouse
- Greenhouse is a pager library and program. Basic usage is `greenhouse <file>`
- Using this also brings enhancements to the `doc` command like easy
navigation of neighboring doc files.
Ctrl-N can be used for the table of contents, which views adjacent documentation.
### Changed
- Documentation for EVERYTHING has been improved, with more
information added, code example, parameter details, etc.
You can see the improvements!
- Documentation has gotten an uplift in the `doc` command.
This includes:
- Proper highlighting of code
- Paging (via Greenhouse)
- Highlighting more markdown things
### Fixed
- Fix panic when runner doesn't return a table
- Fix edge case of crash on empty alias resolve
- File completion on Windows
- Job management commands work now
- Fix infinite loop when navigating history without any history. [#252](https://github.com/Rosettea/Hilbish/issues/252)
- Return the prefix when calling `hilbish.completions.call`. [#219](https://github.com/Rosettea/Hilbish/issues/219)
- Replaced `sed` in-place editing with `grep` and `mv` for compatibility with BSD utils
## [2.1.2] - 2022-04-10
### Removed
- Bad april fools code ;(
## [2.1.1] - 2022-04-01
### Added
- Validation checks for command input
- Improved runtime performance
- Validate Lua code
## [2.1.0] - 2022-02-10 ## [2.1.0] - 2022-02-10
### Added ### Added
- Documented custom userdata types (Job and Timer Objects) - Documented custom userdata types (Job and Timer Objects)
@ -635,6 +747,12 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish. First "stable" release of Hilbish.
[2.2.3]: https://github.com/Rosettea/Hilbish/compare/v2.2.2...v2.2.3
[2.2.2]: https://github.com/Rosettea/Hilbish/compare/v2.2.1...v2.2.2
[2.2.1]: https://github.com/Rosettea/Hilbish/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/Rosettea/Hilbish/compare/v2.1.0...v2.2.0
[2.1.2]: https://github.com/Rosettea/Hilbish/compare/v2.1.1...v2.1.2
[2.1.1]: https://github.com/Rosettea/Hilbish/compare/v2.1.0...v2.1.1
[2.1.0]: https://github.com/Rosettea/Hilbish/compare/v2.0.1...v2.1.0 [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.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]: https://github.com/Rosettea/Hilbish/compare/v1.2.0...v2.0.0

View File

@ -1,45 +1,35 @@
<div align="center"> <img src="./assets/hilbish-logo-and-text.png" width=512><br>
<img src="./assets/hilbish-flower.png" width=128><br> <blockquote>
<img src="./assets/hilbish-text.png" width=256><br> 🌓 The Moon-powered shell! A comfy and extensible shell for Lua fans! 🌺 ✨
<blockquote> </blockquote>
🌺 The flower shell. A comfy and nice little shell for Lua fans!
</blockquote>
<p align="center">
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/Rosettea/Hilbish?style=flat-square">
<img alt="GitHub commits since latest release (by date)" src="https://img.shields.io/github/commits-since/Rosettea/Hilbish/latest?style=flat-square">
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors/Rosettea/Hilbish?style=flat-square"><br>
<a href="https://github.com/Rosettea/Hilbish/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22"><img src="https://img.shields.io/github/issues/Hilbis/Hilbish/help%20wanted?style=flat-square&color=green" alt="help wanted"></a>
<a href="https://github.com/Rosettea/Hilbish/blob/master/LICENSE"><img alt="GitHub license" src="https://img.shields.io/github/license/Rosettea/Hilbish?style=flat-square"></a>
<a href="https://discord.gg/3PDdcQz"><img alt="Discord" src="https://img.shields.io/discord/732357621503229962?color=blue&style=flat-square"></a>
</p>
</div>
Hilbish is a extensible shell (framework). It was made to be very customizable <img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/Rosettea/Hilbish?style=flat-square"><img alt="GitHub commits since latest release (by date)" src="https://img.shields.io/github/commits-since/Rosettea/Hilbish/latest?style=flat-square"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/Rosettea/Hilbish?style=flat-square"><br>
via the Lua programming language. It aims to be easy to use for the casual <a href="https://github.com/Rosettea/Hilbish/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22"><img src="https://img.shields.io/github/issues/Hilbis/Hilbish/help%20wanted?style=flat-square&color=green" alt="help wanted"></a>
people but powerful for those who want to tinker more with their shell, <a href="https://github.com/Rosettea/Hilbish/blob/master/LICENSE"><img alt="GitHub license" src="https://img.shields.io/github/license/Rosettea/Hilbish?style=flat-square"></a>
the thing used to interface with most of the system. <a href="https://discord.gg/3PDdcQz"><img alt="Discord" src="https://img.shields.io/discord/732357621503229962?color=blue&style=flat-square"></a>
<br>
Hilbish is an extensible shell designed to be highly customizable.
It is configured in Lua and provides a good range of features.
It aims to be easy to use for anyone but powerful enough for
those who need it.
The motivation for choosing Lua was that its simpler and better to use 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, 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 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! and aims to be infinitely configurable. If something isn't, open an issue!
# Table of Contents
- [Screenshots](#Screenshots)
- [Getting Hilbish](#Getting-Hilbish)
- [Contributing](#Contributing)
# Screenshots # Screenshots
<div align="center"> <div align="center">
<img src="gallery/terminal.png"><br><br> <img src="gallery/tab.png">
<img src="gallery/tab.png"><br><br>
<img src="gallery/pillprompt.png"> <img src="gallery/pillprompt.png">
</div> </div>
# Getting Hilbish # Getting Hilbish
**NOTE:** Hilbish is not guaranteed to work properly on Windows, starting **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 from the 2.0 version. It will still be able to compile, but functionality
may be lacking. may be lacking. If you want to contribute to make the situation better,
comment on the Windows discussion.
You can check the [install page](https://rosettea.github.io/Hilbish/install/) You can check the [install page](https://rosettea.github.io/Hilbish/install/)
on the website for distributed binaries from GitHub or other package repositories. on the website for distributed binaries from GitHub or other package repositories.

View File

@ -13,12 +13,22 @@ vars:
tasks: tasks:
default: default:
cmds:
- go build {{.GOFLAGS}}
vars:
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)"'
default-nocgo:
cmds: cmds:
- CGO_ENABLED=0 go build {{.GOFLAGS}} - CGO_ENABLED=0 go build {{.GOFLAGS}}
vars: vars:
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)"' 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: build:
cmds:
- go build {{.GOFLAGS}}
build-nocgo:
cmds: cmds:
- CGO_ENABLED=0 go build {{.GOFLAGS}} - CGO_ENABLED=0 go build {{.GOFLAGS}}
@ -34,4 +44,4 @@ tasks:
- rm -vrf - rm -vrf
"{{.DESTDIR}}{{.BINDIR}}/hilbish" "{{.DESTDIR}}{{.BINDIR}}/hilbish"
"{{.DESTDIR}}{{.LIBDIR}}" "{{.DESTDIR}}{{.LIBDIR}}"
- sed -i '/hilbish/d' /etc/shells - grep -v 'hilbish' /etc/shells > /tmp/shells.hilbish_uninstall && mv /tmp/shells.hilbish_uninstall /etc/shells

View File

@ -1,6 +1,8 @@
package main package main
import ( import (
"regexp"
"strconv"
"strings" "strings"
"sync" "sync"
@ -46,9 +48,32 @@ func (a *aliasModule) Resolve(cmdstr string) string {
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() defer a.mu.RUnlock()
args := strings.Split(cmdstr, " ") arg, _ := regexp.Compile(`[\\]?%\d+`)
args, _ := splitInput(cmdstr)
if len(args) == 0 {
// this shouldnt reach but...????
return cmdstr
}
for a.aliases[args[0]] != "" { for a.aliases[args[0]] != "" {
alias := a.aliases[args[0]] alias := a.aliases[args[0]]
alias = arg.ReplaceAllStringFunc(alias, func(a string) string {
idx, _ := strconv.Atoi(a[1:])
if strings.HasPrefix(a, "\\") || idx == 0 {
return strings.TrimPrefix(a, "\\")
}
if idx + 1 > len(args) {
return a
}
val := args[idx]
args = cut(args, idx)
cmdstr = strings.Join(args, " ")
return val
})
cmdstr = alias + strings.TrimPrefix(cmdstr, args[0]) cmdstr = alias + strings.TrimPrefix(cmdstr, args[0])
cmdArgs, _ := splitInput(cmdstr) cmdArgs, _ := splitInput(cmdstr)
args = cmdArgs args = cmdArgs
@ -86,15 +111,23 @@ func (a *aliasModule) Loader(rtm *rt.Runtime) *rt.Table {
// #interface aliases // #interface aliases
// add(alias, cmd) // add(alias, cmd)
// This is an alias (ha) for the `hilbish.alias` function. // This is an alias (ha) for the [hilbish.alias](../#alias) function.
// --- @param alias string // --- @param alias string
// --- @param cmd string // --- @param cmd string
func _hlalias() {} func _hlalias() {}
// #interface aliases // #interface aliases
// list() -> table<string, string> // list() -> table[string, string]
// Get a table of all aliases, with string keys as the alias and the value as the command. // Get a table of all aliases, with string keys as the alias and the value as the command.
// --- @returns table<string, string> // #returns table[string, string]
/*
#example
hilbish.aliases.add('hi', 'echo hi')
local aliases = hilbish.aliases.list()
-- -> {hi = 'echo hi'}
#example
*/
func (a *aliasModule) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (a *aliasModule) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
aliasesList := rt.NewTable() aliasesList := rt.NewTable()
for k, v := range a.All() { for k, v := range a.All() {
@ -107,7 +140,7 @@ func (a *aliasModule) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface aliases // #interface aliases
// delete(name) // delete(name)
// Removes an alias. // Removes an alias.
// --- @param name string // #param name string
func (a *aliasModule) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (a *aliasModule) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -122,10 +155,10 @@ func (a *aliasModule) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// #interface aliases // #interface aliases
// resolve(alias) -> command (string) // resolve(alias) -> string?
// Tries to resolve an alias to its command. // Resolves an alias to its original command. Will thrown an error if the alias doesn't exist.
// --- @param alias string // #param alias string
// --- @returns string // #returns string
func (a *aliasModule) luaResolve(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (a *aliasModule) luaResolve(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err

414
api.go
View File

@ -2,19 +2,21 @@
// The Hilbish module includes the core API, containing // The Hilbish module includes the core API, containing
// interfaces and functions which directly relate to shell functionality. // interfaces and functions which directly relate to shell functionality.
// #field ver The version of Hilbish // #field ver The version of Hilbish
// #field goVersion The version of Go that Hilbish was compiled with
// #field user Username of the user // #field user Username of the user
// #field host Hostname of the machine // #field host Hostname of the machine
// #field dataDir Directory for Hilbish data files, including the docs and default modules // #field dataDir Directory for Hilbish data files, including the docs and default modules
// #field interactive Is Hilbish in an interactive shell? // #field interactive Is Hilbish in an interactive shell?
// #field login Is Hilbish the login 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 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 // #field exitCode Exit code of the last executed command
package main package main
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@ -26,6 +28,7 @@ import (
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib" "github.com/arnodel/golua/lib/packagelib"
"github.com/arnodel/golua/lib/iolib"
"github.com/maxlandon/readline" "github.com/maxlandon/readline"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
) )
@ -58,47 +61,8 @@ var hilbishLoader = packagelib.Loader{
} }
func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) { func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
fakeMod := rt.NewTable()
modmt := rt.NewTable()
mod := rt.NewTable() mod := rt.NewTable()
modIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
arg := c.Arg(1)
val := mod.Get(arg)
return c.PushingNext1(t.Runtime, val), nil
}
modNewIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
k, err := c.StringArg(1)
if err != nil {
return nil, err
}
v := c.Arg(2)
if k == "highlighter" {
var err error
// fine to assign, since itll be either nil or a closure
highlighter, err = c.ClosureArg(2)
if err != nil {
return nil, errors.New("hilbish.highlighter has to be a function")
}
} else if k == "hinter" {
var err error
hinter, err = c.ClosureArg(2)
if err != nil {
return nil, errors.New("hilbish.hinter has to be a function")
}
} else if modVal := mod.Get(rt.StringValue(k)); modVal != rt.NilValue {
return nil, errors.New("not allowed to override in hilbish table")
}
mod.Set(rt.StringValue(k), v)
return c.Next(), nil
}
modmt.Set(rt.StringValue("__newindex"), rt.FunctionValue(rt.NewGoFunction(modNewIndex, "__newindex", 3, false)))
modmt.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(modIndex, "__index", 2, false)))
fakeMod.SetMetatable(modmt)
util.SetExports(rtm, mod, exports) util.SetExports(rtm, mod, exports)
hshMod = mod hshMod = mod
@ -109,15 +73,16 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows
} }
util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(getVersion())) util.SetField(rtm, mod, "ver", rt.StringValue(getVersion()))
util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username)) util.SetField(rtm, mod, "goVersion", rt.StringValue(runtime.Version()))
util.SetFieldProtected(fakeMod, mod, "host", rt.StringValue(host)) util.SetField(rtm, mod, "user", rt.StringValue(username))
util.SetFieldProtected(fakeMod, mod, "home", rt.StringValue(curuser.HomeDir)) util.SetField(rtm, mod, "host", rt.StringValue(host))
util.SetFieldProtected(fakeMod, mod, "dataDir", rt.StringValue(dataDir)) util.SetField(rtm, mod, "home", rt.StringValue(curuser.HomeDir))
util.SetFieldProtected(fakeMod, mod, "interactive", rt.BoolValue(interactive)) util.SetField(rtm, mod, "dataDir", rt.StringValue(dataDir))
util.SetFieldProtected(fakeMod, mod, "login", rt.BoolValue(login)) util.SetField(rtm, mod, "interactive", rt.BoolValue(interactive))
util.SetFieldProtected(fakeMod, mod, "vimMode", rt.NilValue) util.SetField(rtm, mod, "login", rt.BoolValue(login))
util.SetFieldProtected(fakeMod, mod, "exitCode", rt.IntValue(0)) util.SetField(rtm, mod, "vimMode", rt.NilValue)
util.SetField(rtm, mod, "exitCode", rt.IntValue(0))
// hilbish.userDir table // hilbish.userDir table
hshuser := userDirLoader(rtm) hshuser := userDirLoader(rtm)
@ -138,7 +103,9 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
// hilbish.completion table // hilbish.completion table
hshcomp := completionLoader(rtm) hshcomp := completionLoader(rtm)
// TODO: REMOVE "completion" AND ONLY USE "completions" WITH AN S
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp)) mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
mod.Set(rt.StringValue("completions"), rt.TableValue(hshcomp))
// hilbish.runner table // hilbish.runner table
runnerModule := runnerModeLoader(rtm) runnerModule := runnerModeLoader(rtm)
@ -164,7 +131,10 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
util.SetField(rtm, versionModule, "release", rt.StringValue(releaseName)) util.SetField(rtm, versionModule, "release", rt.StringValue(releaseName))
mod.Set(rt.StringValue("version"), rt.TableValue(versionModule)) mod.Set(rt.StringValue("version"), rt.TableValue(versionModule))
return rt.TableValue(fakeMod), nil pluginModule := moduleLoader(rtm)
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
return rt.TableValue(mod), nil
} }
func getenv(key, fallback string) string { func getenv(key, fallback string) string {
@ -184,14 +154,64 @@ func unsetVimMode() {
util.SetField(l, hshMod, "vimMode", rt.NilValue) util.SetField(l, hshMod, "vimMode", rt.NilValue)
} }
// run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string) func handleStream(v rt.Value, strms *streams, errStream bool) error {
// Runs `cmd` in Hilbish's sh interpreter. ud, ok := v.TryUserData()
// If returnOut is true, the outputs of `cmd` will be returned as the 2nd and if !ok {
// 3rd values instead of being outputted to the terminal. return errors.New("expected metatable argument")
// --- @param cmd string }
// --- @param returnOut boolean
// --- @returns number, string, string val := ud.Value()
var varstrm io.Writer
if f, ok := val.(*iolib.File); ok {
varstrm = f.Handle()
}
if f, ok := val.(*sink); ok {
varstrm = f.writer
}
if varstrm == nil {
return errors.New("expected either a sink or file")
}
if errStream {
strms.stderr = varstrm
} else {
strms.stdout = varstrm
}
return nil
}
// run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
// Runs `cmd` in Hilbish's shell script interpreter.
// The `streams` parameter specifies the output and input streams the command should use.
// For example, to write command output to a sink.
// As a table, the caller can directly specify the standard output, error, and input
// streams of the command with the table keys `out`, `err`, and `input` respectively.
// As a boolean, it specifies whether the command should use standard output or return its output streams.
// #param cmd string
// #param streams table|boolean
// #returns number, string, string
// #example
/*
// This code is the same as `ls -l | wc -l`
local fs = require 'fs'
local pr, pw = fs.pipe()
hilbish.run('ls -l', {
stdout = pw,
stderr = pw,
})
pw:close()
hilbish.run('wc -l', {
stdin = pr
})
*/
// #example
func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// TODO: ON BREAKING RELEASE, DO NOT ACCEPT `streams` AS A BOOLEAN.
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
} }
@ -200,20 +220,57 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
strms := &streams{}
var terminalOut bool var terminalOut bool
if len(c.Etc()) != 0 { if len(c.Etc()) != 0 {
tout := c.Etc()[0] tout := c.Etc()[0]
termOut, ok := tout.TryBool()
terminalOut = termOut var ok bool
terminalOut, ok = tout.TryBool()
if !ok { if !ok {
return nil, errors.New("bad argument to run (expected boolean, got " + tout.TypeName() + ")") luastreams, ok := tout.TryTable()
if !ok {
return nil, errors.New("bad argument to run (expected boolean or table, got " + tout.TypeName() + ")")
}
handleStream(luastreams.Get(rt.StringValue("out")), strms, false)
handleStream(luastreams.Get(rt.StringValue("err")), strms, true)
stdinstrm := luastreams.Get(rt.StringValue("input"))
if !stdinstrm.IsNil() {
ud, ok := stdinstrm.TryUserData()
if !ok {
return nil, errors.New("bad type as run stdin stream (expected userdata as either sink or file, got " + stdinstrm.TypeName() + ")")
}
val := ud.Value()
var varstrm io.Reader
if f, ok := val.(*iolib.File); ok {
varstrm = f.Handle()
}
if f, ok := val.(*sink); ok {
varstrm = f.reader
}
if varstrm == nil {
return nil, errors.New("bad type as run stdin stream (expected userdata as either sink or file)")
}
strms.stdin = varstrm
}
} else {
if !terminalOut {
strms = &streams{
stdout: new(bytes.Buffer),
stderr: new(bytes.Buffer),
}
}
} }
} else {
terminalOut = true
} }
var exitcode uint8 var exitcode uint8
stdout, stderr, err := execCommand(cmd, terminalOut) stdout, stderr, err := execCommand(cmd, strms)
if code, ok := interp.IsExitStatus(err); ok { if code, ok := interp.IsExitStatus(err); ok {
exitcode = code exitcode = code
@ -221,19 +278,20 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
exitcode = 1 exitcode = 1
} }
stdoutStr := "" var stdoutStr, stderrStr string
stderrStr := "" if stdoutBuf, ok := stdout.(*bytes.Buffer); ok {
if !terminalOut { stdoutStr = stdoutBuf.String()
stdoutStr = stdout.(*bytes.Buffer).String() }
stderrStr = stderr.(*bytes.Buffer).String() if stderrBuf, ok := stderr.(*bytes.Buffer); ok {
stderrStr = stderrBuf.String()
} }
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
} }
// cwd() -> string // cwd() -> string
// Returns the current directory of the shell // Returns the current directory of the shell.
// --- @returns string // #returns string
func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
cwd, _ := os.Getwd() cwd, _ := os.Getwd()
@ -244,9 +302,9 @@ func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// read(prompt) -> input (string) // read(prompt) -> input (string)
// Read input from the user, using Hilbish's line editor/input reader. // Read input from the user, using Hilbish's line editor/input reader.
// This is a separate instance from the one Hilbish actually uses. // 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) // Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
// --- @param prompt? string // #param prompt? string Text to print before input, can be empty.
// --- @returns string|nil // #returns string|nil
func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
luaprompt := c.Arg(0) luaprompt := c.Arg(0)
if typ := luaprompt.Type(); typ != rt.StringType && typ != rt.NilType { if typ := luaprompt.Type(); typ != rt.StringType && typ != rt.NilType {
@ -274,14 +332,21 @@ func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
/* /*
prompt(str, typ) prompt(str, typ)
Changes the shell prompt to `str` Changes the shell prompt to the provided string.
There are a few verbs that can be used in the prompt text. There are a few verbs that can be used in the prompt text.
These will be formatted and replaced with the appropriate values. These will be formatted and replaced with the appropriate values.
`%d` - Current working directory `%d` - Current working directory
`%u` - Name of current user `%u` - Name of current user
`%h` - Hostname of device `%h` - Hostname of device
--- @param str string #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.
#example
-- the default hilbish prompt without color
hilbish.prompt '%u %d '
-- or something of old:
hilbish.prompt '%u@%h :%d $'
-- prompt: user@hostname: ~/directory $
#example
*/ */
func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := c.Check1Arg() err := c.Check1Arg()
@ -315,8 +380,28 @@ func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// multiprompt(str) // multiprompt(str)
// Changes the continued line prompt to `str` // Changes the text prompt when Hilbish asks for more input.
// --- @param str string // This will show up when text is incomplete, like a missing quote
// #param str string
/*
#example
--[[
imagine this is your text input:
user ~ echo "hey
but there's a missing quote! hilbish will now prompt you so the terminal
will look like:
user ~ echo "hey
--> ...!"
so then you get
user ~ echo "hey
--> ...!"
hey ...!
]]--
hilbish.multiprompt '-->'
#example
*/
func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -331,9 +416,19 @@ func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// alias(cmd, orig) // alias(cmd, orig)
// Sets an alias of `cmd` to `orig` // Sets an alias, with a name of `cmd` to another command.
// --- @param cmd string // #param cmd string Name of the alias
// --- @param orig string // #param orig string Command that will be aliased
/*
#example
-- With this, "ga file" will turn into "git add file"
hilbish.alias('ga', 'git add')
-- Numbered substitutions are supported here!
hilbish.alias('dircount', 'ls %1 | wc -l')
-- "dircount ~" would count how many files are in ~ (home directory).
#example
*/
func hlalias(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlalias(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil { if err := c.CheckNArgs(2); err != nil {
return nil, err return nil, err
@ -353,8 +448,20 @@ func hlalias(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// appendPath(dir) // appendPath(dir)
// Appends `dir` to $PATH // Appends the provided dir to the command path (`$PATH`)
// --- @param dir string|table // #param dir string|table Directory (or directories) to append to path
/*
#example
hilbish.appendPath '~/go/bin'
-- Will add ~/go/bin to the command path.
-- Or do multiple:
hilbish.appendPath {
'~/go/bin',
'~/.local/bin'
}
#example
*/
func hlappendPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlappendPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -388,8 +495,9 @@ func appendPath(dir string) {
} }
// exec(cmd) // exec(cmd)
// Replaces running hilbish with `cmd` // Replaces the currently running Hilbish instance with the supplied command.
// --- @param cmd string // This can be used to do an in-place restart.
// #param cmd string
func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -423,8 +531,11 @@ func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// goro(fn) // goro(fn)
// Puts `fn` in a goroutine // Puts `fn` in a Goroutine.
// --- @param fn function // This can be used to run any function in another thread at the same time as other Lua code.
// **NOTE: THIS FUNCTION MAY CRASH HILBISH IF OUTSIDE VARIABLES ARE ACCESSED.**
// **This is a limitation of the Lua runtime.**
// #param fn function
func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -436,6 +547,12 @@ func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// call fn // call fn
go func() { go func() {
defer func() {
if r := recover(); r != nil {
// do something here?
}
}()
_, err := rt.Call1(l.MainThread(), rt.FunctionValue(fn), c.Etc()...) _, err := rt.Call1(l.MainThread(), rt.FunctionValue(fn), c.Etc()...)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "Error in goro function:\n\n", err) fmt.Fprintln(os.Stderr, "Error in goro function:\n\n", err)
@ -446,11 +563,11 @@ func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// timeout(cb, time) -> @Timer // timeout(cb, time) -> @Timer
// Runs the `cb` function after `time` in milliseconds. // Executed the `cb` function after a period of `time`.
// This creates a timer that starts immediately. // This creates a Timer that starts ticking immediately.
// --- @param cb function // #param cb function
// --- @param time number // #param time number Time to run in milliseconds.
// --- @returns Timer // #returns Timer
func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil { if err := c.CheckNArgs(2); err != nil {
return nil, err return nil, err
@ -472,11 +589,11 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// interval(cb, time) -> @Timer // interval(cb, time) -> @Timer
// Runs the `cb` function every `time` milliseconds. // Runs the `cb` function every specified amount of `time`.
// This creates a timer that starts immediately. // This creates a timer that ticking immediately.
// --- @param cb function // #param cb function
// --- @param time number // #param time number Time in milliseconds.
// --- @return Timer // #return Timer
func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil { if err := c.CheckNArgs(2); err != nil {
return nil, err return nil, err
@ -498,13 +615,40 @@ func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// complete(scope, cb) // complete(scope, cb)
// Registers a completion handler for `scope`. // Registers a completion handler for the specified scope.
// A `scope` is currently only expected to be `command.<cmd>`, // A `scope` is expected to be `command.<cmd>`,
// replacing <cmd> with the name of the command (for example `command.git`). // replacing <cmd> with the name of the command (for example `command.git`).
// `cb` must be a function that returns a table of "completion groups." // The documentation for completions, under Features/Completions or `doc completions`
// Check `doc completions` for more information. // provides more details.
// --- @param scope string // #param scope string
// --- @param cb function // #param cb function
/*
#example
-- This is a very simple example. Read the full doc for completions for details.
hilbish.complete('command.sudo', function(query, ctx, fields)
if #fields == 0 then
-- complete for commands
local comps, pfx = hilbish.completion.bins(query, ctx, fields)
local compGroup = {
items = comps, -- our list of items to complete
type = 'grid' -- what our completions will look like.
}
return {compGroup}, pfx
end
-- otherwise just be boring and return files
local comps, pfx = hilbish.completion.files(query, ctx, fields)
local compGroup = {
items = comps,
type = 'grid'
}
return {compGroup}, pfx
end)
#example
*/
func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
scope, cb, err := util.HandleStrCallback(t, c) scope, cb, err := util.HandleStrCallback(t, c)
if err != nil { if err != nil {
@ -516,8 +660,8 @@ func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// prependPath(dir) // prependPath(dir)
// Prepends `dir` to $PATH // Prepends `dir` to $PATH.
// --- @param dir string // #param dir string
func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -540,8 +684,8 @@ func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// which(name) -> string // which(name) -> string
// Checks if `name` is a valid command. // Checks if `name` is a valid command.
// Will return the path of the binary, or a basename if it's a commander. // Will return the path of the binary, or a basename if it's a commander.
// --- @param name string // #param name string
// --- @returns string // #returns string
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -557,7 +701,7 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
cmd := strings.Split(alias, " ")[0] cmd := strings.Split(alias, " ")[0]
// check for commander // check for commander
if commands[cmd] != nil { if cmds.Commands[cmd] != nil {
// they dont resolve to a path, so just send the cmd // they dont resolve to a path, so just send the cmd
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
} }
@ -571,8 +715,10 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// inputMode(mode) // inputMode(mode)
// Sets the input mode for Hilbish's line reader. Accepts either emacs or vim // Sets the input mode for Hilbish's line reader.
// --- @param mode string // `emacs` is the default. Setting it to `vim` changes behavior of input to be
// Vim-like with modes and Vim keybinds.
// #param mode string Can be set to either `emacs` or `vim`
func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -597,12 +743,14 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// runnerMode(mode) // runnerMode(mode)
// Sets the execution/runner mode for interactive Hilbish. This determines whether // Sets the execution/runner mode for interactive Hilbish.
// Hilbish wll try to run input as Lua and/or sh or only do one of either. // 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), // 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 // sh, and lua. It also accepts a function, to which if it is passed one
// will call it to execute user input instead. // will call it to execute user input instead.
// --- @param mode string|function // Read [about runner mode](../features/runner-mode) for more information.
// #param mode string|function
func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -628,18 +776,42 @@ func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// line and cursor position. It is expected to return a string which is used // 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, // as the text for the hint. This is by default a shim. To set hints,
// override this function with your custom handler. // override this function with your custom handler.
// --- @param line string // #param line string
// --- @param pos number // #param pos number Position of cursor in line. Usually equals string.len(line)
/*
#example
-- this will display "hi" after the cursor in a dimmed color.
function hilbish.hinter(line, pos)
return 'hi'
end
#example
*/
func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
// highlighter(line) // highlighter(line)
// Line highlighter handler. This is mainly for syntax highlighting, but in // Line highlighter handler.
// reality could set the input of the prompt to *display* anything. The // This is mainly for syntax highlighting, but in reality could set the input
// callback is passed the current line and is expected to return a line that // of the prompt to *display* anything. The callback is passed the current line
// will be used as the input display. // and is expected to return a line that will be used as the input display.
// --- @param line string // Note that to set a highlighter, one has to override this function.
// #example
// --This code will highlight all double quoted strings in green.
// function hilbish.highlighter(line)
// return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
// end
// #example
// #param line string
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil if err := c.Check1Arg(); err != nil {
return nil, err
}
line, err := c.StringArg(0)
if err != nil {
return nil, err
}
return c.PushingNext1(t.Runtime, rt.StringValue(line)), nil
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -11,6 +11,8 @@ import (
"strings" "strings"
"os" "os"
"sync" "sync"
md "github.com/atsushinee/go-markdown-generator/doc"
) )
var header = `--- var header = `---
@ -43,6 +45,12 @@ type module struct {
HasTypes bool HasTypes bool
} }
type param struct{
Name string
Type string
Doc []string
}
type docPiece struct { type docPiece struct {
Doc []string Doc []string
FuncSig string FuncSig string
@ -55,11 +63,14 @@ type docPiece struct {
IsType bool IsType bool
Fields []docPiece Fields []docPiece
Properties []docPiece Properties []docPiece
Params []param
Tags map[string][]tag
} }
type tag struct { type tag struct {
id string id string
fields []string fields []string
startIdx int
} }
var docs = make(map[string]module) var docs = make(map[string]module)
@ -80,7 +91,7 @@ func getTagsAndDocs(docs string) (map[string][]tag, []string) {
parts := []string{} parts := []string{}
tags := make(map[string][]tag) tags := make(map[string][]tag)
for _, part := range pts { for idx, part := range pts {
if strings.HasPrefix(part, "#") { if strings.HasPrefix(part, "#") {
tagParts := strings.Split(strings.TrimPrefix(part, "#"), " ") tagParts := strings.Split(strings.TrimPrefix(part, "#"), " ")
if tags[tagParts[0]] == nil { if tags[tagParts[0]] == nil {
@ -89,12 +100,21 @@ func getTagsAndDocs(docs string) (map[string][]tag, []string) {
id = tagParts[1] id = tagParts[1]
} }
tags[tagParts[0]] = []tag{ tags[tagParts[0]] = []tag{
{id: id}, {id: id, startIdx: idx},
} }
if len(tagParts) >= 2 { if len(tagParts) >= 2 {
tags[tagParts[0]][0].fields = tagParts[2:] tags[tagParts[0]][0].fields = tagParts[2:]
} }
} else { } else {
if tagParts[0] == "example" {
exampleIdx := tags["example"][0].startIdx
exampleCode := pts[exampleIdx+1:idx]
tags["example"][0].fields = exampleCode
parts = strings.Split(strings.Replace(strings.Join(parts, "\n"), strings.TrimPrefix(strings.Join(exampleCode, "\n"), "#example\n"), "", -1), "\n")
continue
}
fleds := []string{} fleds := []string{}
if len(tagParts) >= 2 { if len(tagParts) >= 2 {
fleds = tagParts[2:] fleds = tagParts[2:]
@ -179,6 +199,7 @@ func setupDocType(mod string, typ *doc.Type) *docPiece {
ParentModule: parentMod, ParentModule: parentMod,
Fields: fields, Fields: fields,
Properties: properties, Properties: properties,
Tags: tags,
} }
typeTable[strings.ToLower(typeName)] = []string{parentMod, interfaces} typeTable[strings.ToLower(typeName)] = []string{parentMod, interfaces}
@ -215,6 +236,17 @@ start:
fields := docPieceTag("field", tags) fields := docPieceTag("field", tags)
properties := docPieceTag("property", tags) properties := docPieceTag("property", tags)
var params []param
if paramsRaw := tags["param"]; paramsRaw != nil {
params = make([]param, len(paramsRaw))
for i, p := range paramsRaw {
params[i] = param{
Name: p.id,
Type: p.fields[0],
Doc: p.fields[1:],
}
}
}
for _, d := range doc { for _, d := range doc {
if strings.HasPrefix(d, "---") { if strings.HasPrefix(d, "---") {
@ -252,6 +284,8 @@ start:
ParentModule: parentMod, ParentModule: parentMod,
Fields: fields, Fields: fields,
Properties: properties, Properties: properties,
Params: params,
Tags: tags,
} }
if strings.HasSuffix(dps.GoFuncName, strings.ToLower("loader")) { if strings.HasSuffix(dps.GoFuncName, strings.ToLower("loader")) {
dps.Doc = parts dps.Doc = parts
@ -412,13 +446,14 @@ func main() {
defer wg.Done() defer wg.Done()
modOrIface := "Module" modOrIface := "Module"
if modu.ParentModule != "" { if modu.ParentModule != "" {
modOrIface = "Interface" modOrIface = "Module"
} }
lastHeader := ""
f, _ := os.Create(docPath) f, _ := os.Create(docPath)
f.WriteString(fmt.Sprintf(header, modOrIface, modname, modu.ShortDescription)) f.WriteString(fmt.Sprintf(header, modOrIface, modname, modu.ShortDescription))
typeTag, _ := regexp.Compile(`@\w+`) typeTag, _ := regexp.Compile(`\B@\w+`)
modDescription := typeTag.ReplaceAllStringFunc(strings.Replace(modu.Description, "<", `\<`, -1), func(typ string) string { modDescription := typeTag.ReplaceAllStringFunc(strings.Replace(strings.Replace(modu.Description, "<", `\<`, -1), "{{\\<", "{{<", -1), func(typ string) string {
typName := typ[1:] typName := typ[1:]
typLookup := typeTable[strings.ToLower(typName)] typLookup := typeTable[strings.ToLower(typName)]
ifaces := typLookup[0] + "." + typLookup[1] + "/" ifaces := typLookup[0] + "." + typLookup[1] + "/"
@ -429,32 +464,81 @@ func main() {
return fmt.Sprintf(`<a href="%s" style="text-decoration: none;">%s</a>`, linkedTyp, typName) return fmt.Sprintf(`<a href="%s" style="text-decoration: none;">%s</a>`, linkedTyp, typName)
}) })
f.WriteString(fmt.Sprintf("## Introduction\n%s\n\n", modDescription)) 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 { if len(modu.Docs) != 0 {
f.WriteString("## Functions\n") funcCount := 0
for _, dps := range modu.Docs { for _, dps := range modu.Docs {
if dps.IsMember { if dps.IsMember {
continue continue
} }
htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(dps.FuncSig, "<", `\<`, -1), func(typ string) string { funcCount++
}
f.WriteString("## Functions\n")
lastHeader = "functions"
mdTable := md.NewTable(funcCount, 2)
mdTable.SetTitle(0, "")
mdTable.SetTitle(1, "")
diff := 0
for i, dps := range modu.Docs {
if dps.IsMember {
diff++
continue
}
mdTable.SetContent(i - diff, 0, fmt.Sprintf(`<a href="#%s">%s</a>`, dps.FuncName, dps.FuncSig))
if len(dps.Doc) == 0 {
fmt.Printf("WARNING! Function %s on module %s has no documentation!\n", dps.FuncName, modname)
} else {
mdTable.SetContent(i - diff, 1, dps.Doc[0])
}
}
f.WriteString(mdTable.String())
f.WriteString("\n")
}
if len(modu.Fields) != 0 {
f.WriteString("## Static module fields\n")
mdTable := md.NewTable(len(modu.Fields), 2)
mdTable.SetTitle(0, "")
mdTable.SetTitle(1, "")
for i, dps := range modu.Fields {
mdTable.SetContent(i, 0, dps.FuncName)
mdTable.SetContent(i, 1, strings.Join(dps.Doc, " "))
}
f.WriteString(mdTable.String())
f.WriteString("\n")
}
if len(modu.Properties) != 0 {
f.WriteString("## Object properties\n")
mdTable := md.NewTable(len(modu.Fields), 2)
mdTable.SetTitle(0, "")
mdTable.SetTitle(1, "")
for i, dps := range modu.Properties {
mdTable.SetContent(i, 0, dps.FuncName)
mdTable.SetContent(i, 1, strings.Join(dps.Doc, " "))
}
f.WriteString(mdTable.String())
f.WriteString("\n")
}
if len(modu.Docs) != 0 {
if lastHeader != "functions" {
f.WriteString("## Functions\n")
}
for _, dps := range modu.Docs {
if dps.IsMember {
continue
}
f.WriteString(fmt.Sprintf("<hr>\n<div id='%s'>", dps.FuncName))
htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(modname + "." + dps.FuncSig, "<", `\<`, -1), func(typ string) string {
typName := typ[1:] typName := typ[1:]
typLookup := typeTable[strings.ToLower(typName)] typLookup := typeTable[strings.ToLower(typName)]
ifaces := typLookup[0] + "." + typLookup[1] + "/" ifaces := typLookup[0] + "." + typLookup[1] + "/"
@ -462,21 +546,55 @@ func main() {
ifaces = "" ifaces = ""
} }
linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s#%s", typLookup[0], ifaces, strings.ToLower(typName)) linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s#%s", typLookup[0], ifaces, strings.ToLower(typName))
return fmt.Sprintf(`<a href="%s" style="text-decoration: none;">%s</a>`, linkedTyp, typName) return fmt.Sprintf(`<a href="%s" style="text-decoration: none;" id="lol">%s</a>`, linkedTyp, typName)
}) })
f.WriteString(fmt.Sprintf("### %s\n", htmlSig)) f.WriteString(fmt.Sprintf(`
<h4 class='heading'>
%s
<a href="#%s" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
`, htmlSig, dps.FuncName))
for _, doc := range dps.Doc { for _, doc := range dps.Doc {
if !strings.HasPrefix(doc, "---") { if !strings.HasPrefix(doc, "---") && doc != "" {
f.WriteString(doc + "\n") f.WriteString(doc + " \n")
} }
} }
f.WriteString("\n") f.WriteString("\n#### Parameters\n")
if len(dps.Params) == 0 {
f.WriteString("This function has no parameters. \n")
}
for _, p := range dps.Params {
isVariadic := false
typ := p.Type
if strings.HasPrefix(p.Type, "...") {
isVariadic = true
typ = p.Type[3:]
}
f.WriteString(fmt.Sprintf("`%s` **`%s`**", typ, p.Name))
if isVariadic {
f.WriteString(" (This type is variadic. You can pass an infinite amount of parameters with this type.)")
}
f.WriteString(" \n")
f.WriteString(strings.Join(p.Doc, " "))
f.WriteString("\n\n")
}
if codeExample := dps.Tags["example"]; codeExample != nil {
f.WriteString("#### Example\n")
f.WriteString(fmt.Sprintf("```lua\n%s\n```\n", strings.Join(codeExample[0].fields, "\n")))
}
f.WriteString("</div>")
f.WriteString("\n\n")
} }
} }
if len(modu.Types) != 0 { if len(modu.Types) != 0 {
f.WriteString("## Types\n") f.WriteString("## Types\n")
for _, dps := range modu.Types { for _, dps := range modu.Types {
f.WriteString("<hr>\n\n")
f.WriteString(fmt.Sprintf("## %s\n", dps.FuncName)) f.WriteString(fmt.Sprintf("## %s\n", dps.FuncName))
for _, doc := range dps.Doc { for _, doc := range dps.Doc {
if !strings.HasPrefix(doc, "---") { if !strings.HasPrefix(doc, "---") {
@ -484,12 +602,18 @@ func main() {
} }
} }
if len(dps.Properties) != 0 { if len(dps.Properties) != 0 {
f.WriteString("### Properties\n") f.WriteString("## Object properties\n")
for _, dps := range dps.Properties {
f.WriteString(fmt.Sprintf("- `%s`: ", dps.FuncName)) mdTable := md.NewTable(len(dps.Properties), 2)
f.WriteString(strings.Join(dps.Doc, " ")) mdTable.SetTitle(0, "")
f.WriteString("\n") mdTable.SetTitle(1, "")
for i, d := range dps.Properties {
mdTable.SetContent(i, 0, d.FuncName)
mdTable.SetContent(i, 1, strings.Join(d.Doc, " "))
} }
f.WriteString(mdTable.String())
f.WriteString("\n")
} }
f.WriteString("\n") f.WriteString("\n")
f.WriteString("### Methods\n") f.WriteString("### Methods\n")

View File

@ -0,0 +1,146 @@
local fs = require 'fs'
local emmyPattern = '^%-%-%- (.+)'
local modpattern = '^%-+ @module (%w+)'
local pieces = {}
local files = fs.readdir 'nature'
for _, fname in ipairs(files) do
local isScript = fname:match'%.lua$'
if not isScript then goto continue end
local f = io.open(string.format('nature/%s', fname))
local header = f:read '*l'
local mod = header:match(modpattern)
if not mod then goto continue end
print(fname, mod)
pieces[mod] = {}
local docPiece = {}
local lines = {}
local lineno = 0
for line in f:lines() do
lineno = lineno + 1
lines[lineno] = line
if line == header then goto continue2 end
if not line:match(emmyPattern) then
if line:match '^function' then
local pattern = (string.format('^function %s%%.', mod) .. '(%w+)')
local funcName = line:match(pattern)
if not funcName then goto continue2 end
local dps = {
description = {},
params = {}
}
local offset = 1
while true do
local prev = lines[lineno - offset]
local docline = prev:match '^%-+ (.+)'
if docline then
local emmy = docline:match '@(%w+)'
local cut = 0
if emmy then cut = emmy:len() + 3 end
local emmythings = string.split(docline:sub(cut), ' ')
if emmy then
if emmy == 'param' then
table.insert(dps.params, 1, {
name = emmythings[1],
type = emmythings[2]
})
end
else
table.insert(dps.description, 1, docline)
end
offset = offset + 1
else
break
end
end
pieces[mod][funcName] = dps
end
docPiece = {}
goto continue2
end
table.insert(docPiece, line)
::continue2::
end
::continue::
end
local header = [[---
title: %s %s
description: %s
layout: doc
menu:
docs:
parent: "Nature"
---
]]
for iface, dps in pairs(pieces) do
local mod = iface:match '(%w+)%.' or 'nature'
local path = string.format('docs/%s/%s.md', mod, iface)
fs.mkdir(fs.dir(path), true)
local f <close> = io.open(path, 'w')
f:write(string.format(header, 'Module', iface, 'No description.'))
print(f)
print(mod, path)
for func, docs in pairs(dps) do
f:write(string.format('<hr>\n<div id=\'%s\'>', func))
local sig = string.format('%s.%s(', iface, func)
for idx, param in ipairs(docs.params) do
sig = sig .. ((param.name:gsub('%?$', '')))
if idx ~= #docs.params then sig = sig .. ', ' end
end
sig = sig .. ')'
f:write(string.format([[
<h4 class='heading'>
%s
<a href="#%s" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
]], sig, func))
f:write(table.concat(docs.description, '\n') .. '\n')
f:write '#### Parameters\n'
if #docs.params == 0 then
f:write 'This function has no parameters. \n'
end
for _, param in ipairs(docs.params) do
f:write(string.format('`%s` **`%s`**\n', param.name:gsub('%?$', ''), param.type))
end
--[[
local params = table.filter(docs, function(t)
return t:match '^%-%-%- @param'
end)
for i, str in ipairs(params) do
if i ~= 1 then
f:write ', '
end
f:write(str:match '^%-%-%- @param ([%w]+) ')
end
f:write(')\n')
for _, str in ipairs(docs) do
if not str:match '^%-%-%- @' then
f:write(str:match '^%-%-%- (.+)' .. '\n')
end
end
]]--
f:write('</div>')
f:write('\n\n')
end
end

View File

@ -128,7 +128,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
} }
// add lua registered commands to completions // add lua registered commands to completions
for cmdName := range commands { for cmdName := range cmds.Commands {
if strings.HasPrefix(cmdName, query) { if strings.HasPrefix(cmdName, query) {
completions = append(completions, cmdName) completions = append(completions, cmdName)
} }
@ -188,15 +188,15 @@ func escapeFilename(fname string) string {
return escapeReplaer.Replace(fname) return escapeReplaer.Replace(fname)
} }
// #interface completions // #interface completion
// tab completions // tab completions
// The completions interface deals with tab completions. // The completions interface deals with tab completions.
func completionLoader(rtm *rt.Runtime) *rt.Table { func completionLoader(rtm *rt.Runtime) *rt.Table {
exports := map[string]util.LuaExport{ exports := map[string]util.LuaExport{
"files": {luaFileComplete, 3, false}, "bins": {hcmpBins, 3, false},
"bins": {luaBinaryComplete, 3, false}, "call": {hcmpCall, 4, false},
"call": {callLuaCompleter, 4, false}, "files": {hcmpFiles, 3, false},
"handler": {completionHandler, 2, false}, "handler": {hcmpHandler, 2, false},
} }
mod := rt.NewTable() mod := rt.NewTable()
@ -205,27 +205,58 @@ func completionLoader(rtm *rt.Runtime) *rt.Table {
return mod return mod
} }
// #interface completions // #interface completion
// handler(line, pos) // bins(query, ctx, fields) -> entries (table), prefix (string)
// The handler function is the callback for tab completion in Hilbish. // Return binaries/executables based on the provided parameters.
// You can check the completions doc for more info. // This function is meant to be used as a helper in a command completion handler.
// --- @param line string // #param query string
// --- @param pos string // #param ctx string
func completionHandler(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { // #param fields table
return c.Next(), nil /*
#example
-- an extremely simple completer for sudo.
hilbish.complete('command.sudo', function(query, ctx, fields)
table.remove(fields, 1)
if #fields[1] then
-- return commands because sudo runs a command as root..!
local entries, pfx = hilbish.completion.bins(query, ctx, fields)
return {
type = 'grid',
items = entries
}, pfx
end
-- ... else suggest files or anything else ..
end)
#example
*/
func hcmpBins(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
query, ctx, fds, err := getCompleteParams(t, c)
if err != nil {
return nil, err
}
completions, pfx := binaryComplete(query, ctx, fds)
luaComps := rt.NewTable()
for i, comp := range completions {
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
}
return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil
} }
// #interface completions // #interface completion
// call(name, query, ctx, fields) -> completionGroups (table), prefix (string) // call(name, query, ctx, fields) -> completionGroups (table), prefix (string)
// Calls a completer function. This is mainly used to call // Calls a completer function. This is mainly used to call a command completer, which will have a `name`
// a command completer, which will have a `name` in the form // in the form of `command.name`, example: `command.git`.
// of `command.name`, example: `command.git`. // You can check the Completions doc or `doc completions` for info on the `completionGroups` return value.
// You can check `doc completions` for info on the `completionGroups` return value. // #param name string
// --- @param name string // #param query string
// --- @param query string // #param ctx string
// --- @param ctx string // #param fields table
// --- @param fields table func hcmpCall(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(4); err != nil { if err := c.CheckNArgs(4); err != nil {
return nil, err return nil, err
} }
@ -253,24 +284,26 @@ func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// we must keep the holy 80 cols // we must keep the holy 80 cols
completerReturn, err := rt.Call1(l.MainThread(), cont := c.Next()
rt.FunctionValue(completecb), rt.StringValue(query), err = rt.Call(l.MainThread(), rt.FunctionValue(completecb),
rt.StringValue(ctx), rt.TableValue(fields)) []rt.Value{rt.StringValue(query), rt.StringValue(ctx), rt.TableValue(fields)},
cont)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.PushingNext1(t.Runtime, completerReturn), nil return cont, nil
} }
// #interface completions // #interface completion
// files(query, ctx, fields) -> entries (table), prefix (string) // files(query, ctx, fields) -> entries (table), prefix (string)
// Returns file completion candidates based on the provided query. // Returns file matches based on the provided parameters.
// --- @param query string // This function is meant to be used as a helper in a command completion handler.
// --- @param ctx string // #param query string
// --- @param fields table // #param ctx string
func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { // #param fields table
func hcmpFiles(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
query, ctx, fds, err := getCompleteParams(t, c) query, ctx, fds, err := getCompleteParams(t, c)
if err != nil { if err != nil {
return nil, err return nil, err
@ -286,28 +319,32 @@ func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil
} }
// #interface completions // #interface completion
// bins(query, ctx, fields) -> entries (table), prefix (string) // handler(line, pos)
// Returns binary/executale completion candidates based on the provided query. // This function contains the general completion handler for Hilbish. This function handles
// --- @param query string // completion of everything, which includes calling other command handlers, binaries, and files.
// --- @param ctx string // This function can be overriden to supply a custom handler. Note that alias resolution is required to be done in this function.
// --- @param fields table // #param line string The current Hilbish command line
func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { // #param pos number Numerical position of the cursor
query, ctx, fds, err := getCompleteParams(t, c) /*
if err != nil { #example
return nil, err -- stripped down version of the default implementation
} function hilbish.completion.handler(line, pos)
local query = fields[#fields]
completions, pfx := binaryComplete(query, ctx, fds) if #fields == 1 then
luaComps := rt.NewTable() -- call bins handler here
else
for i, comp := range completions { -- call command completer or files completer here
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp)) end
} end
#example
return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil */
func hcmpHandler(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
} }
func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, error) { func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, error) {
if err := c.CheckNArgs(3); err != nil { if err := c.CheckNArgs(3); err != nil {
return "", "", []string{}, err return "", "", []string{}, err

View File

@ -1,7 +1,7 @@
--- ---
title: API title: API
layout: doc layout: doc
weight: -50 weight: -100
menu: docs menu: docs
--- ---

View File

@ -8,27 +8,160 @@ menu:
--- ---
## Introduction ## 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 Bait is the event emitter for Hilbish. Much like Node.js and
you want to listen in on hooks to know when certain things have its `events` system, many actions in Hilbish emit events.
happened, like when you've changed directory, a command has failed, Unlike Node.js, Hilbish events are global. So make sure to
etc. To find all available hooks thrown by Hilbish, see doc hooks. pick a unique name!
Usage of the Bait module consists of userstanding
event-driven architecture, but it's pretty simple:
If you want to act on a certain event, you can `catch` it.
You can act on events via callback functions.
Examples of this are in the Hilbish default config!
Consider this part of it:
```lua
bait.catch('command.exit', function(code)
running = false
doPrompt(code ~= 0)
doNotifyPrompt()
end)
```
What this does is, whenever the `command.exit` event is thrown,
this function will set the user prompt.
## Functions ## Functions
### catch(name, cb) |||
Catches a hook with `name`. Runs the `cb` when it is thrown |----|----|
|<a href="#catch">catch(name, cb)</a>|Catches an event. This function can be used to act on events.|
|<a href="#catchOnce">catchOnce(name, cb)</a>|Catches an event, but only once. This will remove the hook immediately after it runs for the first time.|
|<a href="#hooks">hooks(name) -> table</a>|Returns a table of functions that are hooked on an event with the corresponding `name`.|
|<a href="#release">release(name, catcher)</a>|Removes the `catcher` for the event with `name`.|
|<a href="#throw">throw(name, ...args)</a>|Throws a hook with `name` with the provided `args`.|
### catchOnce(name, cb) <hr>
Same as catch, but only runs the `cb` once and then removes the hook <div id='catch'>
<h4 class='heading'>
bait.catch(name, cb)
<a href="#catch" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### hooks(name) -> table Catches an event. This function can be used to act on events.
Returns a table with hooks (callback functions) on the event with `name`.
### release(name, catcher) #### Parameters
Removes the `catcher` for the event with `name`. `string` **`name`**
For this to work, `catcher` has to be the same function used to catch The name of the hook.
an event, like one saved to a variable.
### throw(name, ...args) `function` **`cb`**
Throws a hook with `name` with the provided `args` The function that will be called when the hook is thrown.
#### Example
```lua
bait.catch('hilbish.exit', function()
print 'Goodbye Hilbish!'
end)
```
</div>
<hr>
<div id='catchOnce'>
<h4 class='heading'>
bait.catchOnce(name, cb)
<a href="#catchOnce" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Catches an event, but only once. This will remove the hook immediately after it runs for the first time.
#### Parameters
`string` **`name`**
The name of the event
`function` **`cb`**
The function that will be called when the event is thrown.
</div>
<hr>
<div id='hooks'>
<h4 class='heading'>
bait.hooks(name) -> table
<a href="#hooks" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns a table of functions that are hooked on an event with the corresponding `name`.
#### Parameters
`string` **`name`**
The name of the hook
</div>
<hr>
<div id='release'>
<h4 class='heading'>
bait.release(name, catcher)
<a href="#release" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
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.
#### Parameters
`string` **`name`**
Name of the event the hook is on
`function` **`catcher`**
Hook function to remove
#### Example
```lua
local hookCallback = function() print 'hi' end
bait.catch('event', hookCallback)
-- a little while later....
bait.release('event', hookCallback)
-- and now hookCallback will no longer be ran for the event.
```
</div>
<hr>
<div id='throw'>
<h4 class='heading'>
bait.throw(name, ...args)
<a href="#throw" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Throws a hook with `name` with the provided `args`.
#### Parameters
`string` **`name`**
The name of the hook.
`any` **`args`** (This type is variadic. You can pass an infinite amount of parameters with this type.)
The arguments to pass to the hook.
#### Example
```lua
bait.throw('greeting', 'world')
-- This can then be listened to via
bait.catch('gretting', function(greetTo)
print('Hello ' .. greetTo)
end)
```
</div>

View File

@ -9,11 +9,10 @@ menu:
## Introduction ## Introduction
Commander is a library for writing custom commands in Lua. Commander is the library which handles Hilbish commands. This makes
In order to make it easier to write commands for Hilbish, the user able to add Lua-written commands to their shell without making
not require separate scripts and to be able to use in a config, a separate script in a bin folder. Instead, you may simply use the Commander
the Commander library exists. This is like a very simple wrapper library in your Hilbish config.
that works with Hilbish for writing commands. Example:
```lua ```lua
local commander = require 'commander' local commander = require 'commander'
@ -27,20 +26,88 @@ In this example, a command with the name of `hello` is created
that will print `Hello world!` to output. One question you may that will print `Hello world!` to output. One question you may
have is: What is the `sinks` parameter? have is: What is the `sinks` parameter?
The `sinks` parameter is a table with 3 keys: `in`, `out`, The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
and `err`. The values of these is a <a href="/Hilbish/docs/api/hilbish/#sink" style="text-decoration: none;">Sink</a>. There is an `in` alias to `input`, but it requires using the string accessor syntax (`sinks['in']`)
as `in` is also a Lua keyword, so `input` is preferred for use.
All of them are a <a href="/Hilbish/docs/api/hilbish/#sink" style="text-decoration: none;">Sink</a>.
In the future, `sinks.in` will be removed.
- `in` is the standard input. You can read from this sink - `in` is the standard input.
to get user input. (**This is currently unimplemented.**) You may use the read functions on this sink to get input from the user.
- `out` is standard output. This is usually where text meant for - `out` is standard output.
output should go. This is usually where command output should go.
- `err` is standard error. This sink is for writing errors, as the - `err` is standard error.
name would suggest. This sink is for writing errors, as the name would suggest.
## Functions ## Functions
### deregister(name) |||
Deregisters any command registered with `name` |----|----|
|<a href="#deregister">deregister(name)</a>|Removes the named command. Note that this will only remove Commander-registered commands.|
|<a href="#register">register(name, cb)</a>|Adds a new command with the given `name`. When Hilbish has to run a command with a name,|
|<a href="#registry">registry() -> table</a>|Returns all registered commanders. Returns a list of tables with the following keys:|
### register(name, cb) <hr>
Register a command with `name` that runs `cb` when ran <div id='deregister'>
<h4 class='heading'>
commander.deregister(name)
<a href="#deregister" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Removes the named command. Note that this will only remove Commander-registered commands.
#### Parameters
`string` **`name`**
Name of the command to remove.
</div>
<hr>
<div id='register'>
<h4 class='heading'>
commander.register(name, cb)
<a href="#register" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Adds a new command with the given `name`. When Hilbish has to run a command with a name,
it will run the function providing the arguments and sinks.
#### Parameters
`string` **`name`**
Name of the command
`function` **`cb`**
Callback to handle command invocation
#### Example
```lua
-- When you run the command `hello` in the shell, it will print `Hello world`.
-- If you run it with, for example, `hello Hilbish`, it will print 'Hello Hilbish'
commander.register('hello', function(args, sinks)
local name = 'world'
if #args > 0 then name = args[1] end
sinks.out:writeln('Hello ' .. name)
end)
```
</div>
<hr>
<div id='registry'>
<h4 class='heading'>
commander.registry() -> table
<a href="#registry" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns all registered commanders. Returns a list of tables with the following keys:
- `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
#### Parameters
This function has no parameters.
</div>

View File

@ -8,44 +8,250 @@ menu:
--- ---
## Introduction ## 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 The fs module provides filesystem functions to Hilbish. While Lua's standard
I/O and filesystem functions. library has some I/O functions, they're missing a lot of the basics. The `fs`
library offers more functions and will work on any operating system Hilbish does.
## Functions ## Functions
### abs(path) -> string |||
Gives an absolute version of `path`. |----|----|
|<a href="#abs">abs(path) -> string</a>|Returns an absolute version of the `path`.|
|<a href="#basename">basename(path) -> string</a>|Returns the "basename," or the last part of the provided `path`. If path is empty,|
|<a href="#cd">cd(dir)</a>|Changes Hilbish's directory to `dir`.|
|<a href="#dir">dir(path) -> string</a>|Returns the directory part of `path`. If a file path like|
|<a href="#glob">glob(pattern) -> matches (table)</a>|Match all files based on the provided `pattern`.|
|<a href="#join">join(...path) -> string</a>|Takes any list of paths and joins them based on the operating system's path separator.|
|<a href="#mkdir">mkdir(name, recursive)</a>|Creates a new directory with the provided `name`.|
|<a href="#pipe">fpipe() -> File, File</a>|Returns a pair of connected files, also known as a pipe.|
|<a href="#readdir">readdir(path) -> table[string]</a>|Returns a list of all files and directories in the provided path.|
|<a href="#stat">stat(path) -> {}</a>|Returns the information about a given `path`.|
### basename(path) -> string ## Static module fields
Gives the basename of `path`. For the rules, |||
see Go's filepath.Base |----|----|
|pathSep|The operating system's path separator.|
### cd(dir) <hr>
Changes directory to `dir` <div id='abs'>
<h4 class='heading'>
fs.abs(path) -> string
<a href="#abs" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### dir(path) -> string Returns an absolute version of the `path`.
Returns the directory part of `path`. For the rules, see Go's This can be used to resolve short paths like `..` to `/home/user`.
filepath.Dir
### glob(pattern) -> matches (table) #### Parameters
Glob all files and directories that match the pattern. `string` **`path`**
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) </div>
Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
### readdir(dir) -> {} <hr>
Returns a table of files in `dir`. <div id='basename'>
<h4 class='heading'>
fs.basename(path) -> string
<a href="#basename" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### stat(path) -> {} Returns the "basename," or the last part of the provided `path`. If path is empty,
Returns a table of info about the `path`. `.` will be returned.
It contains the following keys:
name (string) - Name of the path #### Parameters
size (number) - Size of the path `string` **`path`**
mode (string) - Permission mode in an octal format string (with leading 0) Path to get the base name of.
isDir (boolean) - If the path is a directory
</div>
<hr>
<div id='cd'>
<h4 class='heading'>
fs.cd(dir)
<a href="#cd" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Changes Hilbish's directory to `dir`.
#### Parameters
`string` **`dir`**
Path to change directory to.
</div>
<hr>
<div id='dir'>
<h4 class='heading'>
fs.dir(path) -> string
<a href="#dir" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns the directory part of `path`. If a file path like
`~/Documents/doc.txt` then this function will return `~/Documents`.
#### Parameters
`string` **`path`**
Path to get the directory for.
</div>
<hr>
<div id='glob'>
<h4 class='heading'>
fs.glob(pattern) -> matches (table)
<a href="#glob" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Match all files based on the provided `pattern`.
For the syntax' refer to Go's filepath.Match function: https://pkg.go.dev/path/filepath#Match
#### Parameters
`string` **`pattern`**
Pattern to compare files with.
#### Example
```lua
--[[
Within a folder that contains the following files:
a.txt
init.lua
code.lua
doc.pdf
]]--
local matches = fs.glob './*.lua'
print(matches)
-- -> {'init.lua', 'code.lua'}
```
</div>
<hr>
<div id='join'>
<h4 class='heading'>
fs.join(...path) -> string
<a href="#join" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Takes any list of paths and joins them based on the operating system's path separator.
#### Parameters
`string` **`path`** (This type is variadic. You can pass an infinite amount of parameters with this type.)
Paths to join together
#### Example
```lua
-- This prints the directory for Hilbish's config!
print(fs.join(hilbish.userDir.config, 'hilbish'))
-- -> '/home/user/.config/hilbish' on Linux
```
</div>
<hr>
<div id='mkdir'>
<h4 class='heading'>
fs.mkdir(name, recursive)
<a href="#mkdir" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Creates a new directory with the provided `name`.
With `recursive`, mkdir will create parent directories.
#### Parameters
`string` **`name`**
Name of the directory
`boolean` **`recursive`**
Whether to create parent directories for the provided name
#### Example
```lua
-- This will create the directory foo, then create the directory bar in the
-- foo directory. If recursive is false in this case, it will fail.
fs.mkdir('./foo/bar', true)
```
</div>
<hr>
<div id='pipe'>
<h4 class='heading'>
fs.fpipe() -> File, File
<a href="#pipe" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns a pair of connected files, also known as a pipe.
The type returned is a Lua file, same as returned from `io` functions.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='readdir'>
<h4 class='heading'>
fs.readdir(path) -> table[string]
<a href="#readdir" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns a list of all files and directories in the provided path.
#### Parameters
`string` **`dir`**
</div>
<hr>
<div id='stat'>
<h4 class='heading'>
fs.stat(path) -> {}
<a href="#stat" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns the information about a given `path`.
The returned table contains the following values:
name (string) - Name of the path
size (number) - Size of the path in bytes
mode (string) - Unix permission mode in an octal format string (with leading 0)
isDir (boolean) - If the path is a directory
#### Parameters
`string` **`path`**
#### Example
```lua
local inspect = require 'inspect'
local stat = fs.stat '~'
print(inspect(stat))
--[[
Would print the following:
{
isDir = true,
mode = "0755",
name = "username",
size = 12288
}
]]--
```
</div>

View File

@ -11,104 +11,531 @@ menu:
The Hilbish module includes the core API, containing The Hilbish module includes the core API, containing
interfaces and functions which directly relate to shell functionality. 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 ## Functions
### alias(cmd, orig) |||
Sets an alias of `cmd` to `orig` |----|----|
|<a href="#alias">alias(cmd, orig)</a>|Sets an alias, with a name of `cmd` to another command.|
|<a href="#appendPath">appendPath(dir)</a>|Appends the provided dir to the command path (`$PATH`)|
|<a href="#complete">complete(scope, cb)</a>|Registers a completion handler for the specified scope.|
|<a href="#cwd">cwd() -> string</a>|Returns the current directory of the shell.|
|<a href="#exec">exec(cmd)</a>|Replaces the currently running Hilbish instance with the supplied command.|
|<a href="#goro">goro(fn)</a>|Puts `fn` in a Goroutine.|
|<a href="#highlighter">highlighter(line)</a>|Line highlighter handler.|
|<a href="#hinter">hinter(line, pos)</a>|The command line hint handler. It gets called on every key insert to|
|<a href="#inputMode">inputMode(mode)</a>|Sets the input mode for Hilbish's line reader.|
|<a href="#interval">interval(cb, time) -> @Timer</a>|Runs the `cb` function every specified amount of `time`.|
|<a href="#multiprompt">multiprompt(str)</a>|Changes the text prompt when Hilbish asks for more input.|
|<a href="#prependPath">prependPath(dir)</a>|Prepends `dir` to $PATH.|
|<a href="#prompt">prompt(str, typ)</a>|Changes the shell prompt to the provided string.|
|<a href="#read">read(prompt) -> input (string)</a>|Read input from the user, using Hilbish's line editor/input reader.|
|<a href="#run">run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)</a>|Runs `cmd` in Hilbish's shell script interpreter.|
|<a href="#runnerMode">runnerMode(mode)</a>|Sets the execution/runner mode for interactive Hilbish.|
|<a href="#timeout">timeout(cb, time) -> @Timer</a>|Executed the `cb` function after a period of `time`.|
|<a href="#which">which(name) -> string</a>|Checks if `name` is a valid command.|
### appendPath(dir) ## Static module fields
Appends `dir` to $PATH |||
|----|----|
|ver|The version of Hilbish|
|goVersion|The version of Go that Hilbish was compiled with|
|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|Exit code of the last executed command|
### complete(scope, cb) <hr>
Registers a completion handler for `scope`. <div id='alias'>
A `scope` is currently only expected to be `command.<cmd>`, <h4 class='heading'>
replacing <cmd> with the name of the command (for example `command.git`). hilbish.alias(cmd, orig)
`cb` must be a function that returns a table of "completion groups." <a href="#alias" class='heading-link'>
Check `doc completions` for more information. <i class="fas fa-paperclip"></i>
</a>
</h4>
### cwd() -> string Sets an alias, with a name of `cmd` to another command.
Returns the current directory of the shell
### exec(cmd) #### Parameters
Replaces running hilbish with `cmd` `string` **`cmd`**
Name of the alias
### goro(fn) `string` **`orig`**
Puts `fn` in a goroutine Command that will be aliased
### highlighter(line) #### Example
Line highlighter handler. This is mainly for syntax highlighting, but in ```lua
reality could set the input of the prompt to *display* anything. The -- With this, "ga file" will turn into "git add file"
callback is passed the current line and is expected to return a line that hilbish.alias('ga', 'git add')
will be used as the input display.
### hinter(line, pos) -- Numbered substitutions are supported here!
The command line hint handler. It gets called on every key insert to hilbish.alias('dircount', 'ls %1 | wc -l')
determine what text to use as an inline hint. It is passed the current -- "dircount ~" would count how many files are in ~ (home directory).
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, </div>
override this function with your custom handler.
### inputMode(mode) <hr>
Sets the input mode for Hilbish's line reader. Accepts either emacs or vim <div id='appendPath'>
<h4 class='heading'>
hilbish.appendPath(dir)
<a href="#appendPath" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### interval(cb, time) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;">Timer</a> Appends the provided dir to the command path (`$PATH`)
Runs the `cb` function every `time` milliseconds.
This creates a timer that starts immediately.
### multiprompt(str) #### Parameters
Changes the continued line prompt to `str` `string|table` **`dir`**
Directory (or directories) to append to path
### prependPath(dir) #### Example
Prepends `dir` to $PATH ```lua
hilbish.appendPath '~/go/bin'
-- Will add ~/go/bin to the command path.
### prompt(str, typ) -- Or do multiple:
Changes the shell prompt to `str` hilbish.appendPath {
There are a few verbs that can be used in the prompt text. '~/go/bin',
These will be formatted and replaced with the appropriate values. '~/.local/bin'
`%d` - Current working directory }
`%u` - Name of current user ```
`%h` - Hostname of device </div>
### read(prompt) -> input (string) <hr>
Read input from the user, using Hilbish's line editor/input reader. <div id='complete'>
This is a separate instance from the one Hilbish actually uses. <h4 class='heading'>
Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen) hilbish.complete(scope, cb)
<a href="#complete" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string) Registers a completion handler for the specified scope.
Runs `cmd` in Hilbish's sh interpreter. A `scope` is expected to be `command.<cmd>`,
If returnOut is true, the outputs of `cmd` will be returned as the 2nd and replacing <cmd> with the name of the command (for example `command.git`).
3rd values instead of being outputted to the terminal. The documentation for completions, under Features/Completions or `doc completions`
provides more details.
### runnerMode(mode) #### Parameters
Sets the execution/runner mode for interactive Hilbish. This determines whether `string` **`scope`**
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) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;">Timer</a>
Runs the `cb` function after `time` in milliseconds.
This creates a timer that starts immediately.
### which(name) -> string `function` **`cb`**
Checks if `name` is a valid command.
Will return the path of the binary, or a basename if it's a commander.
#### Example
```lua
-- This is a very simple example. Read the full doc for completions for details.
hilbish.complete('command.sudo', function(query, ctx, fields)
if #fields == 0 then
-- complete for commands
local comps, pfx = hilbish.completion.bins(query, ctx, fields)
local compGroup = {
items = comps, -- our list of items to complete
type = 'grid' -- what our completions will look like.
}
return {compGroup}, pfx
end
-- otherwise just be boring and return files
local comps, pfx = hilbish.completion.files(query, ctx, fields)
local compGroup = {
items = comps,
type = 'grid'
}
return {compGroup}, pfx
end)
```
</div>
<hr>
<div id='cwd'>
<h4 class='heading'>
hilbish.cwd() -> string
<a href="#cwd" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns the current directory of the shell.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='exec'>
<h4 class='heading'>
hilbish.exec(cmd)
<a href="#exec" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Replaces the currently running Hilbish instance with the supplied command.
This can be used to do an in-place restart.
#### Parameters
`string` **`cmd`**
</div>
<hr>
<div id='goro'>
<h4 class='heading'>
hilbish.goro(fn)
<a href="#goro" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Puts `fn` in a Goroutine.
This can be used to run any function in another thread at the same time as other Lua code.
**NOTE: THIS FUNCTION MAY CRASH HILBISH IF OUTSIDE VARIABLES ARE ACCESSED.**
**This is a limitation of the Lua runtime.**
#### Parameters
`function` **`fn`**
</div>
<hr>
<div id='highlighter'>
<h4 class='heading'>
hilbish.highlighter(line)
<a href="#highlighter" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
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.
Note that to set a highlighter, one has to override this function.
#### Parameters
`string` **`line`**
#### Example
```lua
--This code will highlight all double quoted strings in green.
function hilbish.highlighter(line)
return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
end
```
</div>
<hr>
<div id='hinter'>
<h4 class='heading'>
hilbish.hinter(line, pos)
<a href="#hinter" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
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.
#### Parameters
`string` **`line`**
`number` **`pos`**
Position of cursor in line. Usually equals string.len(line)
#### Example
```lua
-- this will display "hi" after the cursor in a dimmed color.
function hilbish.hinter(line, pos)
return 'hi'
end
```
</div>
<hr>
<div id='inputMode'>
<h4 class='heading'>
hilbish.inputMode(mode)
<a href="#inputMode" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Sets the input mode for Hilbish's line reader.
`emacs` is the default. Setting it to `vim` changes behavior of input to be
Vim-like with modes and Vim keybinds.
#### Parameters
`string` **`mode`**
Can be set to either `emacs` or `vim`
</div>
<hr>
<div id='interval'>
<h4 class='heading'>
hilbish.interval(cb, time) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;" id="lol">Timer</a>
<a href="#interval" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Runs the `cb` function every specified amount of `time`.
This creates a timer that ticking immediately.
#### Parameters
`function` **`cb`**
`number` **`time`**
Time in milliseconds.
</div>
<hr>
<div id='multiprompt'>
<h4 class='heading'>
hilbish.multiprompt(str)
<a href="#multiprompt" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Changes the text prompt when Hilbish asks for more input.
This will show up when text is incomplete, like a missing quote
#### Parameters
`string` **`str`**
#### Example
```lua
--[[
imagine this is your text input:
user ~ ∆ echo "hey
but there's a missing quote! hilbish will now prompt you so the terminal
will look like:
user ~ ∆ echo "hey
--> ...!"
so then you get
user ~ ∆ echo "hey
--> ...!"
hey ...!
]]--
hilbish.multiprompt '-->'
```
</div>
<hr>
<div id='prependPath'>
<h4 class='heading'>
hilbish.prependPath(dir)
<a href="#prependPath" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Prepends `dir` to $PATH.
#### Parameters
`string` **`dir`**
</div>
<hr>
<div id='prompt'>
<h4 class='heading'>
hilbish.prompt(str, typ)
<a href="#prompt" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Changes the shell prompt to the provided string.
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
#### Parameters
`string` **`str`**
`string` **`typ?`**
Type of prompt, being left or right. Left by default.
#### Example
```lua
-- the default hilbish prompt without color
hilbish.prompt '%u %d ∆'
-- or something of old:
hilbish.prompt '%u@%h :%d $'
-- prompt: user@hostname: ~/directory $
```
</div>
<hr>
<div id='read'>
<h4 class='heading'>
hilbish.read(prompt) -> input (string)
<a href="#read" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
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.
#### Parameters
`string` **`prompt?`**
Text to print before input, can be empty.
</div>
<hr>
<div id='run'>
<h4 class='heading'>
hilbish.run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
<a href="#run" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Runs `cmd` in Hilbish's shell script interpreter.
The `streams` parameter specifies the output and input streams the command should use.
For example, to write command output to a sink.
As a table, the caller can directly specify the standard output, error, and input
streams of the command with the table keys `out`, `err`, and `input` respectively.
As a boolean, it specifies whether the command should use standard output or return its output streams.
#### Parameters
`string` **`cmd`**
`table|boolean` **`streams`**
#### Example
```lua
// This code is the same as `ls -l | wc -l`
local fs = require 'fs'
local pr, pw = fs.pipe()
hilbish.run('ls -l', {
stdout = pw,
stderr = pw,
})
pw:close()
hilbish.run('wc -l', {
stdin = pr
})
```
</div>
<hr>
<div id='runnerMode'>
<h4 class='heading'>
hilbish.runnerMode(mode)
<a href="#runnerMode" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
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.
Read [about runner mode](../features/runner-mode) for more information.
#### Parameters
`string|function` **`mode`**
</div>
<hr>
<div id='timeout'>
<h4 class='heading'>
hilbish.timeout(cb, time) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;" id="lol">Timer</a>
<a href="#timeout" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Executed the `cb` function after a period of `time`.
This creates a Timer that starts ticking immediately.
#### Parameters
`function` **`cb`**
`number` **`time`**
Time to run in milliseconds.
</div>
<hr>
<div id='which'>
<h4 class='heading'>
hilbish.which(name) -> string
<a href="#which" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Checks if `name` is a valid command.
Will return the path of the binary, or a basename if it's a commander.
#### Parameters
`string` **`name`**
</div>
## Types ## Types
<hr>
## Sink ## Sink
A sink is a structure that has input and/or output to/from A sink is a structure that has input and/or output to/from
a desination. a desination.
### Methods ### Methods
#### autoFlush(auto)
Sets/toggles the option of automatically flushing output.
A call with no argument will toggle the value.
#### flush()
Flush writes all buffered input to the sink.
#### read() -> string
Reads a liine of input from the sink.
#### readAll() -> string
Reads all input from the sink.
#### write(str) #### write(str)
Writes data to a sink. Writes data to a sink.

View File

@ -1,5 +1,5 @@
--- ---
title: Interface hilbish.aliases title: Module hilbish.aliases
description: command aliasing description: command aliasing
layout: doc layout: doc
menu: menu:
@ -11,15 +11,81 @@ menu:
The alias interface deals with all command aliases in Hilbish. The alias interface deals with all command aliases in Hilbish.
## Functions ## Functions
### add(alias, cmd) |||
This is an alias (ha) for the `hilbish.alias` function. |----|----|
|<a href="#aliases.add">add(alias, cmd)</a>|This is an alias (ha) for the [hilbish.alias](../#alias) function.|
|<a href="#aliases.delete">delete(name)</a>|Removes an alias.|
|<a href="#aliases.list">list() -> table[string, string]</a>|Get a table of all aliases, with string keys as the alias and the value as the command.|
|<a href="#aliases.resolve">resolve(alias) -> string?</a>|Resolves an alias to its original command. Will thrown an error if the alias doesn't exist.|
### delete(name) <hr>
Removes an alias. <div id='aliases.add'>
<h4 class='heading'>
hilbish.aliases.add(alias, cmd)
<a href="#aliases.add" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### list() -> table\<string, string> This is an alias (ha) for the [hilbish.alias](../#alias) function.
Get a table of all aliases, with string keys as the alias and the value as the command.
### resolve(alias) -> command (string) #### Parameters
Tries to resolve an alias to its command. This function has no parameters.
</div>
<hr>
<div id='aliases.delete'>
<h4 class='heading'>
hilbish.aliases.delete(name)
<a href="#aliases.delete" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Removes an alias.
#### Parameters
`string` **`name`**
</div>
<hr>
<div id='aliases.list'>
<h4 class='heading'>
hilbish.aliases.list() -> table[string, string]
<a href="#aliases.list" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Get a table of all aliases, with string keys as the alias and the value as the command.
#### Parameters
This function has no parameters.
#### Example
```lua
hilbish.aliases.add('hi', 'echo hi')
local aliases = hilbish.aliases.list()
-- -> {hi = 'echo hi'}
```
</div>
<hr>
<div id='aliases.resolve'>
<h4 class='heading'>
hilbish.aliases.resolve(alias) -> string?
<a href="#aliases.resolve" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Resolves an alias to its original command. Will thrown an error if the alias doesn't exist.
#### Parameters
`string` **`alias`**
</div>

View File

@ -0,0 +1,149 @@
---
title: Module hilbish.completion
description: tab completions
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The completions interface deals with tab completions.
## Functions
|||
|----|----|
|<a href="#completion.bins">bins(query, ctx, fields) -> entries (table), prefix (string)</a>|Return binaries/executables based on the provided parameters.|
|<a href="#completion.call">call(name, query, ctx, fields) -> completionGroups (table), prefix (string)</a>|Calls a completer function. This is mainly used to call a command completer, which will have a `name`|
|<a href="#completion.files">files(query, ctx, fields) -> entries (table), prefix (string)</a>|Returns file matches based on the provided parameters.|
|<a href="#completion.handler">handler(line, pos)</a>|This function contains the general completion handler for Hilbish. This function handles|
<hr>
<div id='completion.bins'>
<h4 class='heading'>
hilbish.completion.bins(query, ctx, fields) -> entries (table), prefix (string)
<a href="#completion.bins" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Return binaries/executables based on the provided parameters.
This function is meant to be used as a helper in a command completion handler.
#### Parameters
`string` **`query`**
`string` **`ctx`**
`table` **`fields`**
#### Example
```lua
-- an extremely simple completer for sudo.
hilbish.complete('command.sudo', function(query, ctx, fields)
table.remove(fields, 1)
if #fields[1] then
-- return commands because sudo runs a command as root..!
local entries, pfx = hilbish.completion.bins(query, ctx, fields)
return {
type = 'grid',
items = entries
}, pfx
end
-- ... else suggest files or anything else ..
end)
```
</div>
<hr>
<div id='completion.call'>
<h4 class='heading'>
hilbish.completion.call(name, query, ctx, fields) -> completionGroups (table), prefix (string)
<a href="#completion.call" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
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 the Completions doc or `doc completions` for info on the `completionGroups` return value.
#### Parameters
`string` **`name`**
`string` **`query`**
`string` **`ctx`**
`table` **`fields`**
</div>
<hr>
<div id='completion.files'>
<h4 class='heading'>
hilbish.completion.files(query, ctx, fields) -> entries (table), prefix (string)
<a href="#completion.files" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns file matches based on the provided parameters.
This function is meant to be used as a helper in a command completion handler.
#### Parameters
`string` **`query`**
`string` **`ctx`**
`table` **`fields`**
</div>
<hr>
<div id='completion.handler'>
<h4 class='heading'>
hilbish.completion.handler(line, pos)
<a href="#completion.handler" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
This function contains the general completion handler for Hilbish. This function handles
completion of everything, which includes calling other command handlers, binaries, and files.
This function can be overriden to supply a custom handler. Note that alias resolution is required to be done in this function.
#### Parameters
`string` **`line`**
The current Hilbish command line
`number` **`pos`**
Numerical position of the cursor
#### Example
```lua
-- stripped down version of the default implementation
function hilbish.completion.handler(line, pos)
local query = fields[#fields]
if #fields == 1 then
-- call bins handler here
else
-- call command completer or files completer here
end
end
```
</div>

View File

@ -1,29 +0,0 @@
---
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.

View File

@ -1,5 +1,5 @@
--- ---
title: Interface hilbish.editor title: Module hilbish.editor
description: interactions for Hilbish's line reader description: interactions for Hilbish's line reader
layout: doc layout: doc
menu: menu:
@ -12,15 +12,92 @@ The hilbish.editor interface provides functions to
directly interact with the line editor in use. directly interact with the line editor in use.
## Functions ## Functions
### getLine() -> string |||
Returns the current input line. |----|----|
|<a href="#editor.getLine">getLine() -> string</a>|Returns the current input line.|
|<a href="#editor.getVimRegister">getVimRegister(register) -> string</a>|Returns the text that is at the register.|
|<a href="#editor.insert">insert(text)</a>|Inserts text into the Hilbish command line.|
|<a href="#editor.getChar">getChar() -> string</a>|Reads a keystroke from the user. This is in a format of something like Ctrl-L.|
|<a href="#editor.setVimRegister">setVimRegister(register, text)</a>|Sets the vim register at `register` to hold the passed text.|
### getVimRegister(register) -> string <hr>
Returns the text that is at the register. <div id='editor.getLine'>
<h4 class='heading'>
hilbish.editor.getLine() -> string
<a href="#editor.getLine" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### insert(text) Returns the current input line.
Inserts text into the line.
### setVimRegister(register, text) #### Parameters
Sets the vim register at `register` to hold the passed text. This function has no parameters.
</div>
<hr>
<div id='editor.getVimRegister'>
<h4 class='heading'>
hilbish.editor.getVimRegister(register) -> string
<a href="#editor.getVimRegister" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns the text that is at the register.
#### Parameters
`string` **`register`**
</div>
<hr>
<div id='editor.insert'>
<h4 class='heading'>
hilbish.editor.insert(text)
<a href="#editor.insert" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Inserts text into the Hilbish command line.
#### Parameters
`string` **`text`**
</div>
<hr>
<div id='editor.getChar'>
<h4 class='heading'>
hilbish.editor.getChar() -> string
<a href="#editor.getChar" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Reads a keystroke from the user. This is in a format of something like Ctrl-L.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='editor.setVimRegister'>
<h4 class='heading'>
hilbish.editor.setVimRegister(register, text)
<a href="#editor.setVimRegister" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Sets the vim register at `register` to hold the passed text.
#### Parameters
`string` **`text`**
</div>

View File

@ -1,5 +1,5 @@
--- ---
title: Interface hilbish.history title: Module hilbish.history
description: command history description: command history
layout: doc layout: doc
menu: menu:
@ -13,18 +13,90 @@ This includes the ability to override functions to change the main
method of saving history. method of saving history.
## Functions ## Functions
### add(cmd) |||
Adds a command to the history. |----|----|
|<a href="#history.add">add(cmd)</a>|Adds a command to the history.|
|<a href="#history.all">all() -> table</a>|Retrieves all history as a table.|
|<a href="#history.clear">clear()</a>|Deletes all commands from the history.|
|<a href="#history.get">get(index)</a>|Retrieves a command from the history based on the `index`.|
|<a href="#history.size">size() -> number</a>|Returns the amount of commands in the history.|
### all() -> table <hr>
Retrieves all history. <div id='history.add'>
<h4 class='heading'>
hilbish.history.add(cmd)
<a href="#history.add" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### clear() Adds a command to the history.
Deletes all commands from the history.
### get(idx) #### Parameters
Retrieves a command from the history based on the `idx`. `string` **`cmd`**
### size() -> number
Returns the amount of commands in the history. </div>
<hr>
<div id='history.all'>
<h4 class='heading'>
hilbish.history.all() -> table
<a href="#history.all" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Retrieves all history as a table.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='history.clear'>
<h4 class='heading'>
hilbish.history.clear()
<a href="#history.clear" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Deletes all commands from the history.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='history.get'>
<h4 class='heading'>
hilbish.history.get(index)
<a href="#history.get" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Retrieves a command from the history based on the `index`.
#### Parameters
`number` **`index`**
</div>
<hr>
<div id='history.size'>
<h4 class='heading'>
hilbish.history.size() -> number
<a href="#history.size" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns the amount of commands in the history.
#### Parameters
This function has no parameters.
</div>

View File

@ -1,5 +1,5 @@
--- ---
title: Interface hilbish.jobs title: Module hilbish.jobs
description: background job management description: background job management
layout: doc layout: doc
menu: menu:
@ -15,32 +15,120 @@ 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. interactive usage or with the functions defined below for use in external runners.
## Functions ## Functions
### add(cmdstr, args, execPath) |||
Adds a new job to the job table. Note that this does not immediately run it. |----|----|
|<a href="#jobs.add">add(cmdstr, args, execPath)</a>|Creates a new job. This function does not run the job. This function is intended to be|
|<a href="#jobs.all">all() -> table[@Job]</a>|Returns a table of all job objects.|
|<a href="#jobs.disown">disown(id)</a>|Disowns a job. This simply deletes it from the list of jobs without stopping it.|
|<a href="#jobs.get">get(id) -> @Job</a>|Get a job object via its ID.|
|<a href="#jobs.last">last() -> @Job</a>|Returns the last added job to the table.|
### all() -> table\<<a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;">Job</a>> <hr>
Returns a table of all job objects. <div id='jobs.add'>
<h4 class='heading'>
hilbish.jobs.add(cmdstr, args, execPath)
<a href="#jobs.add" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### disown(id) Creates a new job. This function does not run the job. This function is intended to be
Disowns a job. This deletes it from the job table. used by runners, but can also be used to create jobs via Lua. Commanders cannot be ran as jobs.
### get(id) -> <a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;">Job</a> #### Parameters
Get a job object via its ID. `string` **`cmdstr`**
String that a user would write for the job
### last() -> <a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;">Job</a> `table` **`args`**
Returns the last added job from the table. Arguments for the commands. Has to include the name of the command.
`string` **`execPath`**
Binary to use to run the command. Needs to be an absolute path.
#### Example
```lua
hilbish.jobs.add('go build', {'go', 'build'}, '/usr/bin/go')
```
</div>
<hr>
<div id='jobs.all'>
<h4 class='heading'>
hilbish.jobs.all() -> table[<a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;" id="lol">Job</a>]
<a href="#jobs.all" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns a table of all job objects.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='jobs.disown'>
<h4 class='heading'>
hilbish.jobs.disown(id)
<a href="#jobs.disown" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Disowns a job. This simply deletes it from the list of jobs without stopping it.
#### Parameters
`number` **`id`**
</div>
<hr>
<div id='jobs.get'>
<h4 class='heading'>
hilbish.jobs.get(id) -> <a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;" id="lol">Job</a>
<a href="#jobs.get" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Get a job object via its ID.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='jobs.last'>
<h4 class='heading'>
hilbish.jobs.last() -> <a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;" id="lol">Job</a>
<a href="#jobs.last" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns the last added job to the table.
#### Parameters
This function has no parameters.
</div>
## Types ## Types
<hr>
## Job ## Job
The Job type describes a Hilbish job. The Job type describes a Hilbish job.
### Properties ## Object 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 |cmd|The user entered command string for the job.|
- `pid`: The Process ID |running|Whether the job is running or not.|
- `exitCode`: The last exit code of the job. |id|The ID of the job in the job table|
- `stdout`: The standard output of the job. This just means the normal logs of the process. |pid|The Process ID|
- `stderr`: The standard error stream of the process. This (usually) includes error messages of the job. |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 ### Methods
#### background() #### background()

View File

@ -0,0 +1,73 @@
---
title: Module hilbish.module
description: native module loading
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The hilbish.module interface provides a function to load
Hilbish plugins/modules. Hilbish modules are Go-written
plugins (see https://pkg.go.dev/plugin) that are used to add functionality
to Hilbish that cannot be written in Lua for any reason.
Note that you don't ever need to use the load function that is here as
modules can be loaded with a `require` call like Lua C modules, and the
search paths can be changed with the `paths` property here.
To make a valid native module, the Go plugin has to export a Loader function
with a signature like so: `func(*rt.Runtime) rt.Value`.
`rt` in this case refers to the Runtime type at
https://pkg.go.dev/github.com/arnodel/golua@master/runtime#Runtime
Hilbish uses this package as its Lua runtime. You will need to read
it to use it for a native plugin.
Here is some code for an example plugin:
```go
package main
import (
rt "github.com/arnodel/golua/runtime"
)
func Loader(rtm *rt.Runtime) rt.Value {
return rt.StringValue("hello world!")
}
```
This can be compiled with `go build -buildmode=plugin plugin.go`.
If you attempt to require and print the result (`print(require 'plugin')`), it will show "hello world!"
## Functions
|||
|----|----|
|<a href="#module.load">load(path)</a>|Loads a module at the designated `path`.|
## Static module fields
|||
|----|----|
|paths|A list of paths to search when loading native modules. This is in the style of Lua search paths and will be used when requiring native modules. Example: `?.so;?/?.so`|
<hr>
<div id='module.load'>
<h4 class='heading'>
hilbish.module.load(path)
<a href="#module.load" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Loads a module at the designated `path`.
It will throw if any error occurs.
#### Parameters
`string` **`path`**
</div>

View File

@ -1,6 +1,6 @@
--- ---
title: Interface hilbish.os title: Module hilbish.os
description: OS Info description: operating system info
layout: doc layout: doc
menu: menu:
docs: docs:
@ -8,12 +8,13 @@ menu:
--- ---
## Introduction ## Introduction
The `os` interface provides simple text information properties about Provides simple text information properties about the current operating system.
the current OS on the systen. This mainly includes the name and This mainly includes the name and version.
version.
## Interface fields ## Static module fields
- `family`: Family name of the current OS |||
- `name`: Pretty name of the current OS |----|----|
- `version`: Version of the current OS |family|Family name of the current OS|
|name|Pretty name of the current OS|
|version|Version of the current OS|

View File

@ -1,5 +1,5 @@
--- ---
title: Interface hilbish.runner title: Module hilbish.runner
description: interactive command runner customization description: interactive command runner customization
layout: doc layout: doc
menu: menu:
@ -8,24 +8,107 @@ menu:
--- ---
## Introduction ## Introduction
The runner interface contains functions that allow the user to change The runner interface contains functions that allow the user to change
how Hilbish interprets interactive input. how Hilbish interprets interactive input.
Users can add and change the default runner for interactive input to any 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 language or script of their choosing. A good example is using it to
write command in Fennel. write command in Fennel.
Runners are functions that evaluate user input. The default runners in
Hilbish can run shell script and Lua code.
A runner is passed the input and has to return a table with these values.
All are not required, only the useful ones the runner needs to return.
(So if there isn't an error, just omit `err`.)
- `exitCode` (number): A numerical code to indicate the exit result.
- `input` (string): The user input. This will be used to add
to the history.
- `err` (string): A string to indicate an interal error for the runner.
It can be set to a few special values for Hilbish to throw the right hooks and have a better looking message:
`[command]: not-found` will throw a command.not-found hook based on what `[command]` is.
`[command]: not-executable` will throw a command.not-executable hook.
- `continue` (boolean): Whether to prompt the user for more input.
Here is a simple example of a fennel runner. It falls back to
shell script if fennel eval has an error.
```lua
local fennel = require 'fennel'
hilbish.runnerMode(function(input)
local ok = pcall(fennel.eval, input)
if ok then
return {
input = input
}
end
return hilbish.runner.sh(input)
end)
```
## Functions ## 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. |<a href="#runner.setMode">setMode(cb)</a>|This is the same as the `hilbish.runnerMode` function.|
In normal cases, neither callbacks should be overrided by the user, |<a href="#runner.lua">lua(cmd)</a>|Evaluates `cmd` as Lua input. This is the same as using `dofile`|
as the higher level functions listed below this will handle it. |<a href="#runner.sh">sh(cmd)</a>|Runs a command in Hilbish's shell script interpreter.|
### lua(cmd) <hr>
Evaluates `cmd` as Lua input. This is the same as using `dofile` <div id='runner.setMode'>
or `load`, but is appropriated for the runner interface. <h4 class='heading'>
hilbish.runner.setMode(cb)
<a href="#runner.setMode" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### sh(cmd) This is the same as the `hilbish.runnerMode` function.
Runs a command in Hilbish's shell script interpreter. It takes a callback, which will be used to execute all interactive input.
This is the equivalent of using `source`. In normal cases, neither callbacks should be overrided by the user,
as the higher level functions listed below this will handle it.
#### Parameters
`function` **`cb`**
</div>
<hr>
<div id='runner.lua'>
<h4 class='heading'>
hilbish.runner.lua(cmd)
<a href="#runner.lua" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Evaluates `cmd` as Lua input. This is the same as using `dofile`
or `load`, but is appropriated for the runner interface.
#### Parameters
`string` **`cmd`**
</div>
<hr>
<div id='runner.sh'>
<h4 class='heading'>
hilbish.runner.sh(cmd)
<a href="#runner.sh" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Runs a command in Hilbish's shell script interpreter.
This is the equivalent of using `source`.
#### Parameters
`string` **`cmd`**
</div>

View File

@ -1,5 +1,5 @@
--- ---
title: Interface hilbish.timers title: Module hilbish.timers
description: timeout and interval API description: timeout and interval API
layout: doc layout: doc
menu: menu:
@ -14,14 +14,10 @@ a few seconds, you don't have to rely on timing tricks, as Hilbish has a
timer API to set intervals and timeouts. timer API to set intervals and timeouts.
These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc
accessible with `doc hilbish`). But if you want slightly more control over accessible with `doc hilbish`, or `Module hilbish` on the Website).
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: An example of usage:
``` ```lua
local t = hilbish.timers.create(hilbish.timers.TIMEOUT, 5000, function() local t = hilbish.timers.create(hilbish.timers.TIMEOUT, 5000, function()
print 'hello!' print 'hello!'
end) end)
@ -30,25 +26,70 @@ t:start()
print(t.running) // true print(t.running) // true
``` ```
## Interface fields
- `INTERVAL`: Constant for an interval timer type
- `TIMEOUT`: Constant for a timeout timer type
## Functions ## Functions
### create(type, time, callback) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;">Timer</a> |||
Creates a timer that runs based on the specified `time` in milliseconds. |----|----|
The `type` can either be `hilbish.timers.INTERVAL` or `hilbish.timers.TIMEOUT` |<a href="#timers.create">create(type, time, callback) -> @Timer</a>|Creates a timer that runs based on the specified `time`.|
|<a href="#timers.get">get(id) -> @Timer</a>|Retrieves a timer via its ID.|
### get(id) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;">Timer</a> ## Static module fields
Retrieves a timer via its ID. |||
|----|----|
|INTERVAL|Constant for an interval timer type|
|TIMEOUT|Constant for a timeout timer type|
<hr>
<div id='timers.create'>
<h4 class='heading'>
hilbish.timers.create(type, time, callback) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;" id="lol">Timer</a>
<a href="#timers.create" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Creates a timer that runs based on the specified `time`.
#### Parameters
`number` **`type`**
What kind of timer to create, can either be `hilbish.timers.INTERVAL` or `hilbish.timers.TIMEOUT`
`number` **`time`**
The amount of time the function should run in milliseconds.
`function` **`callback`**
The function to run for the timer.
</div>
<hr>
<div id='timers.get'>
<h4 class='heading'>
hilbish.timers.get(id) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;" id="lol">Timer</a>
<a href="#timers.get" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Retrieves a timer via its ID.
#### Parameters
`number` **`id`**
</div>
## Types ## Types
<hr>
## Timer ## Timer
The Job type describes a Hilbish timer. The Job type describes a Hilbish timer.
### Properties ## Object properties
- `type`: What type of timer it is |||
- `running`: If the timer is running |----|----|
- `duration`: The duration in milliseconds that the timer will run |type|What type of timer it is|
|running|If the timer is running|
|duration|The duration in milliseconds that the timer will run|
### Methods ### Methods
#### start() #### start()

View File

@ -1,5 +1,5 @@
--- ---
title: Interface hilbish.userDir title: Module hilbish.userDir
description: user-related directories description: user-related directories
layout: doc layout: doc
menu: menu:
@ -12,7 +12,9 @@ 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 It is equivalent to XDG on Linux and gets the user's preferred directories
for configs and data. for configs and data.
## Interface fields ## Static module fields
- `config`: The user's config directory |||
- `data`: The user's directory for program data |----|----|
|config|The user's config directory|
|data|The user's directory for program data|

View File

@ -11,16 +11,71 @@ menu:
The terminal library is a simple and lower level library for certain terminal interactions. The terminal library is a simple and lower level library for certain terminal interactions.
## Functions ## Functions
### restoreState() |||
Restores the last saved state of the terminal |----|----|
|<a href="#restoreState">restoreState()</a>|Restores the last saved state of the terminal|
|<a href="#saveState">saveState()</a>|Saves the current state of the terminal.|
|<a href="#setRaw">setRaw()</a>|Puts the terminal into raw mode.|
|<a href="#size">size()</a>|Gets the dimensions of the terminal. Returns a table with `width` and `height`|
### saveState() <hr>
Saves the current state of the terminal <div id='restoreState'>
<h4 class='heading'>
terminal.restoreState()
<a href="#restoreState" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
### setRaw() Restores the last saved state of the terminal
Puts the terminal in raw mode
### size() #### Parameters
Gets the dimensions of the terminal. Returns a table with `width` and `height` This function has no parameters.
Note: this is not the size in relation to the dimensions of the display </div>
<hr>
<div id='saveState'>
<h4 class='heading'>
terminal.saveState()
<a href="#saveState" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Saves the current state of the terminal.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='setRaw'>
<h4 class='heading'>
terminal.setRaw()
<a href="#setRaw" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Puts the terminal into raw mode.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='size'>
<h4 class='heading'>
terminal.size()
<a href="#size" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Gets the dimensions of the terminal. Returns a table with `width` and `height`
NOTE: The size refers to the amount of columns and rows of text that can fit in the terminal.
#### Parameters
This function has no parameters.
</div>

View File

@ -1,56 +1,78 @@
Hilbish has a pretty good completion system. It has a nice looking ---
menu, with 2 types of menus: grid (like file completions) or title: Completions
list. description: Tab completion for commands.
layout: doc
menu:
docs:
parent: "Features"
---
Completions for commands can be created with the [`hilbish.complete`](../api/hilbish#complete)
function. See the link for how to use it.
To create completions for a command is simple.
The callback will be passed 3 parameters:
- `query` (string): The text that the user is currently trying to complete.
This should be used to match entries.
- `ctx` (string): Contains the entire line. Use this if
more text is needed to be parsed for context.
- `fields` (string): The `ctx` split up by spaces.
In most cases, the completer just uses `fields` to check the amount
and `query` on what to match entries on.
In order to return your results, it has to go within a "completion group."
Then you return a table of completion groups and a prefix. The prefix will
usually just be the `query`.
Hilbish allows one to mix completion menus of different types, so
a grid menu and a list menu can be used and complete and display at the same time.
A completion group is a table with these keys:
- `type` (string): type of completion menu, either `grid` or `list`.
- `items` (table): a list of items.
The requirements of the `items` table is different based on the
`type`. If it is a `grid`, it can simply be a table of strings.
Otherwise if it is a `list` then each entry can
either be a string or a table.
Example:
```lua
local cg = {
items = {
'list item 1',
['--command-flag-here'] = {'this does a thing', '--the-flag-alias'}
},
type = 'list'
}
local cg2 = {
items = {'just', 'a bunch', 'of items', 'here', 'hehe'},
type = 'grid'
}
return {cg, cg2}, prefix
```
Which looks like this:
{{< video src="https://safe.saya.moe/t4CiLK6dgPbD.mp4" >}}
# Completion Handler
Like most parts of Hilbish, it's made to be extensible and Like most parts of Hilbish, it's made to be extensible and
customizable. The default handler for completions in general can customizable. The default handler for completions in general can
be overwritten to provide more advanced completions if needed. be overwritten to provide more advanced completions if needed.
This usually doesn't need to be done though, unless you know
what you're doing.
# Completion Handler The default completion handler provides 3 things:
By default, it provides 3 things: for the first argument,
binaries (with a plain name requested to complete, those in binaries (with a plain name requested to complete, those in
$PATH), files, or command completions. With the default $PATH), files, or command completions. It will try to run a handler
completion handler, it will try to run a handler for the for the command or fallback to file completions.
command or fallback to file completions.
To overwrite it, just assign a function to To overwrite it, just assign a function to `hilbish.completion.handler` like so:
`hilbish.completion.handler` like so: ```lua
-- line is the entire line as a string
-- pos is the position of the cursor.
function hilbish.completion.handler(line, pos) function hilbish.completion.handler(line, pos)
-- do things -- do things
end 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.<name>`

View File

@ -15,11 +15,12 @@ It compiles for Windows (CI ensures it does), but otherwise it is not
directly supported. If you'd like to improve this situation, directly supported. If you'd like to improve this situation,
checkout [the discussion](https://github.com/Rosettea/Hilbish/discussions/165). 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? # Why?
Hilbish emerged from the desire of a Lua configured shell. Hilbish emerged from the desire of a Lua configured shell.
It was the initial reason that it was created, but now it's more: It was the initial reason that it was created, but now it's more:
to be hyper extensible, simpler and more user friendly. to be hyper extensible, simpler and more user friendly.
# Does it have "autocompletion" or "tab completion"
Of course! This is a modern shell. Hilbish provides a way for users
to write tab completion for any command and/or the whole shell.
Inline hinting and syntax highlighting are also available.

View File

@ -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
new ones are always being added. 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.

View File

@ -0,0 +1,39 @@
---
title: Notification
description: Get notified of shell actions.
layout: doc
menu:
docs:
parent: "Features"
---
Hilbish features a simple notification system which can be
used by other plugins and parts of the shell to notify the user
of various actions. This is used via the `hilbish.message` interface.
A `message` is defined as a table with the following properties:
- `icon`: A unicode/emoji icon for the notification.
- `title`: The title of the message
- `text`: Message text/body
- `channel`: The source of the message. This should be a
unique and easily readable text identifier.
- `summary`: A short summary of the notification and message.
If this is not present and you are using this to display messages,
you should take part of the `text` instead.
The `hilbish.message` interface provides the following functions:
- `send(message)`: Sends a message and emits the `hilbish.notification`
signal. DO NOT emit the `hilbish.notification` signal directly, or
the message will not be stored by the message handler.
- `read(idx)`: Marks message at `idx` as read.
- `delete(idx)`: Removes message at `idx`.
- `readAll()`: Marks all messages as read.
- `clear()`: Deletes all messages.
There are a few simple use cases of this notification/messaging system.
It could also be used as some "inter-shell" messaging system (???) but
is intended to display to users.
An example is notifying users of completed jobs/commands ran in the background.
Any Hilbish-native command (think the upcoming Greenhouse pager) can display
it.

View File

@ -0,0 +1,78 @@
---
title: Options
description: Simple customizable options.
layout: doc
menu:
docs:
parent: "Features"
---
Opts are simple toggle or value options a user can set in Hilbish.
As toggles, there are things like `autocd` or history saving. As values,
there is the `motd` which the user can either change to a custom string or disable.
Opts are accessed from the `hilbish.opts` table. Here they can either
be read or modified
### `autocd`
#### Value: `boolean`
#### Default: `false`
The autocd opt makes it so that lone directories attempted to be executed are
instead set as the shell's directory.
Example:
```
~/Directory
∆ ~
~
∆ Downloads
~/Downloads
∆ ../Documents
~/Documents
```
<hr>
### `history`
#### Value: `boolean`
#### Default: `true`
Sets whether command history will be saved or not.
<hr>
### `greeting`
#### Value: `boolean` or `string`
The greeting is the message that Hilbish shows on startup
(the one which says Welcome to Hilbish).
This can be set to either true/false to enable/disable or a custom greeting string.
<hr>
### `motd`
#### Value: `boolean`
#### Default: `true`
The message of the day shows the current major.minor version and
includes a small range of things added in the current release.
This can be set to `false` to disable the message.
<hr>
### `fuzzy`
#### Value: `boolean`
#### Default: `false`
Toggles the functionality of fuzzy history searching, usable
via the menu in Ctrl-R. Fuzzy searching is an approximate searching
method, which means results that match *closest* will be shown instead
of an exact match.
<hr>
### `notifyJobFinish`
#### Value: `boolean`
#### Default: `true`
If this is enabled, when a background job is finished,
a [notification](../notifications) will be sent.

View File

@ -1,7 +1,24 @@
Hilbish is *unique,* when interactive it first attempts to run input as ---
Lua and then tries shell script. But if you're normal, you wouldn't title: Runner Mode
really be using Hilbish anyway but you'd also not want this description: Customize the interactive script/command runner.
(or maybe want Lua only in some cases.) 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 like
Fennel as the interactive script runner.
Runner mode can also be used to handle specific kinds of input before
evaluating like normal, which is how [Link.hsh](https://github.com/TorchedSammy/Link.hsh)
handles links.
The "runner mode" of Hilbish is customizable via `hilbish.runnerMode`, The "runner mode" of Hilbish is customizable via `hilbish.runnerMode`,
which determines how Hilbish will run user input. By default, this is which determines how Hilbish will run user input. By default, this is
@ -11,28 +28,23 @@ set it to `hybridRev` and for isolated modes there is `sh` and `lua`
respectively. respectively.
You can also set it to a function, which will be called everytime Hilbish You can also set it to a function, which will be called everytime Hilbish
needs to run interactive input. For example, you can set this to a simple needs to run interactive input. For more detail, see the [API documentation](../../api/hilbish/hilbish.runner)
function to compile and evaluate Fennel, and now you can run Fennel.
You can even mix it with sh to make a hybrid mode with Lua replaced by
Fennel.
An example:
hilbish.runnerMode(function(input)
local ok = pcall(fennel.eval, input)
if ok then
return input, 0, nil
end
return hilbish.runner.sh(input)
end)
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode` The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
and also provides the sh and Lua runner functions that Hilbish itself uses. and also provides the shell script and Lua runner functions that Hilbish itself uses.
A runner function is expected to return 3 values: the input, exit code, and an error.
The input return is there incase you need to prompt for more input. A runner function is expected to return a table with the following values:
If you don't, just return the input passed to the runner function. - `exitCode` (number): Exit code of the command
The exit code has to be a number, it will be 0 otherwise and the error can be - `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
`nil` to indicate no error. more is requested.
- `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.
- `<command>: not-found` will throw a `command.not-found` hook
based on what `<command>` is.
- `<command>: not-executable` will throw a `command.not-executable` hook.
- `continue` (boolean): Whether Hilbish should prompt the user for no input
## Functions ## Functions
These are the "low level" functions for the `hilbish.runner` interface. These are the "low level" functions for the `hilbish.runner` interface.
@ -41,21 +53,6 @@ These are the "low level" functions for the `hilbish.runner` interface.
+ sh(input) -> table > Runs `input` in Hilbish's sh interpreter + sh(input) -> table > Runs `input` in Hilbish's sh interpreter
+ lua(input) -> table > Evals `input` as Lua code + 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.
+ `<command>: not-found` will throw a `command.not-found` hook
based on what `<command>` is.
+ `<command>: 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. These functions should be preferred over the previous ones.
+ setCurrent(mode) > The same as `setMode`, but works with runners managed + setCurrent(mode) > The same as `setMode`, but works with runners managed
via the functions below. via the functions below.

View File

@ -15,9 +15,10 @@ To exit, you can either run the `exit` command or hit Ctrl+D.
There are a few ways to make Hilbish your default shell. A simple way is There are a few ways to make Hilbish your default shell. A simple way is
to make it your user/login shell. 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 {{< warning `It is not recommended to set Hilbish as your login shell. That
POSIX compliant shell, which Hilbish is not. At most, there will just be a is expected to be a POSIX compliant shell, which Hilbish is not. Though if
few variables missing in your environment` >}} you still decide to do it, there will just be a few variables missing in
your environment` >}}
To do that, simply run `chsh -s /usr/bin/hilbish`. To do that, simply run `chsh -s /usr/bin/hilbish`.
Some distros (namely Fedora) might have `lchsh` instead, which is used like `lchsh <user>`. Some distros (namely Fedora) might have `lchsh` instead, which is used like `lchsh <user>`.

View File

@ -1,13 +1,11 @@
Here is a list of bait hooks that are thrown by Hilbish. If a hook is related ---
to a command, it will have the `command` scope, as example. title: Signals
description:
layout: doc
weight: -50
menu:
docs
---
Here is the format for a doc for a hook: Signals are global events emitted with the [Bait](../api/bait) module.
+ <hook name> -> <args> > <description> For more detail on how to use these signals, you may check the Bait page.
`<args>` 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.

View File

@ -1,7 +1,67 @@
+ `command.exit` -> code, cmdStr > Thrown when a command exits. ---
`code` is the exit code of the command, and `cmdStr` is the command that was run. title: Command
description:
layout: doc
menu:
docs:
parent: "Signals"
---
+ `command.not-found` -> cmdStr > Thrown when a command is not found. ## command.preexec
Thrown right before a command is executed.
+ `command.not-executable` -> cmdStr > Thrown when Hilbish attempts to run a file #### Variables
that is not executable. `string` **`input`**
The raw string that the user typed. This will include the text
without changes applied to it (argument substitution, alias expansion,
etc.)
`string` **`cmdStr`**
The command that will be directly executed by the current runner.
<hr>
## command.exit
Thrown after the user's ran command is finished.
#### Variables
`number` **`code`**
The exit code of what was executed.
`string` **`cmdStr`**
The command or code that was executed
<hr>
## command.not-found
Thrown if the command attempted to execute was not found.
This can be used to customize the text printed when a command is not found.
Example:
```lua
local bait = require 'bait'
-- Remove any present handlers on `command.not-found`
local notFoundHooks = bait.hooks 'command.not-found'
for _, hook in ipairs(notFoundHooks) do
bait.release('command.not-found', hook)
end
-- then assign custom
bait.catch('command.not-found', function(cmd)
print(string.format('The command "%s" was not found.', cmd))
end)
```
#### Variables
`string` **`cmdStr`**
The name of the command.
<hr>
## command.not-executable
Thrown when the user attempts to run a file that is not executable
(like a text file, or Unix binary without +x permission).
#### Variables
`string` **`cmdStr`**
The name of the command.

View File

@ -1,9 +1,47 @@
+ `hilbish.exit` > Sent when Hilbish is about to exit. ---
title: Hilbish
description:
layout: doc
menu:
docs:
parent: "Signals"
---
+ `hilbish.vimMode` -> modeName > Sent when Hilbish's Vim mode is changed (example insert to normal mode), ## hilbish.exit
`modeName` is the name of the mode changed to (can be `insert`, `normal`, `delete` or `replace`). Sent when Hilbish is going to exit.
#### Variables
This signal returns no variables.
<hr>
## hilbish.vimMode
Sent when the Vim mode of Hilbish is changed (like from insert to normal mode).
This can be used to change the prompt and notify based on Vim mode.
#### Variables
`string` **`modeName`**
The mode that has been set.
Can be these values: `insert`, `normal`, `delete` or `replace`
<hr>
## hilbish.cancel
Sent when the user cancels their command input with Ctrl-C
#### Variables
This signal returns no variables.
<hr>
## hilbish.notification
Thrown when a [notification](../../features/notifications) is sent.
#### Variables
`table` **`notification`**
The notification. The properties are defined in the link above.
<hr>
+ `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something + `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. 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.

View File

@ -1,7 +1,40 @@
+ `signal.sigint` > Sent when Hilbish receives SIGINT (on Ctrl-C). ---
title: Signal
description:
layout: doc
menu:
docs:
parent: "Signals"
---
+ `signal.resize` > Sent when the terminal is resized. ## signal.sigint
Thrown when Hilbish receive the SIGINT signal,
aka when Ctrl-C is pressed.
+ `signal.sigusr1` #### Variables
This signal returns no variables.
<hr>
## signal.resize
Thrown when the terminal is resized.
#### Variables
This signal returns no variables.
<hr>
## signal.sigusr1
Thrown when SIGUSR1 is sent to Hilbish.
#### Variables
This signal returns no variables.
<hr>
## signal.sigusr2
Thrown when SIGUSR2 is sent to Hilbish.
#### Variables
This signal returns no variables.
+ `signal.sigusr2`

View File

@ -1,3 +1,5 @@
(This has mainly been replaced by [hilbish.jobs](../api/hilbish.jobs)).
Hilbish has pretty standard job control. It's missing one or two things, Hilbish has pretty standard job control. It's missing one or two things,
but works well. One thing which is different from other shells but works well. One thing which is different from other shells
(besides Hilbish) itself is the API for jobs, and of course it's in Lua. (besides Hilbish) itself is the API for jobs, and of course it's in Lua.

View File

@ -1,3 +1,10 @@
---
title: Lunacolors
layout: doc
weight: -60
menu: docs
---
Lunacolors is an ANSI color/styling library for Lua. It is included Lunacolors is an ANSI color/styling library for Lua. It is included
by default in standard Hilbish distributions to provide easy styling by default in standard Hilbish distributions to provide easy styling
for things like prompts and text. for things like prompts and text.

View File

@ -1,10 +1,17 @@
---
title: Nature
layout: doc
weight: -90
menu: docs
---
A bit after creation, we have the outside nature. Little plants, seeds, A bit after creation, we have the outside nature. Little plants, seeds,
growing to their final phase: a full plant. A lot of Hilbish itself is growing to their final phase: a full plant. A lot of Hilbish itself is
written in Go, but there are parts made in Lua, being most builtins written in Go, but there are parts made in Lua, being most builtins
(`doc`, `cd`, cdr), completions, and other things. (`doc`, `cd`, cdr), completions, and other things.
Hilbish's Lua core module is called `nature`. It's handled after everything Hilbish's Lua core module is called `nature`.
on the Go side initializes, which is what that first sentence was from. It runs after Hilbish's Go core does.
# Nature Modules # Nature Modules
Currently, `nature` provides 1 intended public module: `nature.dirs`. Currently, `nature` provides 1 intended public module: `nature.dirs`.

View File

@ -0,0 +1,79 @@
---
title: Module dirs
description: No description.
layout: doc
menu:
docs:
parent: "Nature"
---
<hr>
<div id='setOld'>
<h4 class='heading'>
dirs.setOld(d)
<a href="#setOld" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Sets the old directory string.
#### Parameters
`d` **`string`**
</div>
<hr>
<div id='push'>
<h4 class='heading'>
dirs.push()
<a href="#push" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Add `d` to the recent directories list.
#### Parameters
This function has no parameters.
</div>
<hr>
<div id='peak'>
<h4 class='heading'>
dirs.peak(num)
<a href="#peak" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Look at `num` amount of recent directories, starting from the latest.
#### Parameters
`num` **`number`**
</div>
<hr>
<div id='pop'>
<h4 class='heading'>
dirs.pop(num)
<a href="#pop" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Remove the specified amount of dirs from the recent directories list.
#### Parameters
`num` **`number`**
</div>
<hr>
<div id='recent'>
<h4 class='heading'>
dirs.recent(idx)
<a href="#recent" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Get entry from recent directories list based on index.
#### Parameters
`idx` **`number`**
</div>

View File

@ -1,3 +1,10 @@
---
title: Vim Mode
layout: doc
weight: -90
menu: docs
---
Hilbish has a Vim binding input mode accessible for use. Hilbish has a Vim binding input mode accessible for use.
It can be enabled with the `hilbish.inputMode` function (check `doc hilbish`). It can be enabled with the `hilbish.inputMode` function (check `doc hilbish`).

View File

@ -1,3 +1,12 @@
---
title: Actions
layout: doc
weight: -80
menu:
docs:
parent: "Vim Mode"
---
Vim actions are essentially just when a user uses a Vim keybind. Vim actions are essentially just when a user uses a Vim keybind.
Things like yanking and pasting are Vim actions. Things like yanking and pasting are Vim actions.
This is not an "offical Vim thing," just a Hilbish thing. This is not an "offical Vim thing," just a Hilbish thing.

View File

@ -16,6 +16,7 @@ func editorLoader(rtm *rt.Runtime) *rt.Table {
"setVimRegister": {editorSetRegister, 1, false}, "setVimRegister": {editorSetRegister, 1, false},
"getVimRegister": {editorGetRegister, 2, false}, "getVimRegister": {editorGetRegister, 2, false},
"getLine": {editorGetLine, 0, false}, "getLine": {editorGetLine, 0, false},
"readChar": {editorReadChar, 0, false},
} }
mod := rt.NewTable() mod := rt.NewTable()
@ -26,7 +27,8 @@ func editorLoader(rtm *rt.Runtime) *rt.Table {
// #interface editor // #interface editor
// insert(text) // insert(text)
// Inserts text into the line. // Inserts text into the Hilbish command line.
// #param text string
func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -45,8 +47,8 @@ func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface editor // #interface editor
// setVimRegister(register, text) // setVimRegister(register, text)
// Sets the vim register at `register` to hold the passed text. // Sets the vim register at `register` to hold the passed text.
// --- @param register string // #aram register string
// --- @param text string // #param text string
func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -70,7 +72,7 @@ func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface editor // #interface editor
// getVimRegister(register) -> string // getVimRegister(register) -> string
// Returns the text that is at the register. // Returns the text that is at the register.
// --- @param register string // #param register string
func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -89,8 +91,18 @@ func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface editor // #interface editor
// getLine() -> string // getLine() -> string
// Returns the current input line. // Returns the current input line.
// #returns string
func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
buf := lr.rl.GetLine() buf := lr.rl.GetLine()
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
} }
// #interface editor
// getChar() -> string
// Reads a keystroke from the user. This is in a format of something like Ctrl-L.
func editorReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
buf := lr.rl.ReadChar()
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
}

View File

@ -2,31 +2,27 @@
local bait = {} local bait = {}
--- Catches a hook with `name`. Runs the `cb` when it is thrown --- Catches an event. This function can be used to act on events.
--- @param name string ---
--- @param cb function ---
function bait.catch(name, cb) end function bait.catch(name, cb) end
--- Same as catch, but only runs the `cb` once and then removes the hook --- Catches an event, but only once. This will remove the hook immediately after it runs for the first time.
--- @param name string
--- @param cb function
function bait.catchOnce(name, cb) end function bait.catchOnce(name, cb) end
--- Returns a table with hooks (callback functions) on the event with `name`. --- Returns a table of functions that are hooked on an event with the corresponding `name`.
--- @param name string
--- @returns table<function>
function bait.hooks(name) end function bait.hooks(name) end
--- 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 --- For this to work, `catcher` has to be the same function used to catch
--- an event, like one saved to a variable. --- an event, like one saved to a variable.
--- @param name string ---
--- @param catcher function ---
function bait.release(name, catcher) end function bait.release(name, catcher) end
--- Throws a hook with `name` with the provided `args` --- Throws a hook with `name` with the provided `args`.
--- @param name string ---
--- @vararg any ---
function bait.throw(name, ...args) end function bait.throw(name, ...args) end
return bait return bait

View File

@ -2,13 +2,17 @@
local commander = {} local commander = {}
--- Deregisters any command registered with `name` --- Removes the named command. Note that this will only remove Commander-registered commands.
--- @param name string
function commander.deregister(name) end function commander.deregister(name) end
--- Register a command with `name` that runs `cb` when ran --- Adds a new command with the given `name`. When Hilbish has to run a command with a name,
--- @param name string --- it will run the function providing the arguments and sinks.
--- @param cb function ---
---
function commander.register(name, cb) end function commander.register(name, cb) end
--- Returns all registered commanders. Returns a list of tables with the following keys:
--- - `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
function commander.registry() end
return commander return commander

View File

@ -2,56 +2,53 @@
local fs = {} local fs = {}
--- Gives an absolute version of `path`. --- Returns an absolute version of the `path`.
--- @param path string --- This can be used to resolve short paths like `..` to `/home/user`.
--- @returns string
function fs.abs(path) end function fs.abs(path) end
--- Gives the basename of `path`. For the rules, --- Returns the "basename," or the last part of the provided `path`. If path is empty,
--- see Go's filepath.Base --- `.` will be returned.
--- @returns string
function fs.basename(path) end function fs.basename(path) end
--- Changes directory to `dir` --- Changes Hilbish's directory to `dir`.
--- @param dir string
function fs.cd(dir) end function fs.cd(dir) end
--- Returns the directory part of `path`. For the rules, see Go's --- Returns the directory part of `path`. If a file path like
--- filepath.Dir --- `~/Documents/doc.txt` then this function will return `~/Documents`.
--- @param path string
--- @returns string
function fs.dir(path) end function fs.dir(path) end
--- Glob all files and directories that match the pattern. --- Match all files based on the provided `pattern`.
--- For the rules, see Go's filepath.Glob --- For the syntax' refer to Go's filepath.Match function: https://pkg.go.dev/path/filepath#Match
--- @param pattern string ---
--- @returns table ---
function fs.glob(pattern) end function fs.glob(pattern) end
--- Takes paths and joins them together with the OS's --- Takes any list of paths and joins them based on the operating system's path separator.
--- directory separator (forward or backward slash). ---
--- @vararg string ---
--- @returns string function fs.join(...path) end
function fs.join(...) end
--- Makes a directory called `name`. If `recursive` is true, it will create its parent directories. --- Creates a new directory with the provided `name`.
--- @param name string --- With `recursive`, mkdir will create parent directories.
--- @param recursive boolean ---
---
function fs.mkdir(name, recursive) end function fs.mkdir(name, recursive) end
--- Returns a table of files in `dir`. --- Returns a pair of connected files, also known as a pipe.
--- @param dir string --- The type returned is a Lua file, same as returned from `io` functions.
--- @return table function fs.fpipe() end
function fs.readdir(dir) end
--- Returns a table of info about the `path`. --- Returns a list of all files and directories in the provided path.
--- It contains the following keys: function fs.readdir(path) end
--- Returns the information about a given `path`.
--- The returned table contains the following values:
--- name (string) - Name of the path --- name (string) - Name of the path
--- size (number) - Size of the path --- size (number) - Size of the path in bytes
--- mode (string) - Permission mode in an octal format string (with leading 0) --- mode (string) - Unix permission mode in an octal format string (with leading 0)
--- isDir (boolean) - If the path is a directory --- isDir (boolean) - If the path is a directory
--- @param path string ---
--- @returns table ---
function fs.stat(path) end function fs.stat(path) end
return fs return fs

View File

@ -2,84 +2,92 @@
local hilbish = {} local hilbish = {}
--- This is an alias (ha) for the `hilbish.alias` function. --- This is an alias (ha) for the [hilbish.alias](../#alias) function.
--- @param alias string --- @param alias string
--- @param cmd string --- @param cmd string
function hilbish.aliases.add(alias, cmd) end function hilbish.aliases.add(alias, cmd) end
--- This is the same as the `hilbish.runnerMode` function. It takes a callback, --- This is the same as the `hilbish.runnerMode` function.
--- which will be used to execute all interactive input. --- It takes a callback, which will be used to execute all interactive input.
--- In normal cases, neither callbacks should be overrided by the user, --- In normal cases, neither callbacks should be overrided by the user,
--- as the higher level functions listed below this will handle it. --- as the higher level functions listed below this will handle it.
--- @param cb function
function hilbish.runner.setMode(cb) end 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. --- Returns the current input line.
function hilbish.editor.getLine() end function hilbish.editor.getLine() end
--- Returns the text that is at the register. --- Returns the text that is at the register.
--- @param register string
function hilbish.editor.getVimRegister(register) end function hilbish.editor.getVimRegister(register) end
--- Inserts text into the line. --- Inserts text into the Hilbish command line.
function hilbish.editor.insert(text) end function hilbish.editor.insert(text) end
--- Reads a keystroke from the user. This is in a format of something like Ctrl-L.
function hilbish.editor.getChar() end
--- Sets the vim register at `register` to hold the passed text. --- Sets the vim register at `register` to hold the passed text.
--- @param register string
--- @param text string
function hilbish.editor.setVimRegister(register, text) end function hilbish.editor.setVimRegister(register, text) end
--- Sets an alias of `cmd` to `orig` --- Return binaries/executables based on the provided parameters.
--- @param cmd string --- This function is meant to be used as a helper in a command completion handler.
--- @param orig string ---
---
function hilbish.completion.bins(query, ctx, fields) 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 the Completions doc or `doc completions` for info on the `completionGroups` return value.
function hilbish.completion.call(name, query, ctx, fields) end
--- Returns file matches based on the provided parameters.
--- This function is meant to be used as a helper in a command completion handler.
function hilbish.completion.files(query, ctx, fields) end
--- This function contains the general completion handler for Hilbish. This function handles
--- completion of everything, which includes calling other command handlers, binaries, and files.
--- This function can be overriden to supply a custom handler. Note that alias resolution is required to be done in this function.
---
---
function hilbish.completion.handler(line, pos) end
--- Sets an alias, with a name of `cmd` to another command.
---
---
function hilbish.alias(cmd, orig) end function hilbish.alias(cmd, orig) end
--- Appends `dir` to $PATH --- Appends the provided dir to the command path (`$PATH`)
--- @param dir string|table ---
---
function hilbish.appendPath(dir) end function hilbish.appendPath(dir) end
--- Registers a completion handler for `scope`. --- Registers a completion handler for the specified scope.
--- A `scope` is currently only expected to be `command.<cmd>`, --- A `scope` is expected to be `command.<cmd>`,
--- replacing <cmd> with the name of the command (for example `command.git`). --- replacing <cmd> with the name of the command (for example `command.git`).
--- `cb` must be a function that returns a table of "completion groups." --- The documentation for completions, under Features/Completions or `doc completions`
--- Check `doc completions` for more information. --- provides more details.
--- @param scope string ---
--- @param cb function ---
function hilbish.complete(scope, cb) end function hilbish.complete(scope, cb) end
--- Returns the current directory of the shell --- Returns the current directory of the shell.
--- @returns string
function hilbish.cwd() end function hilbish.cwd() end
--- Replaces running hilbish with `cmd` --- Replaces the currently running Hilbish instance with the supplied command.
--- @param cmd string --- This can be used to do an in-place restart.
function hilbish.exec(cmd) end function hilbish.exec(cmd) end
--- Puts `fn` in a goroutine --- Puts `fn` in a Goroutine.
--- @param fn function --- This can be used to run any function in another thread at the same time as other Lua code.
--- **NOTE: THIS FUNCTION MAY CRASH HILBISH IF OUTSIDE VARIABLES ARE ACCESSED.**
--- **This is a limitation of the Lua runtime.**
function hilbish.goro(fn) end function hilbish.goro(fn) end
--- Line highlighter handler. This is mainly for syntax highlighting, but in --- Line highlighter handler.
--- reality could set the input of the prompt to *display* anything. The --- This is mainly for syntax highlighting, but in reality could set the input
--- callback is passed the current line and is expected to return a line that --- of the prompt to *display* anything. The callback is passed the current line
--- will be used as the input display. --- and is expected to return a line that will be used as the input display.
--- @param line string --- Note that to set a highlighter, one has to override this function.
---
function hilbish.highlighter(line) end function hilbish.highlighter(line) end
--- The command line hint handler. It gets called on every key insert to --- The command line hint handler. It gets called on every key insert to
@ -87,99 +95,95 @@ function hilbish.highlighter(line) end
--- line and cursor position. It is expected to return a string which is used --- 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, --- as the text for the hint. This is by default a shim. To set hints,
--- override this function with your custom handler. --- override this function with your custom handler.
--- @param line string ---
--- @param pos number ---
function hilbish.hinter(line, pos) end function hilbish.hinter(line, pos) end
--- Sets the input mode for Hilbish's line reader. Accepts either emacs or vim --- Sets the input mode for Hilbish's line reader.
--- @param mode string --- `emacs` is the default. Setting it to `vim` changes behavior of input to be
--- Vim-like with modes and Vim keybinds.
function hilbish.inputMode(mode) end function hilbish.inputMode(mode) end
--- Runs the `cb` function every `time` milliseconds. --- Runs the `cb` function every specified amount of `time`.
--- This creates a timer that starts immediately. --- This creates a timer that ticking immediately.
--- @param cb function
--- @param time number
--- @return Timer
function hilbish.interval(cb, time) end function hilbish.interval(cb, time) end
--- Changes the continued line prompt to `str` --- Changes the text prompt when Hilbish asks for more input.
--- @param str string --- This will show up when text is incomplete, like a missing quote
---
---
function hilbish.multiprompt(str) end function hilbish.multiprompt(str) end
--- Prepends `dir` to $PATH --- Prepends `dir` to $PATH.
--- @param dir string
function hilbish.prependPath(dir) end function hilbish.prependPath(dir) end
--- Changes the shell prompt to `str` --- Changes the shell prompt to the provided string.
--- There are a few verbs that can be used in the prompt text. --- There are a few verbs that can be used in the prompt text.
--- These will be formatted and replaced with the appropriate values. --- These will be formatted and replaced with the appropriate values.
--- `%d` - Current working directory --- `%d` - Current working directory
--- `%u` - Name of current user --- `%u` - Name of current user
--- `%h` - Hostname of device --- `%h` - Hostname of device
--- @param str string ---
--- @param typ? string Type of prompt, being left or right. Left by default.
function hilbish.prompt(str, typ) end function hilbish.prompt(str, typ) end
--- Read input from the user, using Hilbish's line editor/input reader. --- Read input from the user, using Hilbish's line editor/input reader.
--- This is a separate instance from the one Hilbish actually uses. --- 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) --- Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
--- @param prompt? string
--- @returns string|nil
function hilbish.read(prompt) end function hilbish.read(prompt) end
--- Runs `cmd` in Hilbish's sh interpreter. --- Runs `cmd` in Hilbish's shell script interpreter.
--- If returnOut is true, the outputs of `cmd` will be returned as the 2nd and --- The `streams` parameter specifies the output and input streams the command should use.
--- 3rd values instead of being outputted to the terminal. --- For example, to write command output to a sink.
--- @param cmd string --- As a table, the caller can directly specify the standard output, error, and input
--- @param returnOut boolean --- streams of the command with the table keys `out`, `err`, and `input` respectively.
--- @returns number, string, string --- As a boolean, it specifies whether the command should use standard output or return its output streams.
function hilbish.run(cmd, returnOut) end ---
function hilbish.run(cmd, streams) end
--- Sets the execution/runner mode for interactive Hilbish. This determines whether --- Sets the execution/runner mode for interactive Hilbish.
--- Hilbish wll try to run input as Lua and/or sh or only do one of either. --- 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), --- 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 --- sh, and lua. It also accepts a function, to which if it is passed one
--- will call it to execute user input instead. --- will call it to execute user input instead.
--- @param mode string|function --- Read [about runner mode](../features/runner-mode) for more information.
function hilbish.runnerMode(mode) end function hilbish.runnerMode(mode) end
--- Runs the `cb` function after `time` in milliseconds. --- Executed the `cb` function after a period of `time`.
--- This creates a timer that starts immediately. --- This creates a Timer that starts ticking immediately.
--- @param cb function
--- @param time number
--- @returns Timer
function hilbish.timeout(cb, time) end function hilbish.timeout(cb, time) end
--- Checks if `name` is a valid command. --- Checks if `name` is a valid command.
--- Will return the path of the binary, or a basename if it's a commander. --- 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 function hilbish.which(name) end
--- Puts a job in the background. This acts the same as initially running a job. --- Puts a job in the background. This acts the same as initially running a job.
function hilbish.jobs:background() end 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 --- Puts a job in the foreground. This will cause it to run like it was
--- executed normally and wait for it to complete. --- executed normally and wait for it to complete.
function hilbish.jobs:foreground() end function hilbish.jobs:foreground() end
--- Evaluates `cmd` as Lua input. This is the same as using `dofile` --- Evaluates `cmd` as Lua input. This is the same as using `dofile`
--- or `load`, but is appropriated for the runner interface. --- or `load`, but is appropriated for the runner interface.
--- @param cmd string
function hilbish.runner.lua(cmd) end function hilbish.runner.lua(cmd) end
--- Sets/toggles the option of automatically flushing output.
--- A call with no argument will toggle the value.
--- @param auto boolean|nil
function hilbish:autoFlush(auto) end
--- Flush writes all buffered input to the sink.
function hilbish:flush() end
--- Reads a liine of input from the sink.
--- @returns string
function hilbish:read() end
--- Reads all input from the sink.
--- @returns string
function hilbish:readAll() end
--- Writes data to a sink. --- Writes data to a sink.
function hilbish:write(str) end function hilbish:write(str) end
@ -192,9 +196,12 @@ function hilbish.jobs:start() end
--- Stops the job from running. --- Stops the job from running.
function hilbish.jobs:stop() end function hilbish.jobs:stop() end
--- Loads a module at the designated `path`.
--- It will throw if any error occurs.
function hilbish.module.load(path) end
--- Runs a command in Hilbish's shell script interpreter. --- Runs a command in Hilbish's shell script interpreter.
--- This is the equivalent of using `source`. --- This is the equivalent of using `source`.
--- @param cmd string
function hilbish.runner.sh(cmd) end function hilbish.runner.sh(cmd) end
--- Starts a timer. --- Starts a timer.
@ -204,30 +211,26 @@ function hilbish.timers:start() end
function hilbish.timers:stop() end function hilbish.timers:stop() end
--- Removes an alias. --- Removes an alias.
--- @param name string
function hilbish.aliases.delete(name) end function hilbish.aliases.delete(name) end
--- Get a table of all aliases, with string keys as the alias and the value as the command. --- Get a table of all aliases, with string keys as the alias and the value as the command.
--- @returns table<string, string> ---
---
function hilbish.aliases.list() end function hilbish.aliases.list() end
--- Tries to resolve an alias to its command. --- Resolves an alias to its original command. Will thrown an error if the alias doesn't exist.
--- @param alias string
--- @returns string
function hilbish.aliases.resolve(alias) end function hilbish.aliases.resolve(alias) end
--- Adds a new job to the job table. Note that this does not immediately run it. --- Creates a new job. This function does not run the job. This function is intended to be
--- @param cmdstr string --- used by runners, but can also be used to create jobs via Lua. Commanders cannot be ran as jobs.
--- @param args table ---
--- @param execPath string ---
function hilbish.jobs.add(cmdstr, args, execPath) end function hilbish.jobs.add(cmdstr, args, execPath) end
--- Returns a table of all job objects. --- Returns a table of all job objects.
--- @returns table<Job>
function hilbish.jobs.all() end function hilbish.jobs.all() end
--- Disowns a job. This deletes it from the job table. --- Disowns a job. This simply deletes it from the list of jobs without stopping it.
--- @param id number
function hilbish.jobs.disown(id) end function hilbish.jobs.disown(id) end
--- Get a job object via its ID. --- Get a job object via its ID.
@ -235,39 +238,28 @@ function hilbish.jobs.disown(id) end
--- @returns Job --- @returns Job
function hilbish.jobs.get(id) end function hilbish.jobs.get(id) end
--- Returns the last added job from the table. --- Returns the last added job to the table.
--- @returns Job
function hilbish.jobs.last() end function hilbish.jobs.last() end
--- Adds a command to the history. --- Adds a command to the history.
--- @param cmd string
function hilbish.history.add(cmd) end function hilbish.history.add(cmd) end
--- Retrieves all history. --- Retrieves all history as a table.
--- @returns table
function hilbish.history.all() end function hilbish.history.all() end
--- Deletes all commands from the history. --- Deletes all commands from the history.
function hilbish.history.clear() end function hilbish.history.clear() end
--- Retrieves a command from the history based on the `idx`. --- Retrieves a command from the history based on the `index`.
--- @param idx number function hilbish.history.get(index) end
function hilbish.history.get(idx) end
--- Returns the amount of commands in the history. --- Returns the amount of commands in the history.
--- @returns number
function hilbish.history.size() end function hilbish.history.size() end
--- Creates a timer that runs based on the specified `time` in milliseconds. --- Creates a timer that runs based on the specified `time`.
--- 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 function hilbish.timers.create(type, time, callback) end
--- Retrieves a timer via its ID. --- Retrieves a timer via its ID.
--- @param id number
--- @returns Timer
function hilbish.timers.get(id) end function hilbish.timers.get(id) end
return hilbish return hilbish

View File

@ -5,14 +5,14 @@ local terminal = {}
--- Restores the last saved state of the terminal --- Restores the last saved state of the terminal
function terminal.restoreState() end function terminal.restoreState() end
--- Saves the current state of the terminal --- Saves the current state of the terminal.
function terminal.saveState() end function terminal.saveState() end
--- Puts the terminal in raw mode --- Puts the terminal into raw mode.
function terminal.setRaw() end function terminal.setRaw() end
--- Gets the dimensions of the terminal. Returns a table with `width` and `height` --- 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 --- NOTE: The size refers to the amount of columns and rows of text that can fit in the terminal.
function terminal.size() end function terminal.size() end
return terminal return terminal

49
exec.go
View File

@ -28,6 +28,12 @@ var errNotExec = errors.New("not executable")
var errNotFound = errors.New("not found") var errNotFound = errors.New("not found")
var runnerMode rt.Value = rt.StringValue("hybrid") var runnerMode rt.Value = rt.StringValue("hybrid")
type streams struct {
stdout io.Writer
stderr io.Writer
stdin io.Reader
}
type execError struct{ type execError struct{
typ string typ string
cmd string cmd string
@ -175,6 +181,9 @@ func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8
runnerRet := term.Get(0) runnerRet := term.Get(0)
if runner, ok = runnerRet.TryTable(); !ok { if runner, ok = runnerRet.TryTable(); !ok {
fmt.Fprintln(os.Stderr, "runner did not return a table") fmt.Fprintln(os.Stderr, "runner did not return a table")
exitCode = 125
input = userInput
return
} }
if code, ok := runner.Get(rt.StringValue("exitCode")).TryInt(); ok { if code, ok := runner.Get(rt.StringValue("exitCode")).TryInt(); ok {
@ -233,7 +242,7 @@ func handleSh(cmdString string) (input string, exitCode uint8, cont bool, runErr
} }
func execSh(cmdString string) (string, uint8, bool, error) { func execSh(cmdString string) (string, uint8, bool, error) {
_, _, err := execCommand(cmdString, true) _, _, err := execCommand(cmdString, nil)
if err != nil { if err != nil {
// If input is incomplete, start multiline prompting // If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) { if syntax.IsIncomplete(err) {
@ -254,7 +263,7 @@ func execSh(cmdString string) (string, uint8, bool, error) {
} }
// Run command in sh interpreter // Run command in sh interpreter
func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) { func execCommand(cmd string, strms *streams) (io.Writer, io.Writer, error) {
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "") file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -262,15 +271,24 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
runner, _ := interp.New() runner, _ := interp.New()
var stdout io.Writer if strms == nil {
var stderr io.Writer strms = &streams{}
if terminalOut {
interp.StdIO(os.Stdin, os.Stdout, os.Stderr)(runner)
} else {
stdout = new(bytes.Buffer)
stderr = new(bytes.Buffer)
interp.StdIO(os.Stdin, stdout, stderr)(runner)
} }
if strms.stdout == nil {
strms.stdout = os.Stdout
}
if strms.stderr == nil {
strms.stderr = os.Stderr
}
if strms.stdin == nil {
strms.stdin = os.Stdin
}
interp.StdIO(strms.stdin, strms.stdout, strms.stderr)(runner)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
printer := syntax.NewPrinter() printer := syntax.NewPrinter()
@ -289,11 +307,11 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
interp.ExecHandler(execHandle(bg))(runner) interp.ExecHandler(execHandle(bg))(runner)
err = runner.Run(context.TODO(), stmt) err = runner.Run(context.TODO(), stmt)
if err != nil { if err != nil {
return stdout, stderr, err return strms.stdout, strms.stderr, err
} }
} }
return stdout, stderr, nil return strms.stdout, strms.stderr, nil
} }
func execHandle(bg bool) interp.ExecHandlerFunc { func execHandle(bg bool) interp.ExecHandlerFunc {
@ -324,17 +342,18 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
} }
hc := interp.HandlerCtx(ctx) hc := interp.HandlerCtx(ctx)
if commands[args[0]] != nil { if cmd := cmds.Commands[args[0]]; cmd != nil {
stdin := newSinkInput(hc.Stdin) stdin := newSinkInput(hc.Stdin)
stdout := newSinkOutput(hc.Stdout) stdout := newSinkOutput(hc.Stdout)
stderr := newSinkOutput(hc.Stderr) stderr := newSinkOutput(hc.Stderr)
sinks := rt.NewTable() sinks := rt.NewTable()
sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.ud)) sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.ud))
sinks.Set(rt.StringValue("input"), rt.UserDataValue(stdin.ud))
sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.ud)) sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.ud))
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.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)) luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(cmd), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error()) fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
return interp.NewExitStatus(1) return interp.NewExitStatus(1)
@ -346,7 +365,7 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
exitcode = uint8(code) exitcode = uint8(code)
} else if luaexitcode != rt.NilValue { } else if luaexitcode != rt.NilValue {
// deregister commander // deregister commander
delete(commands, args[0]) delete(cmds.Commands, args[0])
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0]) fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
} }

28
go.mod
View File

@ -1,26 +1,28 @@
module hilbish module hilbish
go 1.17 go 1.18
require ( require (
github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86 github.com/arnodel/golua v0.0.0-20230215163904-e0b5347eaaa1
github.com/blackfireio/osinfo v1.0.3 github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 github.com/blackfireio/osinfo v1.0.5
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036 github.com/maxlandon/readline v1.0.14
github.com/pborman/getopt v1.1.0 github.com/pborman/getopt v1.1.0
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a github.com/sahilm/fuzzy v0.1.1
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 golang.org/x/sys v0.19.0
mvdan.cc/sh/v3 v3.5.1 golang.org/x/term v0.19.0
mvdan.cc/sh/v3 v3.8.0
) )
require ( require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/arnodel/strftime v0.1.6 // indirect github.com/arnodel/strftime v0.1.6 // indirect
github.com/evilsocket/islazy v1.10.6 // indirect github.com/evilsocket/islazy v1.11.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.14.0 // indirect
) )
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b
@ -29,4 +31,4 @@ replace github.com/maxlandon/readline => ./readline
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10 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-20221213193027-cbf6d4e4d345 replace github.com/arnodel/golua => github.com/Rosettea/golua v0.0.0-20240427174124-d239074c1749

69
go.sum
View File

@ -1,34 +1,21 @@
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac h1:dtXrgjch8PQyf7C90anZUquB5U3dr8AcMGJofeuirrI= github.com/Rosettea/golua v0.0.0-20240427174124-d239074c1749 h1:jIFnWBTsYw8s7RX7H2AOXjDVhWP3ol7OzUVaPN2KnGI=
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE= github.com/Rosettea/golua v0.0.0-20240427174124-d239074c1749/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
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-20220621002945-b05143999437 h1:6lWu4YVLeKuZ8jR9xwHONhkHBsrIbw5dpfG1gtOVw0A=
github.com/Rosettea/golua v0.0.0-20220621002945-b05143999437/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.20220306140409-795a84b00b4e h1:P2XupP8SaylWaudD1DqbWtZ3mIa8OsE9635LmR+Q+lg=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs=
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 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/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= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/arnodel/edit v0.0.0-20220202110212-dfc8d7a13890/go.mod h1:AcpttpuZBaL9xl8/CX+Em4fBTUbwIkJ66RiAsJlNrBk=
github.com/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw= github.com/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw=
github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4KTKzJfWs= github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4KTKzJfWs=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504 h1:R1/AOzdMbopSliUTTEHvHbyNmnZ3YxY5GvdhTkpPsSY=
github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c= github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504/go.mod h1:kHBCvAXJIatTX1pw6tLiOspjGc3MhUDRlog9yrCUS+k=
github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA= github.com/blackfireio/osinfo v1.0.5 h1:6hlaWzfcpb87gRmznVf7wSdhysGqLRz9V/xuSdCEXrA=
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4= github.com/blackfireio/osinfo v1.0.5/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/evilsocket/islazy v1.10.6 h1:MFq000a1ByoumoJWlytqg0qon0KlBeUfPsDjY0hK0bo= github.com/evilsocket/islazy v1.11.0 h1:B5w6uuS6ki6iDG+aH/RFeoMb8ijQh/pGabewqp2UeJ0=
github.com/evilsocket/islazy v1.10.6/go.mod h1:OrwQGYg3DuZvXUfmH+KIZDjwTCbrjy48T24TUpGqVVw= github.com/evilsocket/islazy v1.11.0/go.mod h1:muYH4x5MB5YRdkxnrOtrXLIBX6LySj1uFIqys94LKdo=
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/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@ -36,44 +23,32 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= 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/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 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk= 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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
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.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 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
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=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0= mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=

View File

@ -1,9 +1,28 @@
// the event emitter // 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 Bait is the event emitter for Hilbish. Much like Node.js and
// you want to listen in on hooks to know when certain things have its `events` system, many actions in Hilbish emit events.
// happened, like when you've changed directory, a command has failed, Unlike Node.js, Hilbish events are global. So make sure to
// etc. To find all available hooks thrown by Hilbish, see doc hooks. pick a unique name!
Usage of the Bait module consists of userstanding
event-driven architecture, but it's pretty simple:
If you want to act on a certain event, you can `catch` it.
You can act on events via callback functions.
Examples of this are in the Hilbish default config!
Consider this part of it:
```lua
bait.catch('command.exit', function(code)
running = false
doPrompt(code ~= 0)
doNotifyPrompt()
end)
```
What this does is, whenever the `command.exit` event is thrown,
this function will set the user prompt.
*/
package bait package bait
import ( import (
@ -228,31 +247,17 @@ func handleHook(t *rt.Thread, c *rt.GoCont, name string, catcher *rt.Closure, ar
} }
} }
// throw(name, ...args)
// Throws a hook with `name` with the provided `args`
// --- @param name string
// --- @vararg any
func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
name, err := c.StringArg(0)
if err != nil {
return nil, err
}
ifaceSlice := make([]interface{}, len(c.Etc()))
for i, v := range c.Etc() {
ifaceSlice[i] = v
}
b.Emit(name, ifaceSlice...)
return c.Next(), nil
}
// catch(name, cb) // catch(name, cb)
// Catches a hook with `name`. Runs the `cb` when it is thrown // Catches an event. This function can be used to act on events.
// --- @param name string // #param name string The name of the hook.
// --- @param cb function // #param cb function The function that will be called when the hook is thrown.
/*
#example
bait.catch('hilbish.exit', function()
print 'Goodbye Hilbish!'
end)
#example
*/
func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
name, catcher, err := util.HandleStrCallback(t, c) name, catcher, err := util.HandleStrCallback(t, c)
if err != nil { if err != nil {
@ -265,9 +270,9 @@ func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// catchOnce(name, cb) // catchOnce(name, cb)
// Same as catch, but only runs the `cb` once and then removes the hook // Catches an event, but only once. This will remove the hook immediately after it runs for the first time.
// --- @param name string // #param name string The name of the event
// --- @param cb function // #param cb function The function that will be called when the event is thrown.
func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
name, catcher, err := util.HandleStrCallback(t, c) name, catcher, err := util.HandleStrCallback(t, c)
if err != nil { if err != nil {
@ -279,27 +284,10 @@ func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
// 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.
// --- @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 {
return nil, err
}
b.OffLua(name, catcher)
return c.Next(), nil
}
// hooks(name) -> table // hooks(name) -> table
// Returns a table with hooks (callback functions) on the event with `name`. // Returns a table of functions that are hooked on an event with the corresponding `name`.
// --- @param name string // #param name string The name of the hook
// --- @returns table<function> // #returns table<function>
func (b *Bait) bhooks(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (b *Bait) bhooks(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -327,3 +315,62 @@ func (b *Bait) bhooks(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.TableValue(luaHandlers)), nil return c.PushingNext1(t.Runtime, rt.TableValue(luaHandlers)), nil
} }
// 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.
// #param name string Name of the event the hook is on
// #param catcher function Hook function to remove
/*
#example
local hookCallback = function() print 'hi' end
bait.catch('event', hookCallback)
-- a little while later....
bait.release('event', hookCallback)
-- and now hookCallback will no longer be ran for the event.
#example
*/
func (b *Bait) brelease(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
name, catcher, err := util.HandleStrCallback(t, c)
if err != nil {
return nil, err
}
b.OffLua(name, catcher)
return c.Next(), nil
}
// throw(name, ...args)
// #param name string The name of the hook.
// #param args ...any The arguments to pass to the hook.
// Throws a hook with `name` with the provided `args`.
/*
#example
bait.throw('greeting', 'world')
-- This can then be listened to via
bait.catch('gretting', function(greetTo)
print('Hello ' .. greetTo)
end)
#example
*/
func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
name, err := c.StringArg(0)
if err != nil {
return nil, err
}
ifaceSlice := make([]interface{}, len(c.Etc()))
for i, v := range c.Etc() {
ifaceSlice[i] = v
}
b.Emit(name, ifaceSlice...)
return c.Next(), nil
}

View File

@ -1,10 +1,9 @@
// library for custom commands // library for custom commands
/* /*
Commander is a library for writing custom commands in Lua. Commander is the library which handles Hilbish commands. This makes
In order to make it easier to write commands for Hilbish, the user able to add Lua-written commands to their shell without making
not require separate scripts and to be able to use in a config, a separate script in a bin folder. Instead, you may simply use the Commander
the Commander library exists. This is like a very simple wrapper library in your Hilbish config.
that works with Hilbish for writing commands. Example:
```lua ```lua
local commander = require 'commander' local commander = require 'commander'
@ -18,15 +17,18 @@ In this example, a command with the name of `hello` is created
that will print `Hello world!` to output. One question you may that will print `Hello world!` to output. One question you may
have is: What is the `sinks` parameter? have is: What is the `sinks` parameter?
The `sinks` parameter is a table with 3 keys: `in`, `out`, The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
and `err`. The values of these is a @Sink. There is an `in` alias to `input`, but it requires using the string accessor syntax (`sinks['in']`)
as `in` is also a Lua keyword, so `input` is preferred for use.
All of them are a @Sink.
In the future, `sinks.in` will be removed.
- `in` is the standard input. You can read from this sink - `in` is the standard input.
to get user input. (**This is currently unimplemented.**) You may use the read functions on this sink to get input from the user.
- `out` is standard output. This is usually where text meant for - `out` is standard output.
output should go. This is usually where command output should go.
- `err` is standard error. This sink is for writing errors, as the - `err` is standard error.
name would suggest. This sink is for writing errors, as the name would suggest.
*/ */
package commander package commander
@ -41,11 +43,13 @@ import (
type Commander struct{ type Commander struct{
Events *bait.Bait Events *bait.Bait
Loader packagelib.Loader Loader packagelib.Loader
Commands map[string]*rt.Closure
} }
func New(rtm *rt.Runtime) Commander { func New(rtm *rt.Runtime) *Commander {
c := Commander{ c := &Commander{
Events: bait.New(rtm), Events: bait.New(rtm),
Commands: make(map[string]*rt.Closure),
} }
c.Loader = packagelib.Loader{ c.Loader = packagelib.Loader{
Load: c.loaderFunc, Load: c.loaderFunc,
@ -59,6 +63,7 @@ func (c *Commander) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
exports := map[string]util.LuaExport{ exports := map[string]util.LuaExport{
"register": util.LuaExport{c.cregister, 2, false}, "register": util.LuaExport{c.cregister, 2, false},
"deregister": util.LuaExport{c.cderegister, 1, false}, "deregister": util.LuaExport{c.cderegister, 1, false},
"registry": util.LuaExport{c.cregistry, 0, false},
} }
mod := rt.NewTable() mod := rt.NewTable()
util.SetExports(rtm, mod, exports) util.SetExports(rtm, mod, exports)
@ -67,23 +72,36 @@ func (c *Commander) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
} }
// register(name, cb) // register(name, cb)
// Register a command with `name` that runs `cb` when ran // Adds a new command with the given `name`. When Hilbish has to run a command with a name,
// --- @param name string // it will run the function providing the arguments and sinks.
// --- @param cb function // #param name string Name of the command
// #param cb function Callback to handle command invocation
/*
#example
-- When you run the command `hello` in the shell, it will print `Hello world`.
-- If you run it with, for example, `hello Hilbish`, it will print 'Hello Hilbish'
commander.register('hello', function(args, sinks)
local name = 'world'
if #args > 0 then name = args[1] end
sinks.out:writeln('Hello ' .. name)
end)
#example
*/
func (c *Commander) cregister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) { func (c *Commander) cregister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
cmdName, cmd, err := util.HandleStrCallback(t, ct) cmdName, cmd, err := util.HandleStrCallback(t, ct)
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.Events.Emit("commandRegister", cmdName, cmd) c.Commands[cmdName] = cmd
return ct.Next(), err return ct.Next(), err
} }
// deregister(name) // deregister(name)
// Deregisters any command registered with `name` // Removes the named command. Note that this will only remove Commander-registered commands.
// --- @param name string // #param name string Name of the command to remove.
func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) { func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
if err := ct.Check1Arg(); err != nil { if err := ct.Check1Arg(); err != nil {
return nil, err return nil, err
@ -93,7 +111,23 @@ func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
c.Events.Emit("commandDeregister", cmdName) delete(c.Commands, cmdName)
return ct.Next(), err return ct.Next(), err
} }
// registry() -> table
// Returns all registered commanders. Returns a list of tables with the following keys:
// - `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
// #returns table
func (c *Commander) cregistry(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
registryLua := rt.NewTable()
for cmdName, cmd := range c.Commands {
cmdTbl := rt.NewTable()
cmdTbl.Set(rt.StringValue("exec"), rt.FunctionValue(cmd))
registryLua.Set(rt.StringValue(cmdName), rt.TableValue(cmdTbl))
}
return ct.PushingNext1(t.Runtime, rt.TableValue(registryLua)), nil
}

View File

@ -1,7 +1,10 @@
// filesystem interaction and functionality library // 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 The fs module provides filesystem functions to Hilbish. While Lua's standard
// I/O and filesystem functions. library has some I/O functions, they're missing a lot of the basics. The `fs`
library offers more functions and will work on any operating system Hilbish does.
#field pathSep The operating system's path separator.
*/
package fs package fs
import ( import (
@ -15,6 +18,7 @@ import (
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib" "github.com/arnodel/golua/lib/packagelib"
"github.com/arnodel/golua/lib/iolib"
) )
var Loader = packagelib.Loader{ var Loader = packagelib.Loader{
@ -33,6 +37,7 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
"dir": util.LuaExport{fdir, 1, false}, "dir": util.LuaExport{fdir, 1, false},
"glob": util.LuaExport{fglob, 1, false}, "glob": util.LuaExport{fglob, 1, false},
"join": util.LuaExport{fjoin, 0, true}, "join": util.LuaExport{fjoin, 0, true},
"pipe": util.LuaExport{fpipe, 0, false},
} }
mod := rt.NewTable() mod := rt.NewTable()
util.SetExports(rtm, mod, exports) util.SetExports(rtm, mod, exports)
@ -42,9 +47,46 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
return rt.TableValue(mod), nil return rt.TableValue(mod), nil
} }
// abs(path) -> string
// Returns an absolute version of the `path`.
// This can be used to resolve short paths like `..` to `/home/user`.
// #param path string
// #returns string
func fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
path, err := c.StringArg(0)
if err != nil {
return nil, err
}
path = util.ExpandHome(path)
abspath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
return c.PushingNext1(t.Runtime, rt.StringValue(abspath)), nil
}
// basename(path) -> string
// Returns the "basename," or the last part of the provided `path`. If path is empty,
// `.` will be returned.
// #param path string Path to get the base name of.
// #returns string
func fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
path, err := c.StringArg(0)
if err != nil {
return nil, err
}
return c.PushingNext(t.Runtime, rt.StringValue(filepath.Base(path))), nil
}
// cd(dir) // cd(dir)
// Changes directory to `dir` // Changes Hilbish's directory to `dir`.
// --- @param dir string // #param dir string Path to change directory to.
func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -63,10 +105,103 @@ func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), err return c.Next(), err
} }
// dir(path) -> string
// Returns the directory part of `path`. If a file path like
// `~/Documents/doc.txt` then this function will return `~/Documents`.
// #param path string Path to get the directory for.
// #returns string
func fdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
path, err := c.StringArg(0)
if err != nil {
return nil, err
}
return c.PushingNext(t.Runtime, rt.StringValue(filepath.Dir(path))), nil
}
// glob(pattern) -> matches (table)
// Match all files based on the provided `pattern`.
// For the syntax' refer to Go's filepath.Match function: https://pkg.go.dev/path/filepath#Match
// #param pattern string Pattern to compare files with.
// #returns table A list of file names/paths that match.
/*
#example
--[[
Within a folder that contains the following files:
a.txt
init.lua
code.lua
doc.pdf
]]--
local matches = fs.glob './*.lua'
print(matches)
-- -> {'init.lua', 'code.lua'}
#example
*/
func fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
pattern, err := c.StringArg(0)
if err != nil {
return nil, err
}
matches, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
luaMatches := rt.NewTable()
for i, match := range matches {
luaMatches.Set(rt.IntValue(int64(i + 1)), rt.StringValue(match))
}
return c.PushingNext(t.Runtime, rt.TableValue(luaMatches)), nil
}
// join(...path) -> string
// Takes any list of paths and joins them based on the operating system's path separator.
// #param path ...string Paths to join together
// #returns string The joined path.
/*
#example
-- This prints the directory for Hilbish's config!
print(fs.join(hilbish.userDir.config, 'hilbish'))
-- -> '/home/user/.config/hilbish' on Linux
#example
*/
func fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
strs := make([]string, len(c.Etc()))
for i, v := range c.Etc() {
if v.Type() != rt.StringType {
// +2; go indexes of 0 and first arg from above
return nil, fmt.Errorf("bad argument #%d to run (expected string, got %s)", i + 1, v.TypeName())
}
strs[i] = v.AsString()
}
res := filepath.Join(strs...)
return c.PushingNext(t.Runtime, rt.StringValue(res)), nil
}
// mkdir(name, recursive) // mkdir(name, recursive)
// Makes a directory called `name`. If `recursive` is true, it will create its parent directories. // Creates a new directory with the provided `name`.
// --- @param name string // With `recursive`, mkdir will create parent directories.
// --- @param recursive boolean // #param name string Name of the directory
// #param recursive boolean Whether to create parent directories for the provided name
/*
#example
-- This will create the directory foo, then create the directory bar in the
-- foo directory. If recursive is false in this case, it will fail.
fs.mkdir('./foo/bar', true)
#example
*/
func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil { if err := c.CheckNArgs(2); err != nil {
return nil, err return nil, err
@ -93,15 +228,74 @@ func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), err return c.Next(), err
} }
// fpipe() -> File, File
// Returns a pair of connected files, also known as a pipe.
// The type returned is a Lua file, same as returned from `io` functions.
// #returns File
// #returns File
func fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
rf, wf, err := os.Pipe()
if err != nil {
return nil, err
}
rfLua := iolib.NewFile(rf, 0)
wfLua := iolib.NewFile(wf, 0)
return c.PushingNext(t.Runtime, rfLua.Value(t.Runtime), wfLua.Value(t.Runtime)), nil
}
// readdir(path) -> table[string]
// Returns a list of all files and directories in the provided path.
// #param dir string
// #returns table
func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
dir, err := c.StringArg(0)
if err != nil {
return nil, err
}
dir = util.ExpandHome(dir)
names := rt.NewTable()
dirEntries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for i, entry := range dirEntries {
names.Set(rt.IntValue(int64(i + 1)), rt.StringValue(entry.Name()))
}
return c.PushingNext1(t.Runtime, rt.TableValue(names)), nil
}
// stat(path) -> {} // stat(path) -> {}
// Returns a table of info about the `path`. // Returns the information about a given `path`.
// It contains the following keys: // The returned table contains the following values:
// name (string) - Name of the path // name (string) - Name of the path
// size (number) - Size of the path // size (number) - Size of the path in bytes
// mode (string) - Permission mode in an octal format string (with leading 0) // mode (string) - Unix permission mode in an octal format string (with leading 0)
// isDir (boolean) - If the path is a directory // isDir (boolean) - If the path is a directory
// --- @param path string // #param path string
// --- @returns table // #returns table
/*
#example
local inspect = require 'inspect'
local stat = fs.stat '~'
print(inspect(stat))
--[[
Would print the following:
{
isDir = true,
mode = "0755",
name = "username",
size = 12288
}
]]--
#example
*/
func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -125,128 +319,3 @@ func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), nil return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), nil
} }
// 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) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
dir, err := c.StringArg(0)
if err != nil {
return nil, err
}
dir = util.ExpandHome(dir)
names := rt.NewTable()
dirEntries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for i, entry := range dirEntries {
names.Set(rt.IntValue(int64(i + 1)), rt.StringValue(entry.Name()))
}
return c.PushingNext1(t.Runtime, rt.TableValue(names)), nil
}
// 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 {
return nil, err
}
path = util.ExpandHome(path)
abspath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
return c.PushingNext1(t.Runtime, rt.StringValue(abspath)), nil
}
// 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
}
path, err := c.StringArg(0)
if err != nil {
return nil, err
}
return c.PushingNext(t.Runtime, rt.StringValue(filepath.Base(path))), nil
}
// 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
}
path, err := c.StringArg(0)
if err != nil {
return nil, err
}
return c.PushingNext(t.Runtime, rt.StringValue(filepath.Dir(path))), nil
}
// 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
}
pattern, err := c.StringArg(0)
if err != nil {
return nil, err
}
matches, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
luaMatches := rt.NewTable()
for i, match := range matches {
luaMatches.Set(rt.IntValue(int64(i + 1)), rt.StringValue(match))
}
return c.PushingNext(t.Runtime, rt.TableValue(luaMatches)), nil
}
// 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() {
if v.Type() != rt.StringType {
// +2; go indexes of 0 and first arg from above
return nil, fmt.Errorf("bad argument #%d to run (expected string, got %s)", i + 1, v.TypeName())
}
strs[i] = v.AsString()
}
res := filepath.Join(strs...)
return c.PushingNext(t.Runtime, rt.StringValue(res)), nil
}

View File

@ -34,7 +34,7 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
// size() // size()
// Gets the dimensions of the terminal. Returns a table with `width` and `height` // 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 // NOTE: The size refers to the amount of columns and rows of text that can fit in the terminal.
func termsize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func termsize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
w, h, err := term.GetSize(int(os.Stdin.Fd())) w, h, err := term.GetSize(int(os.Stdin.Fd()))
if err != nil { if err != nil {
@ -49,7 +49,7 @@ func termsize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// saveState() // saveState()
// Saves the current state of the terminal // Saves the current state of the terminal.
func termsaveState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func termsaveState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
state, err := term.GetState(int(os.Stdin.Fd())) state, err := term.GetState(int(os.Stdin.Fd()))
if err != nil { if err != nil {
@ -72,7 +72,7 @@ func termrestoreState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// setRaw() // setRaw()
// Puts the terminal in raw mode // Puts the terminal into raw mode.
func termsetRaw(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func termsetRaw(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
_, err := term.MakeRaw(int(os.Stdin.Fd())) _, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil { if err != nil {

26
job.go
View File

@ -414,10 +414,16 @@ func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface jobs // #interface jobs
// add(cmdstr, args, execPath) // add(cmdstr, args, execPath)
// Adds a new job to the job table. Note that this does not immediately run it. // Creates a new job. This function does not run the job. This function is intended to be
// --- @param cmdstr string // used by runners, but can also be used to create jobs via Lua. Commanders cannot be ran as jobs.
// --- @param args table // #param cmdstr string String that a user would write for the job
// --- @param execPath string // #param args table Arguments for the commands. Has to include the name of the command.
// #param execPath string Binary to use to run the command. Needs to be an absolute path.
/*
#example
hilbish.jobs.add('go build', {'go', 'build'}, '/usr/bin/go')
#example
*/
func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(3); err != nil { if err := c.CheckNArgs(3); err != nil {
return nil, err return nil, err
@ -448,9 +454,9 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
// #interface jobs // #interface jobs
// all() -> table<@Job> // all() -> table[@Job]
// Returns a table of all job objects. // Returns a table of all job objects.
// --- @returns table<Job> // #returns table[Job]
func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j.mu.RLock() j.mu.RLock()
defer j.mu.RUnlock() defer j.mu.RUnlock()
@ -465,8 +471,8 @@ func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface jobs // #interface jobs
// disown(id) // disown(id)
// Disowns a job. This deletes it from the job table. // Disowns a job. This simply deletes it from the list of jobs without stopping it.
// --- @param id number // #param id number
func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil { if err := c.Check1Arg(); err != nil {
return nil, err return nil, err
@ -486,8 +492,8 @@ func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface jobs // #interface jobs
// last() -> @Job // last() -> @Job
// Returns the last added job from the table. // Returns the last added job to the table.
// --- @returns Job // #returns Job
func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j.mu.RLock() j.mu.RLock()
defer j.mu.RUnlock() defer j.mu.RUnlock()

16
lua.go
View File

@ -33,19 +33,7 @@ func luaInit() {
lib.LoadLibs(l, fs.Loader) lib.LoadLibs(l, fs.Loader)
lib.LoadLibs(l, terminal.Loader) lib.LoadLibs(l, terminal.Loader)
cmds := commander.New(l) cmds = commander.New(l)
// When a command from Lua is added, register it for use
cmds.Events.On("commandRegister", func(args ...interface{}) {
cmdName := args[0].(string)
cmd := args[1].(*rt.Closure)
commands[cmdName] = cmd
})
cmds.Events.On("commandDeregister", func(args ...interface{}) {
cmdName := args[0].(string)
delete(commands, cmdName)
})
lib.LoadLibs(l, cmds.Loader) lib.LoadLibs(l, cmds.Loader)
hooks = bait.New(l) hooks = bait.New(l)
@ -68,7 +56,7 @@ func luaInit() {
} }
// Add more paths that Lua can require from // Add more paths that Lua can require from
err := util.DoString(l, "package.path = package.path .. " + requirePaths) _, err := util.DoString(l, "package.path = package.path .. " + requirePaths)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.") fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
} }

21
main.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -9,9 +10,11 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"syscall"
"hilbish/util" "hilbish/util"
"hilbish/golibs/bait" "hilbish/golibs/bait"
"hilbish/golibs/commander"
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/pborman/getopt" "github.com/pborman/getopt"
@ -23,7 +26,6 @@ var (
l *rt.Runtime l *rt.Runtime
lr *lineReader lr *lineReader
commands = map[string]*rt.Closure{}
luaCompletions = map[string]*rt.Closure{} luaCompletions = map[string]*rt.Closure{}
confDir string confDir string
@ -31,6 +33,7 @@ var (
curuser *user.User curuser *user.User
hooks *bait.Bait hooks *bait.Bait
cmds *commander.Commander
defaultConfPath string defaultConfPath string
defaultHistPath string defaultHistPath string
) )
@ -88,7 +91,7 @@ func main() {
interactive = true interactive = true
} }
if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 { if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 || !term.IsTerminal(int(os.Stdin.Fd())) {
interactive = false interactive = false
} }
@ -106,7 +109,7 @@ func main() {
} }
if *verflag { if *verflag {
fmt.Printf("Hilbish %s\n", getVersion()) fmt.Printf("Hilbish %s\nCompiled with %s\n", getVersion(), runtime.Version())
os.Exit(0) os.Exit(0)
} }
@ -189,8 +192,12 @@ input:
} else { } else {
// If we get a completely random error, print // If we get a completely random error, print
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
if errors.Is(err, syscall.ENOTTY) {
// what are we even doing here?
panic("not a tty")
}
<-make(chan struct{})
} }
// TODO: Halt if any other error occurs
continue continue
} }
var priv bool var priv bool
@ -289,7 +296,7 @@ func removeDupes(slice []string) []string {
func contains(s []string, e string) bool { func contains(s []string, e string) bool {
for _, a := range s { for _, a := range s {
if a == e { if strings.ToLower(a) == strings.ToLower(e) {
return true return true
} }
} }
@ -324,3 +331,7 @@ func getVersion() string {
return v.String() return v.String()
} }
func cut(slice []string, idx int) []string {
return append(slice[:idx], slice[idx + 1:]...)
}

93
module.go 100644
View File

@ -0,0 +1,93 @@
package main
import (
"plugin"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
)
// #interface module
// native module loading
// #field paths A list of paths to search when loading native modules. This is in the style of Lua search paths and will be used when requiring native modules. Example: `?.so;?/?.so`
/*
The hilbish.module interface provides a function to load
Hilbish plugins/modules. Hilbish modules are Go-written
plugins (see https://pkg.go.dev/plugin) that are used to add functionality
to Hilbish that cannot be written in Lua for any reason.
Note that you don't ever need to use the load function that is here as
modules can be loaded with a `require` call like Lua C modules, and the
search paths can be changed with the `paths` property here.
To make a valid native module, the Go plugin has to export a Loader function
with a signature like so: `func(*rt.Runtime) rt.Value`.
`rt` in this case refers to the Runtime type at
https://pkg.go.dev/github.com/arnodel/golua@master/runtime#Runtime
Hilbish uses this package as its Lua runtime. You will need to read
it to use it for a native plugin.
Here is some code for an example plugin:
```go
package main
import (
rt "github.com/arnodel/golua/runtime"
)
func Loader(rtm *rt.Runtime) rt.Value {
return rt.StringValue("hello world!")
}
```
This can be compiled with `go build -buildmode=plugin plugin.go`.
If you attempt to require and print the result (`print(require 'plugin')`), it will show "hello world!"
*/
func moduleLoader(rtm *rt.Runtime) *rt.Table {
exports := map[string]util.LuaExport{
"load": {moduleLoad, 2, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
return mod
}
// #interface module
// load(path)
// Loads a module at the designated `path`.
// It will throw if any error occurs.
// #param path string
func moduleLoad(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(1); err != nil {
return nil, err
}
path, err := c.StringArg(0)
if err != nil {
return nil, err
}
p, err := plugin.Open(path)
if err != nil {
return nil, err
}
value, err := p.Lookup("Loader")
if err != nil {
return nil, err
}
loader, ok := value.(func(*rt.Runtime) rt.Value)
if !ok {
return nil, nil
}
val := loader(t.Runtime)
return c.PushingNext1(t.Runtime, val), nil
}

View File

@ -7,7 +7,7 @@ commander.register('bg', function(_, sinks)
return 1 return 1
end end
local err = job.background() local err = job:background()
if err then if err then
sinks.out:writeln('bg: ' .. err) sinks.out:writeln('bg: ' .. err)
return 2 return 2

View File

@ -9,6 +9,8 @@ commander.register('cat', function(args, sinks)
usage: cat [file]...]] usage: cat [file]...]]
end end
local chunkSize = 2^13 -- 8K buffer size
for _, fName in ipairs(args) do for _, fName in ipairs(args) do
local f = io.open(fName) local f = io.open(fName)
if f == nil then if f == nil then
@ -17,7 +19,11 @@ usage: cat [file]...]]
goto continue goto continue
end end
sinks.out:writeln(f:read '*a') while true do
local block = f:read(chunkSize)
if not block then break end
sinks.out:write(block)
end
::continue:: ::continue::
end end
io.flush() io.flush()

View File

@ -10,7 +10,7 @@ cdr: change directory to one which has been recently visied
usage: cdr <index> usage: cdr <index>
to get a list of recent directories, use {green}{underline}cdr list{reset}]]) to get a list of recent directories, use {green}cdr list{reset}]])
return return
end end
@ -20,7 +20,10 @@ to get a list of recent directories, use {green}{underline}cdr list{reset}]])
sinks.out:writeln 'No directories have been visited.' sinks.out:writeln 'No directories have been visited.'
return 1 return 1
end end
sinks.out:writeln(table.concat(recentDirs, '\n')) for idx, d in ipairs(dirs.recentDirs) do
if d:find(hilbish.home, 1, true) then d = fs.join('~', d:sub(hilbish.home:len() + 1)) end
sinks.out:writeln(lunacolors.format(string.format('{cyan}%d{reset} %s', idx, d)))
end
return return
end end

View File

@ -1,6 +1,42 @@
local ansikit = require 'ansikit'
local commander = require 'commander' local commander = require 'commander'
local fs = require 'fs' local fs = require 'fs'
local lunacolors = require 'lunacolors' local lunacolors = require 'lunacolors'
local Greenhouse = require 'nature.greenhouse'
local Page = require 'nature.greenhouse.page'
local docfuncs = require 'nature.doc'
local function strip(text, ...)
for _, pat in ipairs {...} do
text = text:gsub(pat, '\n')
end
return text
end
local function transformHTMLandMD(text)
return strip(text, '|||', '|%-%-%-%-|%-%-%-%-|')
:gsub('|(.-)|(.-)|', function(entry1, entry2)
return string.format('%s - %s', entry1, entry2)
end)
:gsub('<hr>', '{separator}')
:gsub('<.->', '')
--:gsub('^\n\n', '\n')
:gsub('\n%s+\n', '\n\n')
--:gsub(' \n', '\n\n')
:gsub('{{< (%w+) `(.-)` >}}', function(shortcode, text)
return docfuncs.renderInfoBlock(shortcode, text)
end)
:gsub('```(%w+)\n(.-)```', function(lang, text)
return docfuncs.renderCodeBlock(text)
end)
:gsub('```\n(.-)\n```', function(text)
return docfuncs.renderCodeBlock(text)
end)
:gsub('`[^\n].-`', lunacolors.cyan)
:gsub('#+ (.-\n)', function(heading) return lunacolors.blue(lunacolors.bold('' .. heading)) end)
:gsub('%*%*(.-)%*%*', lunacolors.bold)
end
commander.register('doc', function(args, sinks) commander.register('doc', function(args, sinks)
local moddocPath = hilbish.dataDir .. '/docs/' local moddocPath = hilbish.dataDir .. '/docs/'
@ -9,11 +45,6 @@ commander.register('doc', function(args, sinks)
-- hilbish git -- hilbish git
moddocPath = './docs/' moddocPath = './docs/'
end end
local apidocHeader = [[
# %s
{grayBg} {white}{italic}%s {reset}
]]
local modules = table.map(fs.readdir(moddocPath), function(f) local modules = table.map(fs.readdir(moddocPath), function(f)
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', ''))) return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', '')))
@ -25,47 +56,18 @@ to Hilbish.
Usage: doc <section> [subdoc] Usage: doc <section> [subdoc]
Available sections: ]] .. table.concat(modules, ', ') Available sections: ]] .. table.concat(modules, ', ')
if #args > 0 then local f
local mod = args[1] local function handleYamlInfo(d)
local f = io.open(moddocPath .. mod .. '.md', 'rb')
local funcdocs = nil
local subdocName = args[2]
if not f then
-- assume subdir
-- dataDir/docs/<mod>/<mod>.md
moddocPath = moddocPath .. mod .. '/'
if not subdocName then
subdocName = '_index'
end
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
if not f then
f = io.open(moddocPath .. subdocName:match '%w+' .. '/' .. subdocName .. '.md', 'rb')
end
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
local valsStr = funcdocs:match '%-%-%-\n([^%-%-%-]+)\n'
local vals = {} local vals = {}
local docs = d
local valsStr = docs:match '^%-%-%-\n.-\n%-%-%-'
if valsStr then if valsStr then
local _, endpos = funcdocs:find('---\n' .. valsStr .. '\n---\n\n', 1, true) docs = docs:sub(valsStr:len() + 2, #docs)
funcdocs = funcdocs:sub(endpos + 1, #funcdocs) local pre = docs:sub(1, 1)
if pre == '\n' then
docs = docs:sub(2)
end
-- parse vals -- parse vals
local lines = string.split(valsStr, '\n') local lines = string.split(valsStr, '\n')
@ -78,23 +80,113 @@ Available sections: ]] .. table.concat(modules, ', ')
end end
end end
end end
if mod == 'api' then
funcdocs = string.format(apidocHeader, vals.title, vals.description or 'no description.') .. funcdocs --docs = docs:sub(1, #docs - 1)
end return docs, vals
doc = funcdocs:sub(1, #funcdocs - 1)
f:close()
end end
local backtickOccurence = 0 if #args > 0 then
sinks.out:writeln(lunacolors.format(doc:gsub('`', function() local mod = args[1]
backtickOccurence = backtickOccurence + 1
if backtickOccurence % 2 == 0 then f = io.open(moddocPath .. mod .. '.md', 'rb')
return '{reset}' local funcdocs = nil
else local subdocName = args[2]
return '{underline}{green}' if not f then
moddocPath = moddocPath .. mod .. '/'
if not subdocName then
subdocName = '_index'
end
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
local oldmoddocPath = moddocPath
if not f then
moddocPath = moddocPath .. subdocName:match '%w+' .. '/'
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
end
if not f then
moddocPath = oldmoddocPath .. 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 end
end):gsub('\n#+.-\n', function(t)
local signature = t:gsub('<.->(.-)</.->', '{underline}%1'):gsub('\\', '<') end
return '{bold}{yellow}' .. signature .. '{reset}'
end))) 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)
local gh = Greenhouse(sinks.out)
function gh:resize()
local size = terminal.size()
self.region = {
width = size.width,
height = size.height - 1
}
end
gh:resize()
function gh:render()
local workingPage = self.pages[self.curPage]
local offset = self.offset
if self.isSpecial then
offset = self.specialOffset
workingPage = self.specialPage
end
local size = terminal.size()
self.sink:write(ansikit.getCSI(size.height - 1 .. ';1', 'H'))
self.sink:write(ansikit.getCSI(0, 'J'))
if not self.isSpecial then
if args[1] == 'api' then
self.sink:writeln(workingPage.title)
self.sink:write(lunacolors.format(string.format('{grayBg} ↳ {white}{italic}%s {reset}', workingPage.description or 'No description.')))
else
self.sink:write(lunacolors.reset(string.format('Viewing doc page %s', moddocPath)))
end
end
end
local backtickOccurence = 0
local function formatDocText(d)
return transformHTMLandMD(d)
--[[
return lunacolors.format(d:gsub('`(.-)`', function(t)
return docfuncs.renderCodeBlock(t)
end):gsub('\n#+.-\n', function(t)
local signature = t:gsub('<.->(.-)</.->', '{underline}%1'):gsub('\\', '<')
return '{bold}{yellow}' .. signature .. '{reset}'
end))
]]--
end
local doc, vals = handleYamlInfo(#args == 0 and doc or formatDocText(f:read '*a'))
if #moddocs ~= 0 and f then
doc = doc .. '\nSubdocs: ' .. table.concat(subdocs, ', ') .. '\n\n'
end
if f then f:close() end
local page = Page(vals.title, doc)
page.description = vals.description
gh:addPage(page)
-- add subdoc pages
for _, sdName in ipairs(moddocs) do
local sdFile = fs.join(sdName, '_index.md')
if sdName:match '.md$' then
sdFile = sdName
end
local f = io.open(moddocPath .. sdFile, 'rb')
local doc, vals = handleYamlInfo(formatDocText(f:read '*a'))
local page = Page(vals.title or sdName, doc)
page.description = vals.description
gh:addPage(page)
end
ansikit.hideCursor()
gh:initUi()
end) end)

View File

@ -7,7 +7,7 @@ commander.register('fg', function(_, sinks)
return 1 return 1
end end
local err = job.foreground() -- waits for job; blocks local err = job:foreground() -- waits for job; blocks
if err then if err then
sinks.out:writeln('fg: ' .. err) sinks.out:writeln('fg: ' .. err)
return 2 return 2

View File

@ -0,0 +1,124 @@
local ansikit = require 'ansikit'
local bait = require 'bait'
local commander = require 'commander'
local hilbish = require 'hilbish'
local lunacolors = require 'lunacolors'
local terminal = require 'terminal'
local Greenhouse = require 'nature.greenhouse'
local Page = require 'nature.greenhouse.page'
commander.register('greenhouse', function(args, sinks)
local gh = Greenhouse(sinks.out)
local buffer = ''
local display = ''
local command = false
local commands = {
q = function()
gh.keybinds['Ctrl-D'](gh)
end,
['goto'] = function(args)
if not args[1] then
return 'nuh uh'
end
gh:jump(tonumber(args[1]))
end
}
function gh:resize()
local size = terminal.size()
self.region = {
width = size.width,
height = size.height - 2
}
end
function gh:render()
local workingPage = self.pages[self.curPage]
local offset = self.offset
if self.isSpecial then
offset = self.specialOffset
workingPage = self.specialPage
end
self.sink:write(ansikit.getCSI(self.region.height + 1 .. ';1', 'H'))
if not self.isSpecial then
self.sink:writeln(lunacolors.format(string.format('{grayBg} ↳ Page %d%s{reset}', self.curPage, workingPage.title and '' .. workingPage.title .. ' ' or '')))
end
self.sink:write(buffer == '' and display or buffer)
end
function gh:input(c)
-- command handling
if c == ':' and not command then
command = true
end
if c == 'Escape' then
if command then
command = false
buffer = ''
else
if self.isSpecial then gh:special() end
end
elseif c == 'Backspace' then
buffer = buffer:sub(0, -2)
if buffer == '' then
command = false
else
goto update
end
end
if command then
ansikit.showCursor()
if buffer:match '^:' then buffer = buffer .. c else buffer = c end
else
ansikit.hideCursor()
end
::update::
gh:update()
end
gh:resize()
gh:keybind('Enter', function(self)
if self.isSpecial then
self:jump(self.specialPageIdx)
self:special(false)
else
if buffer:len() < 2 then return end
local splitBuf = string.split(buffer, " ")
local command = commands[splitBuf[1]:sub(2)]
if command then
table.remove(splitBuf, 1)
buffer = command(splitBuf) or ''
end
self:update()
end
end)
if sinks['in'].pipe then
local page = Page('stdin', sinks['in']:readAll())
gh:addPage(page)
end
for _, name in ipairs(args) do
local f <close> = io.open(name, 'r')
if not f then
sinks.err:writeln(string.format('could not open file %s', name))
end
local page = Page(name, f:read '*a')
gh:addPage(page)
end
if #gh.pages == 0 then
sinks.out:writeln [[greenhouse is the Hilbish pager library and command!
usage: greenhouse <file>...
example: greenhouse hello.md]]
return 1
end
ansikit.hideCursor()
gh:initUi()
end)

View File

@ -1,3 +1,4 @@
-- @module dirs
local fs = require 'fs' local fs = require 'fs'
local dirs = {} local dirs = {}
@ -11,8 +12,8 @@ dirs.recentDirs = {}
dirs.recentSize = 10 dirs.recentSize = 10
--- Get (and remove) a `num` of entries from recent directories. --- Get (and remove) a `num` of entries from recent directories.
--- @param num number -- @param num number
--- @param remove boolean Whether to remove items -- @param remove boolean Whether to remove items
function dirRecents(num, remove) function dirRecents(num, remove)
num = num or 1 num = num or 1
local entries = {} local entries = {}
@ -34,12 +35,12 @@ function dirRecents(num, remove)
end end
--- Look at `num` amount of recent directories, starting from the latest. --- Look at `num` amount of recent directories, starting from the latest.
--- @param num? number -- @param num? number
function dirs.peak(num) function dirs.peak(num)
return dirRecents(num) return dirRecents(num)
end end
--- Add `d` to the recent directories. --- Add `d` to the recent directories list.
function dirs.push(d) function dirs.push(d)
dirs.recentDirs[dirs.recentSize + 1] = nil dirs.recentDirs[dirs.recentSize + 1] = nil
if dirs.recentDirs[#dirs.recentDirs - 1] ~= d then if dirs.recentDirs[#dirs.recentDirs - 1] ~= d then
@ -50,20 +51,20 @@ function dirs.push(d)
end end
end end
--- Remove `num` amount of dirs from the recent directories. --- Remove the specified amount of dirs from the recent directories list.
--- @param num number -- @param num number
function dirs.pop(num) function dirs.pop(num)
return dirRecents(num, true) return dirRecents(num, true)
end end
--- Get entry from recent directories. --- Get entry from recent directories list based on index.
--- @param idx number -- @param idx number
function dirs.recent(idx) function dirs.recent(idx)
return dirs.recentDirs[idx] return dirs.recentDirs[idx]
end end
--- Sets the old directory. --- Sets the old directory string.
--- @param d string -- @param d string
function dirs.setOld(d) function dirs.setOld(d)
ok, d = pcall(fs.abs, d) ok, d = pcall(fs.abs, d)
assert(ok, 'could not turn "' .. d .. '"into an absolute path') assert(ok, 'could not turn "' .. d .. '"into an absolute path')

47
nature/doc.lua 100644
View File

@ -0,0 +1,47 @@
local lunacolors = require 'lunacolors'
local M = {}
function M.highlight(text)
return text:gsub('\'.-\'', lunacolors.yellow)
--:gsub('%-%- .-', lunacolors.black)
end
function M.renderCodeBlock(text)
local longest = 0
local lines = string.split(text:gsub('\t', ' '), '\n')
for i, line in ipairs(lines) do
local len = line:len()
if len > longest then longest = len end
end
for i, line in ipairs(lines) do
lines[i] = lunacolors.format('{greyBg}' .. ' ' .. M.highlight(line:sub(0, longest))
.. string.rep(' ', longest - line:len()) .. ' ')
end
return '\n' .. lunacolors.format('{greyBg}' .. table.concat(lines, '\n')) .. '\n'
end
function M.renderInfoBlock(type, text)
local longest = 0
local lines = string.split(text:gsub('\t', ' '), '\n')
for i, line in ipairs(lines) do
local len = line:len()
if len > longest then longest = len end
end
for i, line in ipairs(lines) do
lines[i] = ' ' .. M.highlight(line:sub(0, longest))
.. string.rep(' ', longest - line:len()) .. ' '
end
local heading
if type == 'warning' then
heading = lunacolors.yellowBg(lunacolors.black(' ⚠ Warning '))
end
return '\n' .. heading .. '\n' .. lunacolors.format('{greyBg}' .. table.concat(lines, '\n')) .. '\n'
end
return M

View File

@ -0,0 +1,365 @@
-- Greenhouse is a simple text scrolling handler for terminal programs.
-- The idea is that it can be set a region to do its scrolling and paging
-- job and then the user can draw whatever outside it.
-- This reduces code duplication for the message viewer
-- and flowerbook.
local ansikit = require 'ansikit'
local lunacolors = require 'lunacolors'
local terminal = require 'terminal'
local Page = require 'nature.greenhouse.page'
local Object = require 'nature.object'
local Greenhouse = Object:extend()
function Greenhouse:new(sink)
local size = terminal.size()
self.region = size
self.contents = nil -- or can be a table
self.start = 1 -- where to start drawing from (should replace with self.region.y)
self.offset = 1 -- vertical text offset
self.horizOffset = 1
self.sink = sink
self.pages = {}
self.curPage = 1
self.step = {
horizontal = 5,
vertical = 1
}
self.separator = ''
self.keybinds = {
['Up'] = function(self) self:scroll 'up' end,
['Down'] = function(self) self:scroll 'down' end,
['Left'] = function(self) self:scroll 'left' end,
['Right'] = function(self) self:scroll 'right' end,
['Ctrl-Left'] = self.previous,
['Ctrl-Right'] = self.next,
['Ctrl-N'] = function(self) self:toc(true) end,
['Enter'] = function(self)
if self.isSpecial then
self:jump(self.specialPageIdx)
self:special(false)
end
end,
['Page-Down'] = function(self) self:scroll('down', {page = true}) end,
['Page-Up'] = function(self) self:scroll('up', {page = true}) end
}
self.isSpecial = false
self.specialPage = nil
self.specialPageIdx = 1
self.specialOffset = 1
return self
end
function Greenhouse:addPage(page)
table.insert(self.pages, page)
end
function Greenhouse:updateCurrentPage(text)
local page = self.pages[self.curPage]
page:setText(text)
end
local ansiPatters = {
'\x1b%[%d+;%d+;%d+;%d+;%d+%w',
'\x1b%[%d+;%d+;%d+;%d+%w',
'\x1b%[%d+;%d+;%d+%w',
'\x1b%[%d+;%d+%w',
'\x1b%[%d+%w'
}
function Greenhouse:sub(str, offset, limit)
local overhead = 0
local function addOverhead(s)
overhead = overhead + string.len(s)
end
local s = str
for _, pat in ipairs(ansiPatters) do
s = s:gsub(pat, addOverhead)
end
return s:sub(offset, utf8.offset(str, limit + overhead) or limit + overhead)
--return s:sub(offset, limit + overhead)
end
function Greenhouse:draw()
local workingPage = self.pages[self.curPage]
local offset = self.offset
if self.isSpecial then
offset = self.specialOffset
workingPage = self.specialPage
end
if workingPage.lazy and not workingPage.loaded then
workingPage.initialize()
end
local lines = workingPage.lines
self.sink:write(ansikit.getCSI(self.start .. ';1', 'H'))
self.sink:write(ansikit.getCSI(2, 'J'))
local writer = self.sink.writeln
self.attributes = {}
for i = offset, offset + self.region.height - 1 do
local resetEnd = false
if i > #lines then break end
if i == offset + self.region.height - 1 then writer = self.sink.write end
self.sink:write(ansikit.getCSI(self.start + i - offset .. ';1', 'H'))
local line = lines[i]:gsub('{separator}', function() return self.separator:rep(self.region.width - 1) end)
for _, pat in ipairs(ansiPatters) do
line:gsub(pat, function(s)
if s == lunacolors.formatColors.reset then
self.attributes = {}
resetEnd = true
else
--resetEnd = false
--table.insert(self.attributes, s)
end
end)
end
--[[
if #self.attributes ~= 0 then
for _, attr in ipairs(self.attributes) do
--writer(self.sink, attr)
end
end
]]--
self.sink:write(lunacolors.formatColors.reset)
writer(self.sink, self:sub(line:gsub('\t', ' '), self.horizOffset, self.region.width + self.horizOffset))
if resetEnd then
self.sink:write(lunacolors.formatColors.reset)
end
end
writer(self.sink, '\27[0m')
self:render()
end
function Greenhouse:render()
end
function Greenhouse:scroll(direction, opts)
opts = opts or {}
if self.isSpecial then
if direction == 'down' then
self:next(true)
elseif direction == 'up' then
self:previous(true)
end
return
end
local lines = self.pages[self.curPage].lines
local oldOffset = self.offset
local oldHorizOffset = self.horizOffset
local amount = self.step.vertical
if opts.page then
amount = self.region.height
end
if direction == 'down' then
self.offset = math.min(self.offset + amount, math.max(1, #lines - self.region.height))
elseif direction == 'up' then
self.offset = math.max(self.offset - amount, 1)
end
--[[
if direction == 'left' then
self.horizOffset = math.max(self.horizOffset - self.step.horizontal, 1)
elseif direction == 'right' then
self.horizOffset = self.horizOffset + self.step.horizontal
end
]]--
if self.offset ~= oldOffset then self:draw() end
if self.horizOffset ~= oldHorizOffset then self:draw() end
end
function Greenhouse:update()
self:resize()
if self.isSpecial then
self:updateSpecial()
end
self:draw()
end
function Greenhouse:special(val)
self.isSpecial = val
self:update()
end
function Greenhouse:toggleSpecial()
self:special(not self.isSpecial)
end
--- This function will be called when the special page
--- is on and needs to be updated.
function Greenhouse:updateSpecial()
end
function Greenhouse:contents()
end
function Greenhouse:toc(toggle)
if not self.isSpecial then
self.specialPageIdx = self.curPage
end
if toggle then self.isSpecial = not self.isSpecial end
-- Generate a special page for our table of contents
local tocText = string.format([[
%s
]], lunacolors.cyan(lunacolors.bold '―― Table of Contents ――'))
local genericPageCount = 1
local contents = self:contents()
if contents then
for i, c in ipairs(contents) do
local title = c.title
if c.active then
title = lunacolors.invert(title)
end
tocText = tocText .. title .. '\n'
end
else
for i, page in ipairs(self.pages) do
local title = page.title
if title == 'Page' then
title = 'Page #' .. genericPageCount
genericPageCount = genericPageCount + 1
end
if i == self.specialPageIdx then
title = lunacolors.invert(title)
end
tocText = tocText .. title .. '\n'
end
end
self.specialPage = Page('TOC', tocText)
function self:updateSpecial()
self:toc()
end
self:draw()
end
function Greenhouse:resize()
local size = terminal.size()
self.region = size
end
function Greenhouse:next(special)
local oldCurrent = special and self.specialPageIdx or self.curPage
local pageIdx = math.min(oldCurrent + 1, #self.pages)
if special then
self.specialPageIdx = pageIdx
else
self.curPage = pageIdx
end
if pageIdx ~= oldCurrent then
self.offset = 1
self:update()
end
end
function Greenhouse:previous(special)
local oldCurrent = special and self.specialPageIdx or self.curPage
local pageIdx = math.max(self.curPage - 1, 1)
if special then
self.specialPageIdx = pageIdx
else
self.curPage = pageIdx
end
if pageIdx ~= oldCurrent then
self.offset = 1
self:update()
end
end
function Greenhouse:jump(idx)
if idx ~= self.curPage then
self.offset = 1
end
self.curPage = idx
self:update()
end
function Greenhouse:keybind(key, callback)
self.keybinds[key] = callback
end
function Greenhouse:input(char)
end
local function read()
terminal.saveState()
terminal.setRaw()
local c = hilbish.editor.readChar()
terminal.restoreState()
return c
end
function Greenhouse:initUi()
local ansikit = require 'ansikit'
local bait = require 'bait'
local commander = require 'commander'
local hilbish = require 'hilbish'
local terminal = require 'terminal'
local Page = require 'nature.greenhouse.page'
local done = false
local function sigint()
ansikit.clear()
done = true
end
local function resize()
self:update()
end
bait.catch('signal.sigint', sigint)
bait.catch('signal.resize', resize)
ansikit.screenAlt()
ansikit.clear(true)
self:draw()
while not done do
local c = read()
self:keybind('Ctrl-Q', function()
done = true
end)
self:keybind('Ctrl-D', function()
done = true
end)
if self.keybinds[c] then
self.keybinds[c](self)
else
self:input(c)
end
end
ansikit.showCursor()
ansikit.screenMain()
self = nil
bait.release('signal.sigint', sigint)
bait.release('signal.resize', resize)
end
return Greenhouse

View File

@ -0,0 +1,32 @@
local Object = require 'nature.object'
local Page = Object:extend()
function Page:new(title, text)
self:setText(text)
self.title = title or 'Page'
self.lazy = false
self.loaded = true
self.children = {}
end
function Page:setText(text)
self.lines = string.split(text, '\n')
end
function Page:setTitle(title)
self.title = title
end
function Page:dynamic(initializer)
self.initializer = initializer
self.lazy = true
self.loaded = false
end
function Page:initialize()
self.initializer()
self.loaded = true
end
return Page

View File

@ -0,0 +1,84 @@
local bait = require 'bait'
local commander = require 'commander'
local lunacolors = require 'lunacolors'
local M = {}
local counter = 0
local unread = 0
M._messages = {}
M.icons = {
INFO = '',
SUCCESS = '',
WARN = '',
ERROR = ''
}
hilbish.messages = {}
--- Represents a Hilbish message.
--- @class hilbish.message
--- @field icon string Unicode (preferably standard emoji) icon for the message notification.
--- @field title string Title of the message (like an email subject).
--- @field text string Contents of the message.
--- @field channel string Short identifier of the message. `hilbish` and `hilbish.*` is preserved for internal Hilbish messages.
--- @field summary string A short summary of the message.
--- @field read boolean Whether the full message has been read or not.
function expect(tbl, field)
if not tbl[field] or tbl[field] == '' then
error(string.format('expected field %s in message'))
end
end
--- Sends a message.
--- @param message hilbish.message
function hilbish.messages.send(message)
expect(message, 'text')
expect(message, 'title')
counter = counter + 1
unread = unread + 1
message.index = counter
message.read = false
M._messages[message.index] = message
bait.throw('hilbish.notification', message)
end
function hilbish.messages.read(idx)
local msg = M._messages[idx]
if msg then
M._messages[idx].read = true
unread = unread - 1
end
end
function hilbish.messages.readAll(idx)
for _, msg in ipairs(hilbish.messages.all()) do
hilbish.messages.read(msg.index)
end
end
function hilbish.messages.unreadCount()
return unread
end
function hilbish.messages.delete(idx)
local msg = M._messages[idx]
if not msg then
error(string.format('invalid message index %d', idx or -1))
end
M._messages[idx] = nil
end
function hilbish.messages.clear()
for _, msg in ipairs(hilbish.messages.all()) do
hilbish.messages.delete(msg.index)
end
end
function hilbish.messages.all()
return M._messages
end
return M

View File

@ -6,11 +6,24 @@ local fs = require 'fs'
package.path = package.path .. ';' .. hilbish.dataDir .. '/?/init.lua' package.path = package.path .. ';' .. hilbish.dataDir .. '/?/init.lua'
.. ';' .. hilbish.dataDir .. '/?/?.lua' .. ";" .. hilbish.dataDir .. '/?.lua' .. ';' .. hilbish.dataDir .. '/?/?.lua' .. ";" .. hilbish.dataDir .. '/?.lua'
hilbish.module.paths = '?.so;?/?.so;'
.. hilbish.userDir.data .. 'hilbish/libs/?/?.so'
.. ";" .. hilbish.userDir.data .. 'hilbish/libs/?.so'
table.insert(package.searchers, function(module)
local path = package.searchpath(module, hilbish.module.paths)
if not path then return nil end
-- it didnt work normally, idk
return function() return hilbish.module.load(path) end, path
end)
require 'nature.commands' require 'nature.commands'
require 'nature.completions' require 'nature.completions'
require 'nature.opts' require 'nature.opts'
require 'nature.vim' require 'nature.vim'
require 'nature.runner' require 'nature.runner'
require 'nature.hummingbird'
local shlvl = tonumber(os.getenv 'SHLVL') local shlvl = tonumber(os.getenv 'SHLVL')
if shlvl ~= nil then if shlvl ~= nil then

59
nature/object.lua 100644
View File

@ -0,0 +1,59 @@
---@class nature.object
---@field super nature.object
local Object = {}
Object.__index = Object
---Can be overrided by child objects to implement a constructor.
function Object:new() end
---@return nature.object
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
---Check if the object is strictly of the given type.
---@param T any
---@return boolean
function Object:is(T)
return getmetatable(self) == T
end
---Check if the object inherits from the given type.
---@param T any
---@return boolean
function Object:extends(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
---Metamethod to get a string representation of an object.
---@return string
function Object:__tostring()
return "Object"
end
---Methamethod to allow using the object call as a constructor.
---@return nature.object
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
return Object

View File

@ -0,0 +1,11 @@
local lunacolors = require 'lunacolors'
bait.catch('hilbish.init', function()
if os.date '%m' == '12' and hilbish.interactive and hilbish.opts.crimmas then
local crimmas = math.random(1, 31)
if crimmas >= 25 and crimmas <= 29 then
print(lunacolors.format '🎄 {green}Merry {red}Christmas{reset} from your {green}favourite{reset} shell {red}(right?){reset} 🌺')
end
end
end)

View File

@ -1,22 +1,8 @@
local opts = {}
hilbish.opts = {} hilbish.opts = {}
setmetatable(hilbish.opts, {
__newindex = function(_, k, v)
if opts[k] == nil then
error(string.format('opt %s does not exist', k))
end
opts[k] = v
end,
__index = function(_, k)
return opts[k]
end
})
local function setupOpt(name, default) local function setupOpt(name, default)
opts[name] = default hilbish.opts[name] = default
require('nature.opts.' .. name) pcall(require, 'nature.opts.' .. name)
end end
local defaultOpts = { local defaultOpts = {
@ -25,7 +11,10 @@ local defaultOpts = {
greeting = string.format([[Welcome to {magenta}Hilbish{reset}, {cyan}%s{reset}. greeting = string.format([[Welcome to {magenta}Hilbish{reset}, {cyan}%s{reset}.
The nice lil shell for {blue}Lua{reset} fanatics! The nice lil shell for {blue}Lua{reset} fanatics!
]], hilbish.user), ]], hilbish.user),
motd = true motd = true,
fuzzy = false,
notifyJobFinish = true,
crimmas = true
} }
for optsName, default in pairs(defaultOpts) do for optsName, default in pairs(defaultOpts) do

View File

@ -2,8 +2,8 @@ local bait = require 'bait'
local lunacolors = require 'lunacolors' local lunacolors = require 'lunacolors'
hilbish.motd = [[ hilbish.motd = [[
1000 commits on the Hilbish repository brings us to {cyan}Version 2.1!{reset} Finally at {red}v2.2!{reset} So much {green}documentation improvements{reset}
Docs, docs, docs... At least builtins work with pipes now. and 1 single fix for Windows! {blue}.. and a feature they can't use.{reset}
]] ]]
bait.catch('hilbish.init', function() bait.catch('hilbish.init', function()

View File

@ -0,0 +1,23 @@
local bait = require 'bait'
local lunacolors = require 'lunacolors'
bait.catch('job.done', function(job)
if not hilbish.opts.notifyJobFinish then return end
local notifText = string.format(lunacolors.format [[
Background job with ID#%d has exited (PID %d).
Command string: {bold}{yellow}%s{reset}]], job.id, job.pid, job.cmd)
if job.stdout ~= '' then
notifText = notifText .. '\n\nStandard output:\n' .. job.stdout
end
if job.stderr ~= '' then
notifText = notifText .. '\n\nStandard error:\n' .. job.stderr
end
hilbish.messages.send {
channel = 'jobNotify',
title = string.format('Job ID#%d Exited', job.id),
summary = string.format(lunacolors.format 'Background job with command {bold}{yellow}%s{reset} has finished running!', job.cmd),
text = notifText
}
end)

7
os.go
View File

@ -8,10 +8,9 @@ import (
) )
// #interface os // #interface os
// OS Info // operating system info
// The `os` interface provides simple text information properties about // Provides simple text information properties about the current operating system.
// the current OS on the systen. This mainly includes the name and // This mainly includes the name and version.
// version.
// #field family Family name of the current OS // #field family Family name of the current OS
// #field name Pretty name of the current OS // #field name Pretty name of the current OS
// #field version Version of the current OS // #field version Version of the current OS

14
pprof.go 100644
View File

@ -0,0 +1,14 @@
// +build pprof
package main
import (
_ "net/http/pprof"
"net/http"
)
func init() {
go func() {
http.ListenAndServe("localhost:8080", nil)
}()
}

View File

@ -1,5 +1,7 @@
package readline package readline
import "os"
// Character codes // Character codes
const ( const (
charCtrlA = iota + 1 charCtrlA = iota + 1
@ -58,6 +60,8 @@ var (
seqAltF = string([]byte{27, 102}) seqAltF = string([]byte{27, 102})
seqAltR = string([]byte{27, 114}) // Used for alternative history seqAltR = string([]byte{27, 114}) // Used for alternative history
seqAltBackspace = string([]byte{27, 127}) seqAltBackspace = string([]byte{27, 127})
seqPageUp = string([]byte{27, 91, 53, 126})
seqPageDown = string([]byte{27, 91, 54, 126})
) )
const ( const (
@ -72,6 +76,8 @@ const (
seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left
seqGetCursorPos = "\x1b6n" // response: "\x1b{Line};{Column}R" seqGetCursorPos = "\x1b6n" // response: "\x1b{Line};{Column}R"
seqHideCursor = "\x1b[?25l"
seqUnhideCursor = "\x1b[?25h"
seqCtrlLeftArrow = "\x1b[1;5D" seqCtrlLeftArrow = "\x1b[1;5D"
seqCtrlRightArrow = "\x1b[1;5C" seqCtrlRightArrow = "\x1b[1;5C"
@ -134,3 +140,59 @@ const (
const ( const (
seqCtermFg255 = "\033[48;5;255m" seqCtermFg255 = "\033[48;5;255m"
) )
// TODO: return whether its actually a sequence or not
// remedies the edge case of someone literally typing Ctrl-A for example.
func (rl *Instance) ReadChar() string {
b := make([]byte, 1024)
i, _ := os.Stdin.Read(b)
r := []rune(string(b))
s := string(r[:i])
switch b[0] {
case charCtrlA: return "Ctrl-A"
case charCtrlB: return "Ctrl-B"
case charCtrlC: return "Ctrl-C"
case charEOF: return "Ctrl-D"
case charCtrlE: return "Ctrl-E"
case charCtrlF: return "Ctrl-F"
case charCtrlG: return "Ctrl-G"
case charBackspace, charBackspace2: return "Backspace"
case charTab: return "Tab"
case charCtrlK: return "Ctrl-K"
case charCtrlL: return "Ctrl-L"
case charCtrlN: return "Ctrl-N"
case charCtrlO: return "Ctrl-O"
case charCtrlP: return "Ctrl-P"
case charCtrlQ: return "Ctrl-Q"
case charCtrlR: return "Ctrl-R"
case charCtrlS: return "Ctrl-S"
case charCtrlT: return "Ctrl-T"
case charCtrlU: return "Ctrl-U"
case charCtrlV: return "Ctrl-V"
case charCtrlW: return "Ctrl-W"
case charCtrlX: return "Ctrl-X"
case charCtrlY: return "Ctrl-Y"
case charCtrlZ: return "Ctrl-Z"
case '\r': fallthrough
case '\n': return "Enter"
case charEscape:
switch s {
case string(charEscape): return "Escape"
case seqUp: return "Up"
case seqDown: return "Down"
case seqBackwards: return "Left"
case seqForwards: return "Right"
case seqCtrlLeftArrow: return "Ctrl-Left"
case seqCtrlRightArrow: return "Ctrl-Right"
case seqCtrlDelete, seqCtrlDelete2: return "Ctrl-Delete"
case seqHome, seqHomeSc: return "Home"
case seqEnd, seqEndSc: return "End"
case seqDelete, seqDelete2: return "Delete"
case seqPageUp: return "Page-Up"
case seqPageDown: return "Page-Down"
}
}
return s
}

View File

@ -71,10 +71,9 @@ func (g *CompletionGroup) init(rl *Instance) {
// The rx parameter is passed, as the shell already checked that the search pattern is valid. // The rx parameter is passed, as the shell already checked that the search pattern is valid.
func (g *CompletionGroup) updateTabFind(rl *Instance) { func (g *CompletionGroup) updateTabFind(rl *Instance) {
suggs := make([]string, 0) suggs := rl.Searcher(rl.search, g.Suggestions)
// We perform filter right here, so we create a new completion group, and populate it with our results. // We perform filter right here, so we create a new completion group, and populate it with our results.
for i := range g.Suggestions { /*for i := range g.Suggestions {
if rl.regexSearch == nil { continue } if rl.regexSearch == nil { continue }
if rl.regexSearch.MatchString(g.Suggestions[i]) { if rl.regexSearch.MatchString(g.Suggestions[i]) {
suggs = append(suggs, g.Suggestions[i]) suggs = append(suggs, g.Suggestions[i])
@ -82,7 +81,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) {
// this is a list so lets also check the descriptions // this is a list so lets also check the descriptions
suggs = append(suggs, g.Suggestions[i]) suggs = append(suggs, g.Suggestions[i])
} }
} }*/
// We overwrite the group's items, (will be refreshed as soon as something is typed in the search) // We overwrite the group's items, (will be refreshed as soon as something is typed in the search)
g.Suggestions = suggs g.Suggestions = suggs

View File

@ -1,6 +1,7 @@
package readline package readline
import ( import (
// "fmt"
"os" "os"
"regexp" "regexp"
"strconv" "strconv"
@ -68,6 +69,40 @@ func (rl *Instance) getCursorPos() (x int, y int) {
// This means that they are not used to keep any reference point when // This means that they are not used to keep any reference point when
// when we internally move around clearning and printing things // when we internally move around clearning and printing things
/*
func moveCursorUpBuffered(i int) {
if i < 1 {
return
}
fmt.Fprintf(rl.bufferedOut, "\x1b[%dA", i)
}
func moveCursorDownBuffered(i int) {
if i < 1 {
return
}
fmt.Fprintf(rl.bufferedOut, "\x1b[%dB", i)
}
func moveCursorForwardsBuffered(i int) {
if i < 1 {
return
}
fmt.Fprintf(rl.bufferedOut, "\x1b[%dC", i)
}
func moveCursorUpBuffered(i int) {
if i < 1 {
return
}
fmt.Fprintf(rl.bufferedOut, "\x1b[%dD", i)
}
*/
func moveCursorUp(i int) { func moveCursorUp(i int) {
if i < 1 { if i < 1 {
return return
@ -100,6 +135,14 @@ func moveCursorBackwards(i int) {
printf("\x1b[%dD", i) printf("\x1b[%dD", i)
} }
func hideCursor() {
print(seqHideCursor)
}
func unhideCursor() {
print(seqUnhideCursor)
}
func (rl *Instance) backspace(forward bool) { func (rl *Instance) backspace(forward bool) {
if len(rl.line) == 0 || rl.pos == 0 { if len(rl.line) == 0 || rl.pos == 0 {
return return

View File

@ -1,6 +1,6 @@
module github.com/maxlandon/readline module github.com/maxlandon/readline
go 1.16 go 1.18
require ( require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d

View File

@ -156,8 +156,8 @@ func (rl *Instance) walkHistory(i int) {
rl.updateHelpers() rl.updateHelpers()
// In order to avoid having to type j/k twice each time for history navigation, // In order to avoid having to type j/k twice each time for history navigation,
// we walk once again. This only ever happens when we aren't out of bounds. // we walk once again. This only ever happens when we aren't out of bounds and the last history item was not a empty string.
if dedup && old == new { if new != "" && dedup && old == new {
rl.walkHistory(i) rl.walkHistory(i)
} }
} }

View File

@ -1,6 +1,7 @@
package readline package readline
import ( import (
"bufio"
"os" "os"
"regexp" "regexp"
"sync" "sync"
@ -112,8 +113,10 @@ type Instance struct {
modeAutoFind bool // for when invoked via ^R or ^F outside of [tab] modeAutoFind bool // for when invoked via ^R or ^F outside of [tab]
searchMode FindMode // Used for varying hints, and underlying functions called searchMode FindMode // Used for varying hints, and underlying functions called
regexSearch *regexp.Regexp // Holds the current search regex match regexSearch *regexp.Regexp // Holds the current search regex match
search string
mainHist bool // Which history stdin do we want mainHist bool // Which history stdin do we want
histInfo []rune // We store a piece of hist info, for dual history sources histInfo []rune // We store a piece of hist info, for dual history sources
Searcher func(string, []string) []string
// //
// History ----------------------------------------------------------------------------------- // History -----------------------------------------------------------------------------------
@ -201,6 +204,8 @@ type Instance struct {
ViActionCallback func(ViAction, []string) ViActionCallback func(ViAction, []string)
RawInputCallback func([]rune) // called on all input RawInputCallback func([]rune) // called on all input
bufferedOut *bufio.Writer
} }
// NewInstance is used to create a readline instance and initialise it with sane defaults. // NewInstance is used to create a readline instance and initialise it with sane defaults.
@ -229,6 +234,27 @@ func NewInstance() *Instance {
rl.HintFormatting = "\x1b[2m" rl.HintFormatting = "\x1b[2m"
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn) rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
rl.TempDirectory = os.TempDir() rl.TempDirectory = os.TempDir()
rl.Searcher = func(needle string, haystack []string) []string {
suggs := make([]string, 0)
var err error
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
if err != nil {
rl.RefreshPromptLog(err.Error())
rl.infoText = []rune(Red("Failed to match search regexp"))
}
for _, hay := range haystack {
if rl.regexSearch == nil { continue }
if rl.regexSearch.MatchString(hay) {
suggs = append(suggs, hay)
}
}
return suggs
}
rl.bufferedOut = bufio.NewWriter(os.Stdout)
// Registers // Registers
rl.initRegisters() rl.initRegisters()

View File

@ -33,19 +33,20 @@ func (rl *Instance) GetLine() []rune {
func (rl *Instance) echo() { func (rl *Instance) echo() {
// Then we print the prompt, and the line, // Then we print the prompt, and the line,
hideCursor()
switch { switch {
case rl.PasswordMask != 0: case rl.PasswordMask != 0:
case rl.PasswordMask > 0: case rl.PasswordMask > 0:
print(strings.Repeat(string(rl.PasswordMask), len(rl.line)) + " ") rl.bufprint(strings.Repeat(string(rl.PasswordMask), len(rl.line)) + " ")
default: default:
// Go back to prompt position, and clear everything below // Go back to prompt position, and clear everything below
moveCursorBackwards(GetTermWidth()) moveCursorBackwards(GetTermWidth())
moveCursorUp(rl.posY) moveCursorUp(rl.posY)
print(seqClearScreenBelow)
// Print the prompt // Print the prompt
print(string(rl.realPrompt)) rl.bufprint(string(rl.realPrompt))
// Assemble the line, taking virtual completions into account // Assemble the line, taking virtual completions into account
var line []rune var line []rune
@ -57,11 +58,14 @@ func (rl *Instance) echo() {
// Print the input line with optional syntax highlighting // Print the input line with optional syntax highlighting
if rl.SyntaxHighlighter != nil { if rl.SyntaxHighlighter != nil {
print(rl.SyntaxHighlighter(line)) rl.bufprint(rl.SyntaxHighlighter(line))
} else { } else {
print(string(line)) rl.bufprint(string(line))
} }
rl.bufprint(seqClearScreenBelow)
} }
rl.bufflush()
// Update references with new coordinates only now, because // Update references with new coordinates only now, because
// the new line may be longer/shorter than the previous one. // the new line may be longer/shorter than the previous one.
@ -72,6 +76,7 @@ func (rl *Instance) echo() {
moveCursorUp(rl.fullY) moveCursorUp(rl.fullY)
moveCursorDown(rl.posY) moveCursorDown(rl.posY)
moveCursorForwards(rl.posX) moveCursorForwards(rl.posX)
unhideCursor()
} }
func (rl *Instance) insert(r []rune) { func (rl *Instance) insert(r []rune) {
@ -159,7 +164,7 @@ func (rl *Instance) clearLine() {
moveCursorForwards(rl.promptLen) moveCursorForwards(rl.promptLen)
// Clear everything after & below the cursor // Clear everything after & below the cursor
print(seqClearScreenBelow) //print(seqClearScreenBelow)
// Real input line // Real input line
rl.line = []rune{} rl.line = []rune{}

View File

@ -48,7 +48,7 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
rl.stillOnRefresh = true rl.stillOnRefresh = true
moveCursorUp(rl.infoY + rl.tcUsedY) moveCursorUp(rl.infoY + rl.tcUsedY)
moveCursorBackwards(GetTermWidth()) moveCursorBackwards(GetTermWidth())
print("\r\n" + seqClearScreenBelow) //print("\r\n" + seqClearScreenBelow)
// Print the log // Print the log
fmt.Printf(log) fmt.Printf(log)
@ -97,7 +97,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
print(seqClearLine) print(seqClearLine)
moveCursorUp(rl.infoY + rl.tcUsedY) moveCursorUp(rl.infoY + rl.tcUsedY)
moveCursorBackwards(GetTermWidth()) moveCursorBackwards(GetTermWidth())
print("\r\n" + seqClearScreenBelow) //print("\r\n" + seqClearScreenBelow)
// Add a new line if needed // Add a new line if needed
if rl.Multiline { if rl.Multiline {
@ -137,7 +137,7 @@ func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine boo
moveCursorUp(offset) moveCursorUp(offset)
// Then clear everything below our new position // Then clear everything below our new position
print(seqClearScreenBelow) //print(seqClearScreenBelow)
// Update the prompt if a special has been passed. // Update the prompt if a special has been passed.
if prompt != "" { if prompt != "" {

View File

@ -868,7 +868,7 @@ func (rl *Instance) escapeSeq(r []rune) {
if err != nil { if err != nil {
return return
} }
if !rl.mainHist { if !rl.mainHist && rl.altHistory != nil {
line, err = rl.altHistory.GetLine(rl.altHistory.Len() - 1) line, err = rl.altHistory.GetLine(rl.altHistory.Len() - 1)
if err != nil { if err != nil {
return return

View File

@ -94,7 +94,7 @@ func (rl *Instance) getTabSearchCompletion() {
rl.getCurrentGroup() rl.getCurrentGroup()
// Set the info for this completion mode // Set the info for this completion mode
rl.infoText = append([]rune("Completion search: "), rl.tfLine...) rl.infoText = append([]rune("Completion search: " + UNDERLINE + BOLD), rl.tfLine...)
for _, g := range rl.tcGroups { for _, g := range rl.tcGroups {
g.updateTabFind(rl) g.updateTabFind(rl)
@ -102,7 +102,7 @@ func (rl *Instance) getTabSearchCompletion() {
// If total number of matches is zero, we directly change the info, and return // If total number of matches is zero, we directly change the info, and return
if comps, _, _ := rl.getCompletionCount(); comps == 0 { if comps, _, _ := rl.getCompletionCount(); comps == 0 {
rl.infoText = append(rl.infoText, []rune(DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...) rl.infoText = append(rl.infoText, []rune(RESET+DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
} }
} }
@ -276,13 +276,14 @@ func (rl *Instance) writeTabCompletion() {
// than what their MaxLength allows them to, cycling sometimes occur, // than what their MaxLength allows them to, cycling sometimes occur,
// but does not fully clears itself: some descriptions are messed up with. // but does not fully clears itself: some descriptions are messed up with.
// We always clear the screen as a result, between writings. // We always clear the screen as a result, between writings.
print(seqClearScreenBelow) //rl.bufprint(seqClearScreenBelow)
// Crop the completions so that it fits within our MaxTabCompleterRows // Crop the completions so that it fits within our MaxTabCompleterRows
completions, rl.tcUsedY = rl.cropCompletions(completions) completions, rl.tcUsedY = rl.cropCompletions(completions)
// Then we print all of them. // Then we print all of them.
fmt.Printf(completions) rl.bufprintF(completions)
rl.bufflush()
} }
// cropCompletions - When the user cycles through a completion list longer // cropCompletions - When the user cycles through a completion list longer

View File

@ -1,9 +1,5 @@
package readline package readline
import (
"regexp"
)
// FindMode defines how the autocomplete suggestions display // FindMode defines how the autocomplete suggestions display
type FindMode int type FindMode int
@ -30,15 +26,10 @@ func (rl *Instance) updateTabFind(r []rune) {
rl.tfLine = append(rl.tfLine, r...) rl.tfLine = append(rl.tfLine, r...)
// The search regex is common to all search modes // The search regex is common to all search modes
var err error rl.search = string(rl.tfLine)
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
if err != nil {
rl.RefreshPromptLog(err.Error())
rl.infoText = []rune(Red("Failed to match search regexp"))
}
// We update and print // We update and print
rl.clearHelpers() //rl.clearHelpers()
rl.getTabCompletion() rl.getTabCompletion()
rl.renderHelpers() rl.renderHelpers()
} }

View File

@ -14,6 +14,7 @@ var (
// effects // effects
BOLD = "\033[1m" BOLD = "\033[1m"
DIM = "\033[2m" DIM = "\033[2m"
UNDERLINE = "\033[4m"
RESET = "\033[0m" RESET = "\033[0m"
// colors // colors
RED = "\033[31m" RED = "\033[31m"

Some files were not shown because too many files have changed in this diff Show More