chore: update branch

cancel-commander
sammyette 2024-07-19 10:47:44 -04:00
commit 62dc2e8b02
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
190 changed files with 8345 additions and 1401 deletions

6
.editorconfig 100644
View File

@ -0,0 +1,6 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = tab

View File

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

View File

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

View File

@ -2,14 +2,15 @@ name: Generate docs
on:
push:
branches: [master]
branches:
- master
jobs:
gen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- name: Run docgen
run: go run cmd/docgen/docgen.go
- name: Commit new docs

View File

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

51
.github/workflows/website.yml vendored 100644
View File

@ -0,0 +1,51 @@
name: Build website
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.111.3'
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
run: 'cd website && hugo --minify'
- 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
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

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
*.exe
hilbish
!docs/api/hilbish
docgen
!cmd/docgen
.vim
petals/
.hugo_build.lock

View File

@ -1,18 +1,39 @@
-- Default Hilbish config
local hilbish = require 'hilbish'
local lunacolors = require 'lunacolors'
local bait = require 'bait'
local ansikit = require 'ansikit'
local unreadCount = 0
local running = false
local function doPrompt(fail)
hilbish.prompt(lunacolors.format(
'{blue}%u {cyan}%d ' .. (fail and '{red}' or '{green}') .. ''
))
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()
bait.catch('command.preexec', function()
running = true
end)
bait.catch('command.exit', function(code)
running = false
doPrompt(code ~= 0)
doNotifyPrompt()
end)
bait.catch('hilbish.vimMode', function(mode)
@ -22,3 +43,7 @@ bait.catch('hilbish.vimMode', function(mode)
ansikit.cursorStyle(ansikit.lineCursor)
end
end)
bait.catch('hilbish.notification', function(notif)
doNotifyPrompt()
end)

View File

@ -1,7 +1,153 @@
# 🎀 Changelog
## Unreleased
**NOTE:** Hilbish now uses [Task] insead of Make for builds.
### 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
- `exec` command should return if no arg presented
## [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
### Added
- Documented custom userdata types (Job and Timer Objects)
- Coming with this fix is also adding the return types for some functions that were missing it
- Added a dedicated input and dedicated outputs for commanders (sinks - info at `doc api commander`).
- Local docs is used if one of Hilbish's branches is found
- Return 1 exit code on doc not found
- `hilbish.runner.getCurrent()` to get the current runner
- Initialize Hilbish Lua API before handling signals
### Fixed
- `index` or `_index` subdocs should not show up anymore
- `hilbish.which` not working correctly with aliases
- Commanders not being able to pipe with commands or any related operator.
- Resolve symlinks in completions
- Updated `runner-mode` docs
- Fix `hilbish.completion` functions panicking when empty input is provided
## [2.0.1] - 2022-12-28
### Fixed
- Corrected documentation for hooks, removing outdated `command.no-perm`
- Fixed an issue where `cd` with no args would not update the old pwd
- Tiny documentation enhancements for the `hilbish.timer` interface
## [2.0.0] - 2022-12-20
**NOTES FOR USERS/PACKAGERS UPDATING:**
- Hilbish now uses [Task] insead of Make for builds.
- The doc format has been changed from plain text to markdown.
**YOU MUST reinstall Hilbish to remove the duplicate, old docs.**
- Hilbish will by default install to **`/usr/local`** instead of just `/usr/`
when building via Task. This is mainly to avoid conflict of distro packages
and local installs, and is the correct place when building from git either way.
To keep Hilbish in `/usr`, you must have `PREFIX="/usr/"` when running `task build` or `task install`
- Windows is no longer supported. It will build and run, but **will** have problems.
If you want to help fix the situation, start a discussion or open an issue and contribute.
[Task]: https://taskfile.dev/#/
@ -39,7 +185,7 @@ without arguments will disown the last job.
fields on a job object.
- Documentation for jobs is now available via `doc jobs`.
- `hilbish.alias.resolve(cmdstr)` to resolve a command alias.
- `hilbish.opts` for shell options. Currently, the only opt is `autocd`.
- `hilbish.opts` for shell options.
- `hilbish.editor` interface for interacting with the line editor that
Hilbish uses.
- `hilbish.vim` interface to dynamically get/set vim registers.
@ -73,12 +219,20 @@ disables commands being added to history.
- A new and "safer" event emitter has been added. This causes a performance deficit, but avoids a lot of
random errors introduced with the new Lua runtime (see [#197])
- `bait.release(name, catcher)` removes `handler` for the named `event`
- `exec`, `clear` and `cat` builtin commands
- `hilbish.cancel` hook thrown when user cancels input with Ctrl-C
- 1st item on history is now inserted when history search menu is opened ([#148])
- Documentation has been improved vastly!
[#148]: https://github.com/Rosettea/Hilbish/issues/148
[#197]: https://github.com/Rosettea/Hilbish/issues/197
### Changed
- **Breaking Change:** Upgraded to Lua 5.4.
This is probably one of (if not the) biggest things in this release.
To recap quickly on what matters (mostly):
- `os.execute` returns 3 values instead of 1 (but you should be using `hilbish.run`)
- I/O operations must be flushed (`io.flush()`)
- **Breaking Change:** MacOS config paths now match Linux.
- Overrides on the `hilbish` table are no longer permitted.
- **Breaking Change:** Runner functions are now required to return a table.
@ -97,6 +251,7 @@ of a dot. (ie. `job.stop()` -> `job:stop()`)
- All `fs` module functions which take paths now implicitly expand ~ to home.
- **Breaking Change:** `hilbish.greeting` has been moved to an opt (`hilbish.opts.greeting`) and is
always printed by default. To disable it, set the opt to false.
- **Breaking Change:** `command.no-perm` hook has been replaced with `command.not-executable`
- History is now fetched from Lua, which means users can override `hilbish.history`
methods to make it act how they want.
- `guide` has been removed. See the [website](https://rosettea.github.io/Hilbish/)
@ -112,7 +267,7 @@ replacing the last character.
- `hilbish.login` being the wrong value.
- Put full input in history if prompted for continued input
- Don't put alias expanded command in history (sound familiar?)
- Handle cases of stdin being nonblocking (in the case of [#130](https://github.com/Rosettea/Hilbish/issues/130))
- Handle cases of stdin being nonblocking (in the case of [#136](https://github.com/Rosettea/Hilbish/issues/136))
- Don't prompt for continued input if non interactive
- Don't insert unhandled control keys.
- Handle sh syntax error in alias
@ -122,11 +277,12 @@ certain color rules.
- Home/End keys now go to the actual start/end of the input.
- Input getting cut off on enter in certain cases.
- Go to the next line properly if input reaches end of terminal width.
- Cursor position with CJK characters. ([#145](https://github.com/Rosettea/Hilbish/pull/145))
- Files with same name as parent folder in completions getting cut off [#136](https://github.com/Rosettea/Hilbish/issues/136))
- Cursor position with CJK characters has been corrected ([#145](https://github.com/Rosettea/Hilbish/pull/145))
- Files with same name as parent folder in completions getting cut off [#130](https://github.com/Rosettea/Hilbish/issues/130))
- `hilbish.which` now works with commanders and aliases.
- Background jobs no longer take stdin so they do not interfere with shell
input.
- Full name of completion entry is used instead of being cut off
- Completions are fixed in cases where the query/line is an alias alone
where it can also resolve to the beginning of command names.
(reference [this commit](https://github.com/Rosettea/Hilbish/commit/2790982ad123115c6ddbc5764677fdca27668cea))
@ -145,6 +301,29 @@ menu is open.
- Escape codes now work.
- Escape percentage symbols in completion entries, so you will no longer see
an error of missing format variable
- Fix an error with sh syntax in aliases
- Prompt now works with east asian characters (CJK)
- Set back the prompt to normal after exiting the continue prompt with ctrl-d
- Take into account newline in input when calculating input width. Prevents
extra reprinting of the prompt, but input with newlines inserted is still a problem
- Put cursor at the end of input when exiting $EDITOR with Vim mode bind
- Calculate width of virtual input properly (completion candidates)
- Users can now tab complete files with spaces while quoted or with escaped spaces.
This means a query of `Files\ to\ ` with file names of `Files to tab complete` and `Files to complete`
will result in the files being completed.
- Fixed grid menu display if cell width ends up being the width of the terminal
- Cut off item names in grid menu if its longer than cell width
- Fix completion search menu disappearing
- Make binary completion work with bins that have spaces in the name
- Completion paths having duplicated characters if it's escaped
- Get custom completion command properly to call from Lua
- Put proper command on the line when using up and down arrow keys to go through command history
- Don't do anything if length of input rune slice is 0 ([commit for explanation](https://github.com/Rosettea/Hilbish/commit/8d40179a73fe5942707cd43f9c0463dee53eedd8))
## [2.0.0-rc1] - 2022-09-14
This is a pre-release version of Hilbish for testing. To see the changelog,
refer to the `Unreleased` section of the [full changelog](CHANGELOG.md)
(version 2.0.0 for future reference).
## [1.2.0] - 2022-03-17
### Added
@ -569,6 +748,17 @@ This input for example will prompt for more input to complete:
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.0.1]: https://github.com/Rosettea/Hilbish/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/Rosettea/Hilbish/compare/v1.2.0...v2.0.0
[2.0.0-rc1]: https://github.com/Rosettea/Hilbish/compare/v1.2.0...v2.0.0-rc1
[1.2.0]: https://github.com/Rosettea/Hilbish/compare/v1.1.4...v1.2.0
[1.1.0]: https://github.com/Rosettea/Hilbish/compare/v1.0.4...v1.1.0
[1.0.4]: https://github.com/Rosettea/Hilbish/compare/v1.0.3...v1.0.4
[1.0.3]: https://github.com/Rosettea/Hilbish/compare/v1.0.2...v1.0.3

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Rosettea
Copyright (c) 2021-2023 Rosettea
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,74 +1,45 @@
<div align="center">
<img src="./assets/hilbish-flower.png" width=128><br>
<img src="./assets/hilbish-text.png" width=256><br>
<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>
<img src="./assets/hilbish-logo-and-text.png" width=512><br>
<blockquote>
🌓 The Moon-powered shell! A comfy and extensible shell for Lua fans! 🌺 ✨
</blockquote>
Hilbish is a extensible shell (framework). It was made to be very customizable
via the Lua programming language. It aims to be easy to use for the casual
people but powerful for those who want to tinker more with their shell,
the thing used to interface with most of the system.
<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>
<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
than old shell script. It's fine for basic interactive shell uses,
but that's the only place Hilbish has shell script; everything else is Lua
and aims to be infinitely configurable. If something isn't, open an issue!
# Table of Contents
- [Screenshots](#Screenshots)
- [Installation](#Installation)
- [Prebuilt Bins](#Prebuilt-binaries)
- [AUR](#AUR)
- [Nixpkgs](#Nixpkgs)
- [Manual Build](#Manual-Build)
- [Getting Started](#Getting-Started)
- [Contributing](#Contributing)
# Screenshots
<div align="center">
<img src="gallery/default.png"><br><br>
<img src="gallery/terminal.png"><br><br>
<img src="gallery/tab.png">
<img src="gallery/pillprompt.png">
</div>
# Installation
## Prebuilt binaries
Go [here](https://nightly.link/Rosettea/Hilbish/workflows/build/master) for
builds on the master branch.
# Getting Hilbish
**NOTE:** Hilbish is not guaranteed to work properly on Windows, starting
from the 2.0 version. It will still be able to compile, but functionality
may be lacking. If you want to contribute to make the situation better,
comment on the Windows discussion.
## AUR
[![AUR maintainer](https://img.shields.io/aur/maintainer/hilbish?logo=arch-linux&style=flat-square)](https://aur.archlinux.org/packages/hilbish)
Arch Linux users can install Hilbish from the AUR with the following command:
```sh
yay -S hilbish
```
You can check the [install page](https://rosettea.github.io/Hilbish/install/)
on the website for distributed binaries from GitHub or other package repositories.
Otherwise, continue reading for steps on compiling.
[![AUR maintainer](https://img.shields.io/aur/maintainer/hilbish?logo=arch-linux&style=flat-square)](https://aur.archlinux.org/packages/hilbish-git)
Or from the latest `master` commit with:
```sh
yay -S hilbish-git
```
## Prerequisites
- [Go 1.22+](https://go.dev)
- [Task](https://taskfile.dev/installation/) (**Go on the hyperlink here to see Task's install method for your OS.**)
## Nixpkgs
Nix/NixOS users can install Hilbish from the central repository, nixpkgs, through the usual ways.
If you're new to nix you should probably read up on how to do that [here](https://nixos.wiki/wiki/Cheatsheet).
## Manual Build
### Prerequisites
- [Go 1.17+](https://go.dev)
- [Task](https://taskfile.dev/#/)
### Build
## Build
First, clone Hilbish. The recursive is required, as some Lua libraries
are submodules.
```sh

View File

@ -3,24 +3,35 @@
version: '3'
vars:
PREFIX: '{{default "/usr" .PREFIX}}'
PREFIX: '{{default "/usr/local" .PREFIX}}'
bindir__: '{{.PREFIX}}/bin'
BINDIR: '{{default .bindir__ .BINDIR}}'
libdir__: '{{.PREFIX}}/share/hilbish'
LIBDIR: '{{default .libdir__ .LIBDIR}}'
GOFLAGS: '-ldflags "-s -w"'
goflags__: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}}"'
GOFLAGS: '{{default .goflags__ .GOFLAGS}}'
tasks:
default:
cmds:
- go build {{.GOFLAGS}}
vars:
GOFLAGS: '-ldflags "-s -w -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"'
GOFLAGS: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}} -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"'
default-nocgo:
cmds:
- CGO_ENABLED=0 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)"'
build:
cmds:
- go build {{.GOFLAGS}}
build-nocgo:
cmds:
- CGO_ENABLED=0 go build {{.GOFLAGS}}
install:
cmds:
- install -v -d "{{.DESTDIR}}{{.BINDIR}}/" && install -m 0755 -v hilbish "{{.DESTDIR}}{{.BINDIR}}/hilbish"
@ -33,4 +44,4 @@ tasks:
- rm -vrf
"{{.DESTDIR}}{{.BINDIR}}/hilbish"
"{{.DESTDIR}}{{.LIBDIR}}"
- sed -i '/hilbish/d' /etc/shells
- grep -v 'hilbish' /etc/shells > /tmp/shells.hilbish_uninstall && mv /tmp/shells.hilbish_uninstall /etc/shells

View File

@ -1,6 +1,8 @@
package main
import (
"regexp"
"strconv"
"strings"
"sync"
@ -9,46 +11,69 @@ import (
rt "github.com/arnodel/golua/runtime"
)
var aliases *aliasHandler
var aliases *aliasModule
type aliasHandler struct {
type aliasModule struct {
aliases map[string]string
mu *sync.RWMutex
}
// initialize aliases map
func newAliases() *aliasHandler {
return &aliasHandler{
func newAliases() *aliasModule {
return &aliasModule{
aliases: make(map[string]string),
mu: &sync.RWMutex{},
}
}
func (a *aliasHandler) Add(alias, cmd string) {
func (a *aliasModule) Add(alias, cmd string) {
a.mu.Lock()
defer a.mu.Unlock()
a.aliases[alias] = cmd
}
func (a *aliasHandler) All() map[string]string {
func (a *aliasModule) All() map[string]string {
return a.aliases
}
func (a *aliasHandler) Delete(alias string) {
func (a *aliasModule) Delete(alias string) {
a.mu.Lock()
defer a.mu.Unlock()
delete(a.aliases, alias)
}
func (a *aliasHandler) Resolve(cmdstr string) string {
func (a *aliasModule) Resolve(cmdstr string) string {
a.mu.RLock()
defer a.mu.RUnlock()
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]] != "" {
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])
cmdArgs, _ := splitInput(cmdstr)
args = cmdArgs
@ -66,7 +91,10 @@ func (a *aliasHandler) Resolve(cmdstr string) string {
// lua section
func (a *aliasHandler) Loader(rtm *rt.Runtime) *rt.Table {
// #interface aliases
// command aliasing
// The alias interface deals with all command aliases in Hilbish.
func (a *aliasModule) Loader(rtm *rt.Runtime) *rt.Table {
// create a lua module with our functions
hshaliasesLua := map[string]util.LuaExport{
"add": util.LuaExport{hlalias, 2, false},
@ -81,7 +109,26 @@ func (a *aliasHandler) Loader(rtm *rt.Runtime) *rt.Table {
return mod
}
func (a *aliasHandler) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface aliases
// add(alias, cmd)
// This is an alias (ha) for the [hilbish.alias](../#alias) function.
// --- @param alias string
// --- @param cmd string
func _hlalias() {}
// #interface aliases
// list() -> table[string, string]
// Get a table of all aliases, with string keys as the alias and the value as the command.
// #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) {
aliasesList := rt.NewTable()
for k, v := range a.All() {
aliasesList.Set(rt.StringValue(k), rt.StringValue(v))
@ -90,7 +137,11 @@ func (a *aliasHandler) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.TableValue(aliasesList)), nil
}
func (a *aliasHandler) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface aliases
// delete(name)
// Removes an alias.
// #param name string
func (a *aliasModule) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
@ -103,7 +154,12 @@ func (a *aliasHandler) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func (a *aliasHandler) luaResolve(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface aliases
// resolve(alias) -> string?
// Resolves an alias to its original command. Will thrown an error if the alias doesn't exist.
// #param alias string
// #returns string
func (a *aliasModule) luaResolve(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}

503
api.go
View File

@ -1,12 +1,22 @@
// Here is the core api for the hilbi shell itself
// Basically, stuff about the shell itself and other functions
// go here.
// the core Hilbish API
// The Hilbish module includes the core API, containing
// interfaces and functions which directly relate to shell functionality.
// #field ver The version of Hilbish
// #field goVersion The version of Go that Hilbish was compiled with
// #field user Username of the user
// #field host Hostname of the machine
// #field dataDir Directory for Hilbish data files, including the docs and default modules
// #field interactive Is Hilbish in an interactive shell?
// #field login Is Hilbish the login shell?
// #field vimMode Current Vim input mode of Hilbish (will be nil if not in Vim input mode)
// #field exitCode Exit code of the last executed command
package main
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"runtime"
@ -18,8 +28,8 @@ import (
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
"github.com/arnodel/golua/lib/iolib"
"github.com/maxlandon/readline"
"github.com/blackfireio/osinfo"
"mvdan.cc/sh/v3/interp"
)
@ -51,47 +61,8 @@ var hilbishLoader = packagelib.Loader{
}
func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
fakeMod := rt.NewTable()
modmt := 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)
hshMod = mod
@ -102,81 +73,68 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows
}
util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(getVersion()), "Hilbish version")
util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username), "Username of user")
util.SetFieldProtected(fakeMod, mod, "host", rt.StringValue(host), "Host name of the machine")
util.SetFieldProtected(fakeMod, mod, "home", rt.StringValue(curuser.HomeDir), "Home directory of the user")
util.SetFieldProtected(fakeMod, mod, "dataDir", rt.StringValue(dataDir), "Directory for Hilbish's data files")
util.SetFieldProtected(fakeMod, mod, "interactive", rt.BoolValue(interactive), "If this is an interactive shell")
util.SetFieldProtected(fakeMod, mod, "login", rt.BoolValue(login), "Whether this is a login shell")
util.SetFieldProtected(fakeMod, mod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
util.SetFieldProtected(fakeMod, mod, "exitCode", rt.IntValue(0), "Exit code of last exected command")
util.Document(fakeMod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
util.SetField(rtm, mod, "ver", rt.StringValue(getVersion()))
util.SetField(rtm, mod, "goVersion", rt.StringValue(runtime.Version()))
util.SetField(rtm, mod, "user", rt.StringValue(username))
util.SetField(rtm, mod, "host", rt.StringValue(host))
util.SetField(rtm, mod, "home", rt.StringValue(curuser.HomeDir))
util.SetField(rtm, mod, "dataDir", rt.StringValue(dataDir))
util.SetField(rtm, mod, "interactive", rt.BoolValue(interactive))
util.SetField(rtm, mod, "login", rt.BoolValue(login))
util.SetField(rtm, mod, "vimMode", rt.NilValue)
util.SetField(rtm, mod, "exitCode", rt.IntValue(0))
// hilbish.userDir table
hshuser := rt.NewTable()
util.SetField(rtm, hshuser, "config", rt.StringValue(confDir), "User's config directory")
util.SetField(rtm, hshuser, "data", rt.StringValue(userDataDir), "XDG data directory")
util.Document(hshuser, "User directories to store configs and/or modules.")
hshuser := userDirLoader(rtm)
mod.Set(rt.StringValue("userDir"), rt.TableValue(hshuser))
// hilbish.os table
hshos := rt.NewTable()
info, _ := osinfo.GetOSInfo()
util.SetField(rtm, hshos, "family", rt.StringValue(info.Family), "Family name of the current OS")
util.SetField(rtm, hshos, "name", rt.StringValue(info.Name), "Pretty name of the current OS")
util.SetField(rtm, hshos, "version", rt.StringValue(info.Version), "Version of the current OS")
util.Document(hshos, "OS info interface")
hshos := hshosLoader(rtm)
mod.Set(rt.StringValue("os"), rt.TableValue(hshos))
// hilbish.aliases table
aliases = newAliases()
aliasesModule := aliases.Loader(rtm)
util.Document(aliasesModule, "Alias inferface for Hilbish.")
mod.Set(rt.StringValue("aliases"), rt.TableValue(aliasesModule))
// hilbish.history table
historyModule := lr.Loader(rtm)
mod.Set(rt.StringValue("history"), rt.TableValue(historyModule))
util.Document(historyModule, "History interface for Hilbish.")
// hilbish.completion table
hshcomp := completionLoader(rtm)
util.Document(hshcomp, "Completions interface for Hilbish.")
// TODO: REMOVE "completion" AND ONLY USE "completions" WITH AN S
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
mod.Set(rt.StringValue("completions"), rt.TableValue(hshcomp))
// hilbish.runner table
runnerModule := runnerModeLoader(rtm)
util.Document(runnerModule, "Runner/exec interface for Hilbish.")
mod.Set(rt.StringValue("runner"), rt.TableValue(runnerModule))
// hilbish.jobs table
jobs = newJobHandler()
jobModule := jobs.loader(rtm)
util.Document(jobModule, "(Background) job interface.")
mod.Set(rt.StringValue("jobs"), rt.TableValue(jobModule))
// hilbish.timers table
timers = newTimerHandler()
timerModule := timers.loader(rtm)
util.Document(timerModule, "Timer interface, for control of all intervals and timeouts.")
mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule))
timers = newTimersModule()
timersModule := timers.loader(rtm)
mod.Set(rt.StringValue("timers"), rt.TableValue(timersModule))
editorModule := editorLoader(rtm)
util.Document(editorModule, "")
mod.Set(rt.StringValue("editor"), rt.TableValue(editorModule))
versionModule := rt.NewTable()
util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch), "Git branch Hilbish was compiled from")
util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion()), "Full version info, including release name")
util.SetField(rtm, versionModule, "commit", rt.StringValue(gitCommit), "Git commit Hilbish was compiled from")
util.SetField(rtm, versionModule, "release", rt.StringValue(releaseName), "Release name")
util.Document(versionModule, "Version info interface.")
util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch))
util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion()))
util.SetField(rtm, versionModule, "commit", rt.StringValue(gitCommit))
util.SetField(rtm, versionModule, "release", rt.StringValue(releaseName))
mod.Set(rt.StringValue("version"), rt.TableValue(versionModule))
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 {
@ -188,20 +146,72 @@ func getenv(key, fallback string) string {
}
func setVimMode(mode string) {
util.SetField(l, hshMod, "vimMode", rt.StringValue(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)")
util.SetField(l, hshMod, "vimMode", rt.StringValue(mode))
hooks.Emit("hilbish.vimMode", mode)
}
func unsetVimMode() {
util.SetField(l, hshMod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
util.SetField(l, hshMod, "vimMode", rt.NilValue)
}
// run(cmd, returnOut) -> exitCode, stdout, stderr
// Runs `cmd` in Hilbish's sh interpreter.
// If returnOut is true, the outputs of `cmd` will be returned as the 2nd and
// 3rd values instead of being outputted to the terminal.
// --- @param cmd string
func handleStream(v rt.Value, strms *streams, errStream bool) error {
ud, ok := v.TryUserData()
if !ok {
return errors.New("expected metatable argument")
}
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) {
// TODO: ON BREAKING RELEASE, DO NOT ACCEPT `streams` AS A BOOLEAN.
if err := c.Check1Arg(); err != nil {
return nil, err
}
@ -210,20 +220,57 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
strms := &streams{}
var terminalOut bool
if len(c.Etc()) != 0 {
tout := c.Etc()[0]
termOut, ok := tout.TryBool()
terminalOut = termOut
var ok bool
terminalOut, ok = tout.TryBool()
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
stdout, stderr, err := execCommand(cmd, terminalOut)
stdout, stderr, err := execCommand(cmd, strms)
if code, ok := interp.IsExitStatus(err); ok {
exitcode = code
@ -231,18 +278,20 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
exitcode = 1
}
stdoutStr := ""
stderrStr := ""
if !terminalOut {
stdoutStr = stdout.(*bytes.Buffer).String()
stderrStr = stderr.(*bytes.Buffer).String()
var stdoutStr, stderrStr string
if stdoutBuf, ok := stdout.(*bytes.Buffer); ok {
stdoutStr = stdoutBuf.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
}
// cwd()
// Returns the current directory of the shell
// cwd() -> string
// Returns the current directory of the shell.
// #returns string
func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
cwd, _ := os.Getwd()
@ -250,21 +299,28 @@ func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// read(prompt) -> input?
// read(prompt) -> input (string)
// Read input from the user, using Hilbish's line editor/input reader.
// This is a separate instance from the one Hilbish actually uses.
// Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
// --- @param prompt string
// Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
// #param prompt? string Text to print before input, can be empty.
// #returns string|nil
func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
luaprompt := c.Arg(0)
if typ := luaprompt.Type(); typ != rt.StringType && typ != rt.NilType {
return nil, errors.New("expected #1 to be a string")
}
luaprompt, err := c.StringArg(0)
if err != nil {
return nil, err
prompt, ok := luaprompt.TryString()
if !ok {
// if we are here and `luaprompt` is not a string, it's nil
// substitute with an empty string
prompt = ""
}
lualr := newLineReader("", true)
lualr.SetPrompt(luaprompt)
lualr := &lineReader{
rl: readline.NewInstance(),
}
lualr.SetPrompt(prompt)
input, err := lualr.Read()
if err != nil {
@ -275,15 +331,22 @@ func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
/*
prompt(str, typ?)
Changes the shell prompt to `str`
prompt(str, typ)
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
--- @param str string
--- @param typ string Type of prompt, being left or right. Left by default.
#param str string
#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) {
err := c.Check1Arg()
@ -317,8 +380,28 @@ func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// multiprompt(str)
// Changes the continued line prompt to `str`
// --- @param str string
// Changes the text prompt when Hilbish asks for more input.
// 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) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -333,9 +416,19 @@ func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// alias(cmd, orig)
// Sets an alias of `cmd` to `orig`
// --- @param cmd string
// --- @param orig string
// Sets an alias, with a name of `cmd` to another command.
// #param cmd string Name of the alias
// #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) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
@ -355,8 +448,20 @@ func hlalias(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// appendPath(dir)
// Appends `dir` to $PATH
// --- @param dir string|table
// Appends the provided dir to the command path (`$PATH`)
// #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) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -390,8 +495,9 @@ func appendPath(dir string) {
}
// exec(cmd)
// Replaces running hilbish with `cmd`
// --- @param cmd string
// Replaces the currently running Hilbish instance with the supplied command.
// This can be used to do an in-place restart.
// #param cmd string
func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -425,8 +531,11 @@ func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// goro(fn)
// Puts `fn` in a goroutine
// --- @param fn function
// 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.**
// #param fn function
func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -438,6 +547,12 @@ func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// call fn
go func() {
defer func() {
if r := recover(); r != nil {
// do something here?
}
}()
_, err := rt.Call1(l.MainThread(), rt.FunctionValue(fn), c.Etc()...)
if err != nil {
fmt.Fprintln(os.Stderr, "Error in goro function:\n\n", err)
@ -447,12 +562,12 @@ func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// timeout(cb, time)
// Runs the `cb` function after `time` in milliseconds
// Returns a `timer` object (see `doc timers`).
// --- @param cb function
// --- @param time number
// --- @return table
// timeout(cb, time) -> @Timer
// Executed the `cb` function after a period of `time`.
// This creates a Timer that starts ticking immediately.
// #param cb function
// #param time number Time to run in milliseconds.
// #returns Timer
func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
@ -473,12 +588,12 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
}
// interval(cb, time)
// Runs the `cb` function every `time` milliseconds.
// Returns a `timer` object (see `doc timers`).
// --- @param cb function
// --- @param time number
// --- @return table
// interval(cb, time) -> @Timer
// Runs the `cb` function every specified amount of `time`.
// This creates a timer that ticking immediately.
// #param cb function
// #param time number Time in milliseconds.
// #return Timer
func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
@ -500,13 +615,40 @@ func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// complete(scope, cb)
// Registers a completion handler for `scope`.
// A `scope` is currently only expected to be `command.<cmd>`,
// Registers a completion handler for the specified scope.
// A `scope` is expected to be `command.<cmd>`,
// replacing <cmd> with the name of the command (for example `command.git`).
// `cb` must be a function that returns a table of "completion groups."
// Check `doc completions` for more information.
// --- @param scope string
// --- @param cb function
// The documentation for completions, under Features/Completions or `doc completions`
// provides more details.
// #param scope string
// #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) {
scope, cb, err := util.HandleStrCallback(t, c)
if err != nil {
@ -518,8 +660,8 @@ func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// prependPath(dir)
// Prepends `dir` to $PATH
// --- @param dir string
// Prepends `dir` to $PATH.
// #param dir string
func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -539,9 +681,11 @@ func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// which(name)
// Checks if `name` is a valid command
// --- @param binName string
// which(name) -> string
// Checks if `name` is a valid command.
// Will return the path of the binary, or a basename if it's a commander.
// #param name string
// #returns string
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -551,10 +695,13 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
cmd := aliases.Resolve(name)
// itll return either the original command or what was passed
// if name isnt empty its not an issue
alias := aliases.Resolve(name)
cmd := strings.Split(alias, " ")[0]
// check for commander
if commands[cmd] != nil {
if cmds.Commands[cmd] != nil {
// they dont resolve to a path, so just send the cmd
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
}
@ -568,8 +715,10 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// inputMode(mode)
// Sets the input mode for Hilbish's line reader. Accepts either emacs or vim
// --- @param mode string
// 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.
// #param mode string Can be set to either `emacs` or `vim`
func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -594,12 +743,14 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// runnerMode(mode)
// Sets the execution/runner mode for interactive Hilbish. This determines whether
// Hilbish wll try to run input as Lua and/or sh or only do one of either.
// 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.
// --- @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) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -625,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
// as the text for the hint. This is by default a shim. To set hints,
// override this function with your custom handler.
// --- @param line string
// --- @param pos int
// #param line string
// #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) {
return c.Next(), nil
}
// highlighter(line)
// Line highlighter handler. This is mainly for syntax highlighting, but in
// reality could set the input of the prompt to *display* anything. The
// callback is passed the current line and is expected to return a line that
// will be used as the input display.
// --- @param line string
// 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.
// #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) {
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

@ -7,28 +7,300 @@ import (
"go/doc"
"go/parser"
"go/token"
"regexp"
"strings"
"os"
"sync"
md "github.com/atsushinee/go-markdown-generator/doc"
)
type EmmyPiece struct {
FuncName string
Docs []string
var header = `---
title: %s %s
description: %s
layout: doc
menu:
docs:
parent: "API"
---
`
type emmyPiece struct {
DocPiece *docPiece
Annotations []string
Params []string // we only need to know param name to put in function
FuncName string
}
type DocPiece struct {
type module struct {
Types []docPiece
Docs []docPiece
Fields []docPiece
Properties []docPiece
ShortDescription string
Description string
ParentModule string
HasInterfaces bool
HasTypes bool
}
type param struct{
Name string
Type string
Doc []string
}
type docPiece struct {
Doc []string
FuncSig string
FuncName string
Interfacing string
ParentModule string
GoFuncName string
IsInterface bool
IsMember bool
IsType bool
Fields []docPiece
Properties []docPiece
Params []param
Tags map[string][]tag
}
type tag struct {
id string
fields []string
startIdx int
}
var docs = make(map[string]module)
var interfaceDocs = make(map[string]module)
var emmyDocs = make(map[string][]emmyPiece)
var typeTable = make(map[string][]string) // [0] = parentMod, [1] = interfaces
var prefix = map[string]string{
"main": "hl",
"hilbish": "hl",
"fs": "f",
"commander": "c",
"bait": "b",
"terminal": "term",
}
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
pts := strings.Split(docs, "\n")
parts := []string{}
tags := make(map[string][]tag)
for idx, part := range pts {
if strings.HasPrefix(part, "#") {
tagParts := strings.Split(strings.TrimPrefix(part, "#"), " ")
if tags[tagParts[0]] == nil {
var id string
if len(tagParts) > 1 {
id = tagParts[1]
}
tags[tagParts[0]] = []tag{
{id: id, startIdx: idx},
}
if len(tagParts) >= 2 {
tags[tagParts[0]][0].fields = tagParts[2:]
}
} 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{}
if len(tagParts) >= 2 {
fleds = tagParts[2:]
}
tags[tagParts[0]] = append(tags[tagParts[0]], tag{
id: tagParts[1],
fields: fleds,
})
}
} else {
parts = append(parts, part)
}
}
return tags, parts
}
func docPieceTag(tagName string, tags map[string][]tag) []docPiece {
dps := []docPiece{}
for _, tag := range tags[tagName] {
dps = append(dps, docPiece{
FuncName: tag.id,
Doc: tag.fields,
})
}
return dps
}
func setupDocType(mod string, typ *doc.Type) *docPiece {
docs := strings.TrimSpace(typ.Doc)
tags, doc := getTagsAndDocs(docs)
if tags["type"] == nil {
return nil
}
inInterface := tags["interface"] != nil
var interfaces string
typeName := strings.ToUpper(string(typ.Name[0])) + typ.Name[1:]
typeDoc := []string{}
if inInterface {
interfaces = tags["interface"][0].id
}
fields := docPieceTag("field", tags)
properties := docPieceTag("property", tags)
for _, d := range doc {
if strings.HasPrefix(d, "---") {
// TODO: document types in lua
/*
emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---"))
emmyLinePieces := strings.Split(emmyLine, " ")
emmyType := emmyLinePieces[0]
if emmyType == "@param" {
em.Params = append(em.Params, emmyLinePieces[1])
}
if emmyType == "@vararg" {
em.Params = append(em.Params, "...") // add vararg
}
em.Annotations = append(em.Annotations, d)
*/
} else {
typeDoc = append(typeDoc, d)
}
}
var isMember bool
if tags["member"] != nil {
isMember = true
}
parentMod := mod
dps := &docPiece{
Doc: typeDoc,
FuncName: typeName,
Interfacing: interfaces,
IsInterface: inInterface,
IsMember: isMember,
IsType: true,
ParentModule: parentMod,
Fields: fields,
Properties: properties,
Tags: tags,
}
typeTable[strings.ToLower(typeName)] = []string{parentMod, interfaces}
return dps
}
func setupDoc(mod string, fun *doc.Func) *docPiece {
docs := strings.TrimSpace(fun.Doc)
tags, parts := getTagsAndDocs(docs)
// i couldnt fit this into the condition below for some reason so here's a goto!
if tags["member"] != nil {
goto start
}
if (!strings.HasPrefix(fun.Name, prefix[mod]) && tags["interface"] == nil) || (strings.ToLower(fun.Name) == "loader" && tags["interface"] == nil) {
return nil
}
start:
inInterface := tags["interface"] != nil
var interfaces string
funcsig := parts[0]
doc := parts[1:]
funcName := strings.TrimPrefix(fun.Name, prefix[mod])
funcdoc := []string{}
if inInterface {
interfaces = tags["interface"][0].id
funcName = interfaces + "." + strings.Split(funcsig, "(")[0]
}
em := emmyPiece{FuncName: funcName}
fields := docPieceTag("field", tags)
properties := docPieceTag("property", tags)
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 {
if strings.HasPrefix(d, "---") {
emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---"))
emmyLinePieces := strings.Split(emmyLine, " ")
emmyType := emmyLinePieces[0]
if emmyType == "@param" {
em.Params = append(em.Params, emmyLinePieces[1])
}
if emmyType == "@vararg" {
em.Params = append(em.Params, "...") // add vararg
}
em.Annotations = append(em.Annotations, d)
} else {
funcdoc = append(funcdoc, d)
}
}
var isMember bool
if tags["member"] != nil {
isMember = true
}
var parentMod string
if inInterface {
parentMod = mod
}
dps := &docPiece{
Doc: funcdoc,
FuncSig: funcsig,
FuncName: funcName,
Interfacing: interfaces,
GoFuncName: strings.ToLower(fun.Name),
IsInterface: inInterface,
IsMember: isMember,
ParentModule: parentMod,
Fields: fields,
Properties: properties,
Params: params,
Tags: tags,
}
if strings.HasSuffix(dps.GoFuncName, strings.ToLower("loader")) {
dps.Doc = parts
}
em.DocPiece = dps
emmyDocs[mod] = append(emmyDocs[mod], em)
return dps
}
// feel free to clean this up
// it works, dont really care about the code
func main() {
fset := token.NewFileSet()
os.Mkdir("docs", 0777)
os.Mkdir("docs/api", 0777)
os.Mkdir("emmyLuaDocs", 0777)
dirs := []string{"./"}
filepath.Walk("golibs/", func (path string, info os.FileInfo, err error) error {
@ -51,120 +323,356 @@ func main() {
}
}
prefix := map[string]string{
"hilbish": "hl",
"fs": "f",
"commander": "c",
"bait": "b",
"terminal": "term",
}
docs := make(map[string][]DocPiece)
emmyDocs := make(map[string][]EmmyPiece)
interfaceModules := make(map[string]*module)
for l, f := range pkgs {
p := doc.New(f, "./", doc.AllDecls)
pieces := []docPiece{}
typePieces := []docPiece{}
mod := l
if mod == "main" {
mod = "hilbish"
}
var hasInterfaces bool
for _, t := range p.Funcs {
mod := l
if strings.HasPrefix(t.Name, "hl") { mod = "hilbish" }
if !strings.HasPrefix(t.Name, prefix[mod]) || t.Name == "Loader" { continue }
parts := strings.Split(strings.TrimSpace(t.Doc), "\n")
funcsig := parts[0]
doc := parts[1:]
funcdoc := []string{}
em := EmmyPiece{FuncName: strings.TrimPrefix(t.Name, prefix[mod])}
for _, d := range doc {
if strings.HasPrefix(d, "---") {
emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---"))
emmyLinePieces := strings.Split(emmyLine, " ")
emmyType := emmyLinePieces[0]
if emmyType == "@param" {
em.Params = append(em.Params, emmyLinePieces[1])
}
if emmyType == "@vararg" {
em.Params = append(em.Params, "...") // add vararg
}
em.Docs = append(em.Docs, d)
} else {
funcdoc = append(funcdoc, d)
}
piece := setupDoc(mod, t)
if piece == nil {
continue
}
dps := DocPiece{
Doc: funcdoc,
FuncSig: funcsig,
FuncName: strings.TrimPrefix(t.Name, prefix[mod]),
pieces = append(pieces, *piece)
if piece.IsInterface {
hasInterfaces = true
}
docs[mod] = append(docs[mod], dps)
emmyDocs[mod] = append(emmyDocs[mod], em)
}
for _, t := range p.Types {
for _, m := range t.Methods {
if !strings.HasPrefix(m.Name, prefix[l]) || m.Name == "Loader" { continue }
parts := strings.Split(strings.TrimSpace(m.Doc), "\n")
funcsig := parts[0]
doc := parts[1:]
funcdoc := []string{}
em := EmmyPiece{FuncName: strings.TrimPrefix(m.Name, prefix[l])}
for _, d := range doc {
if strings.HasPrefix(d, "---") {
emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---"))
emmyLinePieces := strings.Split(emmyLine, " ")
emmyType := emmyLinePieces[0]
if emmyType == "@param" {
em.Params = append(em.Params, emmyLinePieces[1])
}
if emmyType == "@vararg" {
em.Params = append(em.Params, "...") // add vararg
}
em.Docs = append(em.Docs, d)
} else {
funcdoc = append(funcdoc, d)
}
typePiece := setupDocType(mod, t)
if typePiece != nil {
typePieces = append(typePieces, *typePiece)
if typePiece.IsInterface {
hasInterfaces = true
}
dps := DocPiece{
Doc: funcdoc,
FuncSig: funcsig,
FuncName: strings.TrimPrefix(m.Name, prefix[l]),
}
for _, m := range t.Methods {
piece := setupDoc(mod, m)
if piece == nil {
continue
}
docs[l] = append(docs[l], dps)
emmyDocs[l] = append(emmyDocs[l], em)
pieces = append(pieces, *piece)
if piece.IsInterface {
hasInterfaces = true
}
}
}
tags, descParts := getTagsAndDocs(strings.TrimSpace(p.Doc))
shortDesc := descParts[0]
desc := descParts[1:]
filteredPieces := []docPiece{}
filteredTypePieces := []docPiece{}
for _, piece := range pieces {
if !piece.IsInterface {
filteredPieces = append(filteredPieces, piece)
continue
}
modname := piece.ParentModule + "." + piece.Interfacing
if interfaceModules[modname] == nil {
interfaceModules[modname] = &module{
ParentModule: piece.ParentModule,
}
}
if strings.HasSuffix(piece.GoFuncName, strings.ToLower("loader")) {
shortDesc := piece.Doc[0]
desc := piece.Doc[1:]
interfaceModules[modname].ShortDescription = shortDesc
interfaceModules[modname].Description = strings.Join(desc, "\n")
interfaceModules[modname].Fields = piece.Fields
interfaceModules[modname].Properties = piece.Properties
continue
}
interfaceModules[modname].Docs = append(interfaceModules[modname].Docs, piece)
}
for _, piece := range typePieces {
if !piece.IsInterface {
filteredTypePieces = append(filteredTypePieces, piece)
continue
}
modname := piece.ParentModule + "." + piece.Interfacing
if interfaceModules[modname] == nil {
interfaceModules[modname] = &module{
ParentModule: piece.ParentModule,
}
}
interfaceModules[modname].Types = append(interfaceModules[modname].Types, piece)
}
docs[mod] = module{
Types: filteredTypePieces,
Docs: filteredPieces,
ShortDescription: shortDesc,
Description: strings.Join(desc, "\n"),
HasInterfaces: hasInterfaces,
Properties: docPieceTag("property", tags),
Fields: docPieceTag("field", tags),
}
}
for key, mod := range interfaceModules {
docs[key] = *mod
}
var wg sync.WaitGroup
wg.Add(len(docs) * 2)
for mod, v := range docs {
if mod == "main" { continue }
f, _ := os.Create("docs/" + mod + ".txt")
for _, dps := range v {
f.WriteString(dps.FuncSig + " > ")
for _, doc := range dps.Doc {
if !strings.HasPrefix(doc, "---") {
f.WriteString(doc + "\n")
docPath := "docs/api/" + mod + ".md"
if v.HasInterfaces {
os.Mkdir("docs/api/" + mod, 0777)
os.Remove(docPath) // remove old doc path if it exists
docPath = "docs/api/" + mod + "/_index.md"
}
if v.ParentModule != "" {
docPath = "docs/api/" + v.ParentModule + "/" + mod + ".md"
}
go func(modname, docPath string, modu module) {
defer wg.Done()
modOrIface := "Module"
if modu.ParentModule != "" {
modOrIface = "Module"
}
lastHeader := ""
f, _ := os.Create(docPath)
f.WriteString(fmt.Sprintf(header, modOrIface, modname, modu.ShortDescription))
typeTag, _ := regexp.Compile(`\B@\w+`)
modDescription := typeTag.ReplaceAllStringFunc(strings.Replace(strings.Replace(modu.Description, "<", `\<`, -1), "{{\\<", "{{<", -1), func(typ string) string {
typName := typ[1:]
typLookup := typeTable[strings.ToLower(typName)]
ifaces := typLookup[0] + "." + typLookup[1] + "/"
if typLookup[1] == "" {
ifaces = ""
}
linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s#%s", typLookup[0], ifaces, strings.ToLower(typName))
return fmt.Sprintf(`<a href="%s" style="text-decoration: none;">%s</a>`, linkedTyp, typName)
})
f.WriteString(fmt.Sprintf("## Introduction\n%s\n\n", modDescription))
if len(modu.Docs) != 0 {
funcCount := 0
for _, dps := range modu.Docs {
if dps.IsMember {
continue
}
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:]
typLookup := typeTable[strings.ToLower(typName)]
ifaces := typLookup[0] + "." + typLookup[1] + "/"
if typLookup[1] == "" {
ifaces = ""
}
linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s#%s", typLookup[0], ifaces, strings.ToLower(typName))
return fmt.Sprintf(`<a href="%s" style="text-decoration: none;" id="lol">%s</a>`, linkedTyp, typName)
})
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 {
if !strings.HasPrefix(doc, "---") && doc != "" {
f.WriteString(doc + " \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")
}
}
f.WriteString("\n")
}
}
for mod, v := range emmyDocs {
if mod == "main" { continue }
f, _ := os.Create("emmyLuaDocs/" + mod + ".lua")
f.WriteString("--- @meta\n\nlocal " + mod + " = {}\n\n")
for _, em := range v {
var funcdocs []string
for _, dps := range docs[mod] {
if dps.FuncName == em.FuncName {
funcdocs = dps.Doc
if len(modu.Types) != 0 {
f.WriteString("## Types\n")
for _, dps := range modu.Types {
f.WriteString("<hr>\n\n")
f.WriteString(fmt.Sprintf("## %s\n", dps.FuncName))
for _, doc := range dps.Doc {
if !strings.HasPrefix(doc, "---") {
f.WriteString(doc + "\n")
}
}
if len(dps.Properties) != 0 {
f.WriteString("## Object properties\n")
mdTable := md.NewTable(len(dps.Properties), 2)
mdTable.SetTitle(0, "")
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("### Methods\n")
for _, dps := range modu.Docs {
if !dps.IsMember {
continue
}
htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(dps.FuncSig, "<", `\<`, -1), func(typ string) string {
typName := regexp.MustCompile(`\w+`).FindString(typ[1:])
typLookup := typeTable[strings.ToLower(typName)]
fmt.Printf("%+q, \n", typLookup)
linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s/#%s", typLookup[0], typLookup[0] + "." + typLookup[1], strings.ToLower(typName))
return fmt.Sprintf(`<a href="#%s" style="text-decoration: none;">%s</a>`, linkedTyp, typName)
})
f.WriteString(fmt.Sprintf("#### %s\n", htmlSig))
for _, doc := range dps.Doc {
if !strings.HasPrefix(doc, "---") {
f.WriteString(doc + "\n")
}
}
f.WriteString("\n")
}
}
}
f.WriteString("--- " + strings.Join(funcdocs, "\n--- ") + "\n")
if len(em.Docs) != 0 {
f.WriteString(strings.Join(em.Docs, "\n") + "\n")
}(mod, docPath, v)
go func(md, modname string, modu module) {
defer wg.Done()
if modu.ParentModule != "" {
return
}
f.WriteString("function " + mod + "." + em.FuncName + "(" + strings.Join(em.Params, ", ") + ") end\n\n")
}
f.WriteString("return " + mod + "\n")
ff, _ := os.Create("emmyLuaDocs/" + modname + ".lua")
ff.WriteString("--- @meta\n\nlocal " + modname + " = {}\n\n")
for _, em := range emmyDocs[modname] {
if strings.HasSuffix(em.DocPiece.GoFuncName, strings.ToLower("loader")) {
continue
}
dps := em.DocPiece
funcdocs := dps.Doc
ff.WriteString("--- " + strings.Join(funcdocs, "\n--- ") + "\n")
if len(em.Annotations) != 0 {
ff.WriteString(strings.Join(em.Annotations, "\n") + "\n")
}
accessor := "."
if dps.IsMember {
accessor = ":"
}
signature := strings.Split(dps.FuncSig, " ->")[0]
var intrface string
if dps.IsInterface {
intrface = "." + dps.Interfacing
}
ff.WriteString("function " + modname + intrface + accessor + signature + " end\n\n")
}
ff.WriteString("return " + modname + "\n")
}(mod, mod, v)
}
wg.Wait()
}

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

@ -11,15 +11,49 @@ import (
rt "github.com/arnodel/golua/runtime"
)
func splitQuote(str string) []string {
var charEscapeMap = []string{
"\"", "\\\"",
"'", "\\'",
"`", "\\`",
" ", "\\ ",
"(", "\\(",
")", "\\)",
"[", "\\[",
"]", "\\]",
"$", "\\$",
"&", "\\&",
"*", "\\*",
">", "\\>",
"<", "\\<",
"|", "\\|",
}
var charEscapeMapInvert = invert(charEscapeMap)
var escapeReplaer = strings.NewReplacer(charEscapeMap...)
var escapeInvertReplaer = strings.NewReplacer(charEscapeMapInvert...)
func invert(m []string) []string {
newM := make([]string, len(charEscapeMap))
for i := range m {
if (i + 1) % 2 == 0 {
newM[i] = m[i - 1]
newM[i - 1] = m[i]
}
}
return newM
}
func splitForFile(str string) []string {
split := []string{}
sb := &strings.Builder{}
quoted := false
for _, r := range str {
for i, r := range str {
if r == '"' {
quoted = !quoted
sb.WriteRune(r)
} else if r == ' ' && str[i - 1] == '\\' {
sb.WriteRune(r)
} else if !quoted && r == ' ' {
split = append(split, sb.String())
sb.Reset()
@ -39,12 +73,22 @@ func splitQuote(str string) []string {
}
func fileComplete(query, ctx string, fields []string) ([]string, string) {
q := splitQuote(ctx)
q := splitForFile(ctx)
path := ""
if len(q) != 0 {
path = q[len(q) - 1]
}
return matchPath(q[len(q) - 1])
return matchPath(path)
}
func binaryComplete(query, ctx string, fields []string) ([]string, string) {
q := splitForFile(ctx)
query = ""
if len(q) != 0 {
query = q[len(q) - 1]
}
var completions []string
prefixes := []string{"./", "../", "/", "~/"}
@ -54,7 +98,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
if len(fileCompletions) != 0 {
for _, f := range fileCompletions {
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
if err := findExecutable(fullPath, false, true); err != nil {
if err := findExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
continue
}
completions = append(completions, f)
@ -66,7 +110,6 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
// filter out executables, but in path
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
// print dir to stderr for debugging
// search for an executable which matches our query string
if matches, err := filepath.Glob(filepath.Join(dir, query + "*")); err == nil {
// get basename from matches
@ -85,7 +128,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
}
// add lua registered commands to completions
for cmdName := range commands {
for cmdName := range cmds.Commands {
if strings.HasPrefix(cmdName, query) {
completions = append(completions, cmdName)
}
@ -102,6 +145,7 @@ func matchPath(query string) ([]string, string) {
var entries []string
var baseName string
query = escapeInvertReplaer.Replace(query)
path, _ := filepath.Abs(util.ExpandHome(filepath.Dir(query)))
if string(query) == "" {
// filepath base below would give us "."
@ -112,7 +156,16 @@ func matchPath(query string) ([]string, string) {
}
files, _ := os.ReadDir(path)
for _, file := range files {
for _, entry := range files {
// should we handle errors here?
file, err := entry.Info()
if err == nil && file.Mode() & os.ModeSymlink != 0 {
path, err := filepath.EvalSymlinks(filepath.Join(path, file.Name()))
if err == nil {
file, err = os.Lstat(path)
}
}
if strings.HasPrefix(file.Name(), baseName) {
entry := file.Name()
if file.IsDir() {
@ -124,38 +177,26 @@ func matchPath(query string) ([]string, string) {
entries = append(entries, entry)
}
}
if !strings.HasPrefix(oldQuery, "\"") {
baseName = escapeFilename(baseName)
}
return entries, baseName
}
func escapeFilename(fname string) string {
args := []string{
"\"", "\\\"",
"'", "\\'",
"`", "\\`",
" ", "\\ ",
"(", "\\(",
")", "\\)",
"[", "\\[",
"]", "\\]",
"$", "\\$",
"&", "\\&",
"*", "\\*",
">", "\\>",
"<", "\\<",
"|", "\\|",
}
r := strings.NewReplacer(args...)
return r.Replace(fname)
return escapeReplaer.Replace(fname)
}
// #interface completion
// tab completions
// The completions interface deals with tab completions.
func completionLoader(rtm *rt.Runtime) *rt.Table {
exports := map[string]util.LuaExport{
"files": {luaFileComplete, 3, false},
"bins": {luaBinaryComplete, 3, false},
"call": {callLuaCompleter, 4, false},
"handler": {completionHandler, 2, false},
"bins": {hcmpBins, 3, false},
"call": {hcmpCall, 4, false},
"files": {hcmpFiles, 3, false},
"handler": {hcmpHandler, 2, false},
}
mod := rt.NewTable()
@ -164,12 +205,58 @@ func completionLoader(rtm *rt.Runtime) *rt.Table {
return mod
}
// left as a shim, might doc in the same way as hilbish functions
func completionHandler(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
// #interface completion
// bins(query, ctx, fields) -> entries (table), prefix (string)
// Return binaries/executables based on the provided parameters.
// This function is meant to be used as a helper in a command completion handler.
// #param query string
// #param ctx string
// #param fields table
/*
#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
}
func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface completion
// 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 the Completions doc or `doc completions` for info on the `completionGroups` return value.
// #param name string
// #param query string
// #param ctx string
// #param fields table
func hcmpCall(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(4); err != nil {
return nil, err
}
@ -197,18 +284,26 @@ func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// we must keep the holy 80 cols
completerReturn, err := rt.Call1(l.MainThread(),
rt.FunctionValue(completecb), rt.StringValue(query),
rt.StringValue(ctx), rt.TableValue(fields))
cont := c.Next()
err = rt.Call(l.MainThread(), rt.FunctionValue(completecb),
[]rt.Value{rt.StringValue(query), rt.StringValue(ctx), rt.TableValue(fields)},
cont)
if err != nil {
return nil, err
}
return c.PushingNext1(t.Runtime, completerReturn), nil
return cont, nil
}
func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface completion
// files(query, ctx, fields) -> entries (table), prefix (string)
// Returns file matches based on the provided parameters.
// This function is meant to be used as a helper in a command completion handler.
// #param query string
// #param ctx string
// #param fields table
func hcmpFiles(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
query, ctx, fds, err := getCompleteParams(t, c)
if err != nil {
return nil, err
@ -224,22 +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
}
func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
query, ctx, fds, err := getCompleteParams(t, c)
if err != nil {
return nil, err
}
// #interface completion
// handler(line, pos)
// 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.
// #param line string The current Hilbish command line
// #param pos number Numerical position of the cursor
/*
#example
-- stripped down version of the default implementation
function hilbish.completion.handler(line, pos)
local query = fields[#fields]
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
if #fields == 1 then
-- call bins handler here
else
-- call command completer or files completer here
end
end
#example
*/
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) {
if err := c.CheckNArgs(3); err != nil {
return "", "", []string{}, err

13
docs/_index.md 100644
View File

@ -0,0 +1,13 @@
---
title: Introduction
layout: doc
weight: -1
menu: docs
---
Hilbish is a hyper-extensible shell mainly intended for interactive use.
To enhance the interactive experience, Hilbish comes with a wide range
of features and sane defaults, including a nice looking prompt,
advanced completion menus and history search.
Here documents some of the features of Hilbish and the Lua API.

View File

@ -0,0 +1,9 @@
---
title: API
layout: doc
weight: -100
menu: docs
---
Welcome to the API documentation for Hilbish. This documents Lua functions
provided by Hilbish.

167
docs/api/bait.md 100644
View File

@ -0,0 +1,167 @@
---
title: Module bait
description: the event emitter
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
Bait is the event emitter for Hilbish. Much like Node.js and
its `events` system, many actions in Hilbish emit events.
Unlike Node.js, Hilbish events are global. So make sure to
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
|||
|----|----|
|<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`.|
<hr>
<div id='catch'>
<h4 class='heading'>
bait.catch(name, cb)
<a href="#catch" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Catches an event. This function can be used to act on events.
#### Parameters
`string` **`name`**
The name of the hook.
`function` **`cb`**
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

@ -0,0 +1,113 @@
---
title: Module commander
description: library for custom commands
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
Commander is the library which handles Hilbish commands. This makes
the user able to add Lua-written commands to their shell without making
a separate script in a bin folder. Instead, you may simply use the Commander
library in your Hilbish config.
```lua
local commander = require 'commander'
commander.register('hello', function(args, sinks)
sinks.out:writeln 'Hello world!'
end)
```
In this example, a command with the name of `hello` is created
that will print `Hello world!` to output. One question you may
have is: What is the `sinks` parameter?
The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
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 may use the read functions on this sink to get input from the user.
- `out` is standard output.
This is usually where command output should go.
- `err` is standard error.
This sink is for writing errors, as the name would suggest.
## Functions
|||
|----|----|
|<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:|
<hr>
<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>

257
docs/api/fs.md 100644
View File

@ -0,0 +1,257 @@
---
title: Module fs
description: filesystem interaction and functionality library
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The fs module provides filesystem functions to Hilbish. While Lua's standard
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
|||
|----|----|
|<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`.|
## Static module fields
|||
|----|----|
|pathSep|The operating system's path separator.|
<hr>
<div id='abs'>
<h4 class='heading'>
fs.abs(path) -> string
<a href="#abs" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns an absolute version of the `path`.
This can be used to resolve short paths like `..` to `/home/user`.
#### Parameters
`string` **`path`**
</div>
<hr>
<div id='basename'>
<h4 class='heading'>
fs.basename(path) -> string
<a href="#basename" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Returns the "basename," or the last part of the provided `path`. If path is empty,
`.` will be returned.
#### Parameters
`string` **`path`**
Path to get the base name of.
</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

@ -0,0 +1,544 @@
---
title: Module hilbish
description: the core Hilbish API
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The Hilbish module includes the core API, containing
interfaces and functions which directly relate to shell functionality.
## Functions
|||
|----|----|
|<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.|
## Static module fields
|||
|----|----|
|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|
<hr>
<div id='alias'>
<h4 class='heading'>
hilbish.alias(cmd, orig)
<a href="#alias" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Sets an alias, with a name of `cmd` to another command.
#### Parameters
`string` **`cmd`**
Name of the alias
`string` **`orig`**
Command that will be aliased
#### Example
```lua
-- 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).
```
</div>
<hr>
<div id='appendPath'>
<h4 class='heading'>
hilbish.appendPath(dir)
<a href="#appendPath" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Appends the provided dir to the command path (`$PATH`)
#### Parameters
`string|table` **`dir`**
Directory (or directories) to append to path
#### Example
```lua
hilbish.appendPath '~/go/bin'
-- Will add ~/go/bin to the command path.
-- Or do multiple:
hilbish.appendPath {
'~/go/bin',
'~/.local/bin'
}
```
</div>
<hr>
<div id='complete'>
<h4 class='heading'>
hilbish.complete(scope, cb)
<a href="#complete" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Registers a completion handler for the specified scope.
A `scope` is expected to be `command.<cmd>`,
replacing <cmd> with the name of the command (for example `command.git`).
The documentation for completions, under Features/Completions or `doc completions`
provides more details.
#### Parameters
`string` **`scope`**
`function` **`cb`**
#### 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
<hr>
## Sink
A sink is a structure that has input and/or output to/from
a desination.
### 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)
Writes data to a sink.
#### writeln(str)
Writes data to a sink with a newline at the end.

View File

@ -0,0 +1,91 @@
---
title: Module hilbish.aliases
description: command aliasing
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The alias interface deals with all command aliases in Hilbish.
## Functions
|||
|----|----|
|<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.|
<hr>
<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>
This is an alias (ha) for the [hilbish.alias](../#alias) function.
#### Parameters
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

@ -0,0 +1,103 @@
---
title: Module hilbish.editor
description: interactions for Hilbish's line reader
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The hilbish.editor interface provides functions to
directly interact with the line editor in use.
## Functions
|||
|----|----|
|<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.|
<hr>
<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>
Returns the current input line.
#### Parameters
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

@ -0,0 +1,102 @@
---
title: Module hilbish.history
description: command history
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The history interface deals with command history.
This includes the ability to override functions to change the main
method of saving history.
## Functions
|||
|----|----|
|<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.|
<hr>
<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>
Adds a command to the history.
#### Parameters
`string` **`cmd`**
</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

@ -0,0 +1,146 @@
---
title: Module hilbish.jobs
description: background job management
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
Manage interactive jobs in Hilbish via Lua.
Jobs are the name of background tasks/commands. A job can be started via
interactive usage or with the functions defined below for use in external runners.
## Functions
|||
|----|----|
|<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.|
<hr>
<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>
Creates a new job. This function does not run the job. This function is intended to be
used by runners, but can also be used to create jobs via Lua. Commanders cannot be ran as jobs.
#### Parameters
`string` **`cmdstr`**
String that a user would write for the job
`table` **`args`**
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
<hr>
## Job
The Job type describes a Hilbish job.
## 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|
|pid|The Process ID|
|exitCode|The last exit code of the job.|
|stdout|The standard output of the job. This just means the normal logs of the process.|
|stderr|The standard error stream of the process. This (usually) includes error messages of the job.|
### Methods
#### background()
Puts a job in the background. This acts the same as initially running a job.
#### foreground()
Puts a job in the foreground. This will cause it to run like it was
executed normally and wait for it to complete.
#### start()
Starts running the job.
#### stop()
Stops the job from running.

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

@ -0,0 +1,20 @@
---
title: Module hilbish.os
description: operating system info
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
Provides simple text information properties about the current operating system.
This mainly includes the name and version.
## Static module fields
|||
|----|----|
|family|Family name of the current OS|
|name|Pretty name of the current OS|
|version|Version of the current OS|

View File

@ -0,0 +1,114 @@
---
title: Module hilbish.runner
description: interactive command runner customization
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The runner interface contains functions that allow the user to change
how Hilbish interprets interactive input.
Users can add and change the default runner for interactive input to any
language or script of their choosing. A good example is using it to
write command in Fennel.
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
|||
|----|----|
|<a href="#runner.setMode">setMode(cb)</a>|This is the same as the `hilbish.runnerMode` function.|
|<a href="#runner.lua">lua(cmd)</a>|Evaluates `cmd` as Lua input. This is the same as using `dofile`|
|<a href="#runner.sh">sh(cmd)</a>|Runs a command in Hilbish's shell script interpreter.|
<hr>
<div id='runner.setMode'>
<h4 class='heading'>
hilbish.runner.setMode(cb)
<a href="#runner.setMode" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
This is the same as the `hilbish.runnerMode` function.
It takes a callback, which will be used to execute all interactive input.
In normal cases, neither callbacks should be overrided by the user,
as the higher level functions listed below this will handle it.
#### 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

@ -0,0 +1,100 @@
---
title: Module hilbish.timers
description: timeout and interval API
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
If you ever want to run a piece of code on a timed interval, or want to wait
a few seconds, you don't have to rely on timing tricks, as Hilbish has a
timer API to set intervals and timeouts.
These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc
accessible with `doc hilbish`, or `Module hilbish` on the Website).
An example of usage:
```lua
local t = hilbish.timers.create(hilbish.timers.TIMEOUT, 5000, function()
print 'hello!'
end)
t:start()
print(t.running) // true
```
## Functions
|||
|----|----|
|<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.|
## Static module fields
|||
|----|----|
|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
<hr>
## Timer
The Job type describes a Hilbish timer.
## 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|
### Methods
#### start()
Starts a timer.
#### stop()
Stops a timer.

View File

@ -0,0 +1,20 @@
---
title: Module hilbish.userDir
description: user-related directories
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
This interface just contains properties to know about certain user directories.
It is equivalent to XDG on Linux and gets the user's preferred directories
for configs and data.
## Static module fields
|||
|----|----|
|config|The user's config directory|
|data|The user's directory for program data|

View File

@ -0,0 +1,81 @@
---
title: Module terminal
description: low level terminal library
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The terminal library is a simple and lower level library for certain terminal interactions.
## Functions
|||
|----|----|
|<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`|
<hr>
<div id='restoreState'>
<h4 class='heading'>
terminal.restoreState()
<a href="#restoreState" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h4>
Restores the last saved state of the terminal
#### Parameters
This function has no parameters.
</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,10 +0,0 @@
catch(name, cb) > Catches a hook with `name`. Runs the `cb` when it is thrown
catchOnce(name, cb) > Same as catch, but only runs the `cb` once and then removes the hook
release(name, catcher) > Removes the `catcher` for the event with `name`
For this to work, `catcher` has to be the same function used to catch
an event, like one saved to a variable.
throw(name, ...args) > Throws a hook with `name` with the provided `args`

View File

@ -1,4 +0,0 @@
deregister(name) > Deregisters any command registered with `name`
register(name, cb) > Register a command with `name` that runs `cb` when ran

View File

@ -0,0 +1,78 @@
---
title: Completions
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
customizable. The default handler for completions in general can
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.
The default completion handler provides 3 things:
binaries (with a plain name requested to complete, those in
$PATH), files, or command completions. It will try to run a handler
for the command or fallback to file completions.
To overwrite it, just assign a function to `hilbish.completion.handler` like so:
```lua
-- line is the entire line as a string
-- pos is the position of the cursor.
function hilbish.completion.handler(line, pos)
-- do things
end
```

View File

@ -1,44 +0,0 @@
Hilbish has a pretty good completion system. It has a nice looking menu,
with 2 types of menus: grid (like file completions) or list.
Like most parts of Hilbish, it's made to be extensible and customizable.
The default handler for completions in general can be overwritten to provide
more advanced completions if needed.
# Completion Handler
By default, it provides 3 things: for the first argument, binaries (with a
plain name requested to complete, those in $PATH), files, or command
completions. With the default completion handler, it will try to run a
handler for the command or fallback to file completions.
To overwrite it, just assign a function to `hilbish.completion.handler`
like so:
function hilbish.completion.handler(line, pos)
-- do things
end
It is passed 2 arguments, the entire line, and the current cursor position.
The functions in the completion interface take 3 arguments: query, ctx,
and fields. The `query`, which what the user is currently trying to complete,
`ctx`, being just the entire line, and `fields` being a table of arguments.
It's just `ctx` split up, delimited by spaces.
It's expected to return 2 things: a table of completion groups, and a prefix.
A completion group is defined as a table with 2 keys: `items` and `type`.
The `items` field is just a table of items to use for completions.
The `type` is for the completion menu type, being either `grid` or `list`.
The prefix is what all the completions start with. It should be empty
if the user doesn't have a query. If the beginning of the completion
item does not match the prefix, it will be replaced and fixed properly
in the line. It is case sensitive.
If you want to overwrite the functionality of the general completion handler,
or make your command completion have files as well (and filter them),
then there is the `files` function, which is mentioned below.
# Completion Interface
## Functions
- `files(query, ctx, fields)` -> table, prefix: get file completions, based
on the user's query.
- `bins(query, ctx, fields)` -> table, prefix: get binary/executable
completions, based on user query.
- `call(scope, query, ctx, fields)` -> table, prefix: call a completion handler
with `scope`, usually being in the form of `command.<name>`

26
docs/faq.md 100644
View File

@ -0,0 +1,26 @@
---
title: Frequently Asked Questions
layout: doc
weight: -20
menu: docs
---
# Is Hilbish POSIX compliant?
No, it is not. POSIX compliance is a non-goal. Perhaps in the future,
someone would be able to write a native plugin to support shell scripting
(which would be against it's main goal, but ....)
# Windows Support?
It compiles for Windows (CI ensures it does), but otherwise it is not
directly supported. If you'd like to improve this situation,
checkout [the discussion](https://github.com/Rosettea/Hilbish/discussions/165).
# Why?
Hilbish emerged from the desire of a Lua configured shell.
It was the initial reason that it was created, but now it's more:
to be hyper extensible, simpler and more user friendly.
# 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

@ -0,0 +1,66 @@
---
title: Runner Mode
description: Customize the interactive script/command runner.
layout: doc
menu:
docs:
parent: "Features"
---
Hilbish allows you to change how interactive text can be interpreted.
This is mainly due to the fact that the default method Hilbish uses
is that it runs Lua first and then falls back to shell script.
In some cases, someone might want to switch to just shell script to avoid
it while interactive but still have a Lua config, or go full Lua to use
Hilbish as a REPL. This also allows users to add alternative languages 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`,
which determines how Hilbish will run user input. By default, this is
set to `hybrid` which is the previously mentioned behaviour of running Lua
first then going to shell script. If you want the reverse order, you can
set it to `hybridRev` and for isolated modes there is `sh` and `lua`
respectively.
You can also set it to a function, which will be called everytime Hilbish
needs to run interactive input. For more detail, see the [API documentation](../../api/hilbish/hilbish.runner)
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
and also provides the shell script and Lua runner functions that Hilbish itself uses.
A runner function is expected to return a table with the following values:
- `exitCode` (number): Exit code of the command
- `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
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
These are the "low level" functions for the `hilbish.runner` interface.
+ setMode(mode) > The same as `hilbish.runnerMode`
+ sh(input) -> table > Runs `input` in Hilbish's sh interpreter
+ lua(input) -> table > Evals `input` as Lua code
These functions should be preferred over the previous ones.
+ setCurrent(mode) > The same as `setMode`, but works with runners managed
via the functions below.
+ add(name, runner) > Adds a runner to a table of available runners. The `runner`
argument is either a function or a table with a run callback.
+ set(name, runner) > The same as `add` but requires passing a table and
overwrites if the `name`d runner already exists.
+ get(name) > runner > Gets a runner by name. It is a table with at least a
run function, to run input.
+ exec(cmd, runnerName) > Runs `cmd` with a runner. If `runnerName` isn't passed,
the current runner mode is used.

View File

@ -1,22 +0,0 @@
abs(path) > Gives an absolute version of `path`.
basename(path) > Gives the basename of `path`. For the rules,
see Go's filepath.Base
cd(dir) > Changes directory to `dir`
dir(path) > Returns the directory part of `path`. For the rules, see Go's
filepath.Dir
glob(pattern) > Glob all files and directories that match the pattern.
For the rules, see Go's filepath.Glob
join(paths...) > Takes paths and joins them together with the OS's
directory separator (forward or backward slash).
mkdir(name, recursive) > Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
readdir(dir) > Returns a table of files in `dir`
stat(path) > Returns info about `path`

View File

@ -0,0 +1,60 @@
---
title: Getting Started
layout: doc
weight: -10
menu: docs
---
To start Hilbish, open a terminal. If Hilbish has been installed and is not the
default shell, you can simply run `hilbish` to start it. This will launch
a normal interactive session.
To exit, you can either run the `exit` command or hit Ctrl+D.
# Setting as Default
## Login shell
There are a few ways to make Hilbish your default shell. A simple way is
to make it your user/login shell.
{{< warning `It is not recommended to set Hilbish as your login shell. That
is expected to be a POSIX compliant shell, which Hilbish is not. Though if
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`.
Some distros (namely Fedora) might have `lchsh` instead, which is used like `lchsh <user>`.
When prompted, you can put the path for Hilbish.
## Default with terminal
The simpler way is to set the default shell for your terminal. The way of
doing this depends on how your terminal settings are configured.
## Run after login shell
Some shells (like zsh) have an rc file, like `.zlogin`, which is ran when the shell session
is a login shell. In that file, you can run Hilbish. Example:
```
exec hilbish -S -l
```
This will replace the shell with Hilbish, set $SHELL to Hilbish and launch it as a login shell.
# Configuration
Once installation and setup has been done, you can then configure Hilbish.
It is configured and scripted via Lua, so the config file is a Lua file.
You can use any pure Lua library to do whatever you want.
Hilbish's sample configuration is usually located in `hilbish.dataDir .. '/.hilbishrc.lua'`.
You can print that path via Lua to see what it is: `print(hilbish.dataDir .. '/.hilbishrc.lua')`.
As an example, it will usually will result in `/usr/share/hilbish/.hilbishrc.lua` on Linux.
To edit your user configuration, you can copy that file to `hilbish.userDir.config .. '/hilbish/init.lua'`,
which follows XDG on Linux and MacOS, and is located in %APPDATA% on Windows.
As the directory is usually `~/.config` on Linux, you can run this command to copy it:
`cp /usr/share/hilbish/.hilbishrc.lua ~/.config/hilbish/init.lua`
Now you can get to editing it. Since it's just a Lua file, having basic
knowledge of Lua would help. All of Lua's standard libraries and functions
from Lua 5.4 are available. Hilbish has some custom and modules that are
available. To see them, you can run the `doc` command. This also works as
general documentation for other things.

View File

@ -1,62 +0,0 @@
alias(cmd, orig) > Sets an alias of `cmd` to `orig`
appendPath(dir) > Appends `dir` to $PATH
complete(scope, cb) > Registers a completion handler for `scope`.
A `scope` is currently only expected to be `command.<cmd>`,
replacing <cmd> with the name of the command (for example `command.git`).
`cb` must be a function that returns a table of "completion groups."
Check `doc completions` for more information.
cwd() > Returns the current directory of the shell
exec(cmd) > Replaces running hilbish with `cmd`
goro(fn) > Puts `fn` in a goroutine
highlighter(line) > Line highlighter handler. This is mainly for syntax highlighting, but in
reality could set the input of the prompt to *display* anything. The
callback is passed the current line and is expected to return a line that
will be used as the input display.
hinter(line, pos) > The command line hint handler. It gets called on every key insert to
determine what text to use as an inline hint. It is passed the current
line and cursor position. It is expected to return a string which is used
as the text for the hint. This is by default a shim. To set hints,
override this function with your custom handler.
inputMode(mode) > Sets the input mode for Hilbish's line reader. Accepts either emacs or vim
interval(cb, time) > Runs the `cb` function every `time` milliseconds.
Returns a `timer` object (see `doc timers`).
multiprompt(str) > Changes the continued line prompt to `str`
prependPath(dir) > Prepends `dir` to $PATH
prompt(str, typ?) > Changes the shell prompt to `str`
There are a few verbs that can be used in the prompt text.
These will be formatted and replaced with the appropriate values.
`%d` - Current working directory
`%u` - Name of current user
`%h` - Hostname of device
read(prompt) -> input? > Read input from the user, using Hilbish's line editor/input reader.
This is a separate instance from the one Hilbish actually uses.
Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
run(cmd, returnOut) -> exitCode, stdout, stderr > Runs `cmd` in Hilbish's sh interpreter.
If returnOut is true, the outputs of `cmd` will be returned as the 2nd and
3rd values instead of being outputted to the terminal.
runnerMode(mode) > Sets the execution/runner mode for interactive Hilbish. This determines whether
Hilbish wll try to run input as Lua and/or sh or only do one of either.
Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
sh, and lua. It also accepts a function, to which if it is passed one
will call it to execute user input instead.
timeout(cb, time) > Runs the `cb` function after `time` in milliseconds
Returns a `timer` object (see `doc timers`).
which(name) > Checks if `name` is a valid command

View File

@ -0,0 +1,11 @@
---
title: Signals
description:
layout: doc
weight: -50
menu:
docs
---
Signals are global events emitted with the [Bait](../api/bait) module.
For more detail on how to use these signals, you may check the Bait page.

View File

@ -0,0 +1,67 @@
---
title: Command
description:
layout: doc
menu:
docs:
parent: "Signals"
---
## command.preexec
Thrown right before a command is executed.
#### Variables
`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,7 +0,0 @@
+ `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.
+ `command.not-found` -> cmdStr > Thrown when a command is not found.
+ `command.no-perm` -> cmdStr > Thrown when Hilbish attempts to execute a file but
has no permission.

View File

@ -0,0 +1,47 @@
---
title: Hilbish
description:
layout: doc
menu:
docs:
parent: "Signals"
---
## hilbish.exit
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
like yanking or pasting text. See `doc vim-mode actions` for more info.

View File

@ -1,7 +0,0 @@
+ `hilbish.exit` > Sent when Hilbish is about to exit.
+ `hilbish.vimMode` -> modeName > Sent when Hilbish's Vim mode is changed (example insert to normal mode),
`modeName` is the name of the mode changed to (can be `insert`, `normal`, `delete` or `replace`).
+ `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.

View File

@ -1,8 +0,0 @@
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.
Here is the format for a doc for a hook:
+ <hook name> -> <args> > <description>
`<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`.

View File

@ -0,0 +1,40 @@
---
title: Signal
description:
layout: doc
menu:
docs:
parent: "Signals"
---
## signal.sigint
Thrown when Hilbish receive the SIGINT signal,
aka when Ctrl-C is pressed.
#### 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.

View File

@ -1,7 +0,0 @@
+ `signal.sigint` > Sent when Hilbish receives SIGINT (on Ctrl-C).
+ `signal.resize` > Sent when the terminal is resized.
+ `signal.sigusr1`
+ `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,
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.

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
by default in standard Hilbish distributions to provide easy styling
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,
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
(`doc`, `cd`, cdr), completions, and other things.
Hilbish's Lua core module is called `nature`. It's handled after everything
on the Go side initializes, which is what that first sentence was from.
Hilbish's Lua core module is called `nature`.
It runs after Hilbish's Go core does.
# Nature Modules
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,55 +0,0 @@
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
really be using Hilbish anyway but you'd also not want this
(or maybe want Lua only in some cases.)
The "runner mode" of Hilbish is customizable via `hilbish.runnerMode`,
which determines how Hilbish will run user input. By default, this is
set to `hybrid` which is the previously mentioned behaviour of running Lua
first then going to shell script. If you want the reverse order, you can
set it to `hybridRev` and for isolated modes there is `sh` and `lua`
respectively.
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
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`
and also provides the sh 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.
If you don't, just return the input passed to the runner function.
The exit code has to be a number, it will be 0 otherwise and the error can be
`nil` to indicate no error.
## Functions
These are the "low level" functions for the `hilbish.runner` interface.
+ setMode(mode) > The same as `hilbish.runnerMode`
+ sh(input) -> input, code, err > Runs `input` in Hilbish's sh interpreter
+ lua(input) -> input, code, err > Evals `input` as Lua code
The others here are defined in Lua and have EmmyLua documentation.
These functions should be preferred over the previous ones.
+ setCurrent(mode) > The same as `setMode`, but works with runners managed
via the functions below.
+ add(name, runner) > Adds a runner to a table of available runners. The `runner`
argument is either a function or a table with a run callback.
+ set(name, runner) > The same as `add` but requires passing a table and
overwrites if the `name`d runner already exists.
+ get(name) > runner > Gets a runner by name. It is a table with at least a
run function, to run input.
+ exec(cmd, runnerName) > Runs `cmd` with a runner. If `runnerName` isn't passed,
the current runner mode is used.

View File

@ -1,9 +0,0 @@
restoreState() > Restores the last saved state of the terminal
saveState() > Saves the current state of the terminal
setRaw() > Puts the terminal in raw mode
size() > Gets the dimensions of the terminal. Returns a table with `width` and `height`
Note: this is not the size in relation to the dimensions of the display

1
docs/timers.md 100644
View File

@ -0,0 +1 @@
This has been moved to the `hilbish.timers` API doc (accessible by `doc api hilbish.timers`)

View File

@ -1,38 +0,0 @@
If you ever want to run a piece of code on a timed interval, or want to wait
a few seconds, you don't have to rely on timing tricks, as Hilbish has a
timer API to set intervals and timeouts.
These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc
accessible with `doc hilbish`). But if you want slightly more control over
them, there is the `hilbish.timers` interface. It allows you to get
a timer via ID.
# Timer Interface
## Functions
- `get(id)` -> timer: get a timer via its id
- `create(type, ms, callback)` -> timer: creates a timer, adding it to the timer pool.
`type` is the type of timer it will be. 0 is an interval, 1 is a timeout.
`ms` is the time it will run for in seconds. callback is the function called
when the timer is triggered.
# Timer Object
All those previously mentioned functions return a `timer` object, to which
you can stop and start a timer again.
An example of usage:
local t = hilbish.timers.create(1, 5000, function()
print 'hello!'
end)
t:stop()
print(t.running, t.duration, t.type)
t:start()
## Properties
- `duration`: amount of time the timer runs for in milliseconds
- `running`: whether the timer is running or not
- `type`: the type of timer (0 is interval, 1 is timeout)
## Functions
- `stop()`: stops the timer. returns an error if it's already stopped
- `start()`: starts the timer. returns an error if it's already started

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.
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.
Things like yanking and pasting are Vim actions.
This is not an "offical Vim thing," just a Hilbish thing.

View File

@ -6,12 +6,17 @@ import (
rt "github.com/arnodel/golua/runtime"
)
// #interface editor
// interactions for Hilbish's line reader
// The hilbish.editor interface provides functions to
// directly interact with the line editor in use.
func editorLoader(rtm *rt.Runtime) *rt.Table {
exports := map[string]util.LuaExport{
"insert": {editorInsert, 1, false},
"setVimRegister": {editorSetRegister, 1, false},
"getVimRegister": {editorGetRegister, 2, false},
"getLine": {editorGetLine, 0, false},
"readChar": {editorReadChar, 0, false},
}
mod := rt.NewTable()
@ -20,6 +25,10 @@ func editorLoader(rtm *rt.Runtime) *rt.Table {
return mod
}
// #interface editor
// insert(text)
// Inserts text into the Hilbish command line.
// #param text string
func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -35,6 +44,11 @@ func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// #interface editor
// setVimRegister(register, text)
// Sets the vim register at `register` to hold the passed text.
// #aram register string
// #param text string
func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -55,6 +69,10 @@ func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// #interface editor
// getVimRegister(register) -> string
// Returns the text that is at the register.
// #param register string
func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -70,8 +88,21 @@ func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
}
// #interface editor
// getLine() -> string
// Returns the current input line.
// #returns string
func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
buf := lr.rl.GetLine()
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,24 +2,27 @@
local bait = {}
--- Catches a hook with `name`. Runs the `cb` when it is thrown
--- @param name string
--- @param cb function
--- Catches an event. This function can be used to act on events.
---
---
function bait.catch(name, cb) end
--- Same as catch, but only runs the `cb` once and then removes the hook
--- @param name string
--- @param cb function
--- Catches an event, but only once. This will remove the hook immediately after it runs for the first time.
function bait.catchOnce(name, cb) end
--- Removes the `catcher` for the event with `name`
--- Returns a table of functions that are hooked on an event with the corresponding `name`.
function bait.hooks(name) end
--- Removes the `catcher` for the event with `name`.
--- For this to work, `catcher` has to be the same function used to catch
--- an event, like one saved to a variable.
function bait.release() end
---
---
function bait.release(name, catcher) end
--- Throws a hook with `name` with the provided `args`
--- @param name string
--- @vararg any
function bait.throw(name, ...) end
--- Throws a hook with `name` with the provided `args`.
---
---
function bait.throw(name, ...args) end
return bait

View File

@ -2,13 +2,17 @@
local commander = {}
--- Deregisters any command registered with `name`
--- @param name string
--- Removes the named command. Note that this will only remove Commander-registered commands.
function commander.deregister(name) end
--- Register a command with `name` that runs `cb` when ran
--- @param name string
--- @param cb function
--- 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.
---
---
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

View File

@ -2,42 +2,53 @@
local fs = {}
--- Gives an absolute version of `path`.
--- @param path string
--- Returns an absolute version of the `path`.
--- This can be used to resolve short paths like `..` to `/home/user`.
function fs.abs(path) end
--- Gives the basename of `path`. For the rules,
--- see Go's filepath.Base
function fs.basename() end
--- Returns the "basename," or the last part of the provided `path`. If path is empty,
--- `.` will be returned.
function fs.basename(path) end
--- Changes directory to `dir`
--- @param dir string
--- Changes Hilbish's directory to `dir`.
function fs.cd(dir) end
--- Returns the directory part of `path`. For the rules, see Go's
--- filepath.Dir
function fs.dir() end
--- Returns the directory part of `path`. If a file path like
--- `~/Documents/doc.txt` then this function will return `~/Documents`.
function fs.dir(path) end
--- Glob all files and directories that match the pattern.
--- For the rules, see Go's filepath.Glob
function fs.glob() end
--- 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
---
---
function fs.glob(pattern) end
--- Takes paths and joins them together with the OS's
--- directory separator (forward or backward slash).
function fs.join() end
--- Takes any list of paths and joins them based on the operating system's path separator.
---
---
function fs.join(...path) end
--- Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
--- @param name string
--- @param recursive boolean
--- Creates a new directory with the provided `name`.
--- With `recursive`, mkdir will create parent directories.
---
---
function fs.mkdir(name, recursive) end
--- Returns a table of files in `dir`
--- @param dir string
--- @return table
function fs.readdir(dir) end
--- Returns a pair of connected files, also known as a pipe.
--- The type returned is a Lua file, same as returned from `io` functions.
function fs.fpipe() end
--- Returns info about `path`
--- @param path string
--- Returns a list of all files and directories in the provided path.
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
--- 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
---
---
function fs.stat(path) end
return fs

View File

@ -2,40 +2,92 @@
local hilbish = {}
--- Sets an alias of `cmd` to `orig`
--- This is an alias (ha) for the [hilbish.alias](../#alias) function.
--- @param alias string
--- @param cmd string
--- @param orig string
function hilbish.aliases.add(alias, cmd) end
--- This is the same as the `hilbish.runnerMode` function.
--- It takes a callback, which will be used to execute all interactive input.
--- In normal cases, neither callbacks should be overrided by the user,
--- as the higher level functions listed below this will handle it.
function hilbish.runner.setMode(cb) end
--- Returns the current input line.
function hilbish.editor.getLine() end
--- Returns the text that is at the register.
function hilbish.editor.getVimRegister(register) end
--- Inserts text into the Hilbish command line.
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.
function hilbish.editor.setVimRegister(register, text) end
--- Return binaries/executables based on the provided parameters.
--- This function is meant to be used as a helper in a command completion handler.
---
---
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
--- Appends `dir` to $PATH
--- @param dir string|table
--- Appends the provided dir to the command path (`$PATH`)
---
---
function hilbish.appendPath(dir) end
--- Registers a completion handler for `scope`.
--- A `scope` is currently only expected to be `command.<cmd>`,
--- Registers a completion handler for the specified scope.
--- A `scope` is expected to be `command.<cmd>`,
--- replacing <cmd> with the name of the command (for example `command.git`).
--- `cb` must be a function that returns a table of "completion groups."
--- Check `doc completions` for more information.
--- @param scope string
--- @param cb function
--- The documentation for completions, under Features/Completions or `doc completions`
--- provides more details.
---
---
function hilbish.complete(scope, cb) end
--- Returns the current directory of the shell
--- Returns the current directory of the shell.
function hilbish.cwd() end
--- Replaces running hilbish with `cmd`
--- @param cmd string
--- Replaces the currently running Hilbish instance with the supplied command.
--- This can be used to do an in-place restart.
function hilbish.exec(cmd) end
--- Puts `fn` in a goroutine
--- @param fn function
--- 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.**
function hilbish.goro(fn) end
--- 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.
--- @param line string
--- 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.
---
function hilbish.highlighter(line) end
--- The command line hint handler. It gets called on every key insert to
@ -43,68 +95,171 @@ function hilbish.highlighter(line) end
--- 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.
--- @param line string
--- @param pos int
---
---
function hilbish.hinter(line, pos) end
--- Sets the input mode for Hilbish's line reader. Accepts either emacs or vim
--- @param mode string
--- 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.
function hilbish.inputMode(mode) end
--- Runs the `cb` function every `time` milliseconds.
--- Returns a `timer` object (see `doc timers`).
--- @param cb function
--- @param time number
--- @return table
--- Runs the `cb` function every specified amount of `time`.
--- This creates a timer that ticking immediately.
function hilbish.interval(cb, time) end
--- Changes the continued line prompt to `str`
--- @param str string
--- Changes the text prompt when Hilbish asks for more input.
--- This will show up when text is incomplete, like a missing quote
---
---
function hilbish.multiprompt(str) end
--- Prepends `dir` to $PATH
--- @param dir string
--- Prepends `dir` to $PATH.
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.
--- These will be formatted and replaced with the appropriate values.
--- `%d` - Current working directory
--- `%u` - Name of current user
--- `%h` - Hostname of device
--- @param str string
--- @param typ string Type of prompt, being left or right. Left by default.
---
function hilbish.prompt(str, typ) end
--- Read input from the user, using Hilbish's line editor/input reader.
--- This is a separate instance from the one Hilbish actually uses.
--- Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
--- @param prompt string
--- Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
function hilbish.read(prompt) end
--- Runs `cmd` in Hilbish's sh interpreter.
--- If returnOut is true, the outputs of `cmd` will be returned as the 2nd and
--- 3rd values instead of being outputted to the terminal.
--- @param cmd string
function hilbish.run(cmd) end
--- 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.
---
function hilbish.run(cmd, streams) end
--- Sets the execution/runner mode for interactive Hilbish. This determines whether
--- Hilbish wll try to run input as Lua and/or sh or only do one of either.
--- 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.
--- @param mode string|function
--- Read [about runner mode](../features/runner-mode) for more information.
function hilbish.runnerMode(mode) end
--- Runs the `cb` function after `time` in milliseconds
--- Returns a `timer` object (see `doc timers`).
--- @param cb function
--- @param time number
--- @return table
--- Executed the `cb` function after a period of `time`.
--- This creates a Timer that starts ticking immediately.
function hilbish.timeout(cb, time) end
--- Checks if `name` is a valid command
--- @param binName string
function hilbish.which(binName) end
--- Checks if `name` is a valid command.
--- Will return the path of the binary, or a basename if it's a commander.
function hilbish.which(name) end
--- Puts a job in the background. This acts the same as initially running a job.
function hilbish.jobs:background() end
--- Puts a job in the foreground. This will cause it to run like it was
--- executed normally and wait for it to complete.
function hilbish.jobs:foreground() end
--- Evaluates `cmd` as Lua input. This is the same as using `dofile`
--- or `load`, but is appropriated for the runner interface.
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.
function hilbish:write(str) end
--- Writes data to a sink with a newline at the end.
function hilbish:writeln(str) end
--- Starts running the job.
function hilbish.jobs:start() end
--- Stops the job from running.
function hilbish.jobs:stop() end
--- 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.
--- This is the equivalent of using `source`.
function hilbish.runner.sh(cmd) end
--- Starts a timer.
function hilbish.timers:start() end
--- Stops a timer.
function hilbish.timers:stop() end
--- Removes an alias.
function hilbish.aliases.delete(name) end
--- Get a table of all aliases, with string keys as the alias and the value as the command.
---
---
function hilbish.aliases.list() end
--- Resolves an alias to its original command. Will thrown an error if the alias doesn't exist.
function hilbish.aliases.resolve(alias) end
--- Creates a new job. This function does not run the job. This function is intended to be
--- used by runners, but can also be used to create jobs via Lua. Commanders cannot be ran as jobs.
---
---
function hilbish.jobs.add(cmdstr, args, execPath) end
--- Returns a table of all job objects.
function hilbish.jobs.all() end
--- Disowns a job. This simply deletes it from the list of jobs without stopping it.
function hilbish.jobs.disown(id) end
--- Get a job object via its ID.
--- @param id number
--- @returns Job
function hilbish.jobs.get(id) end
--- Returns the last added job to the table.
function hilbish.jobs.last() end
--- Adds a command to the history.
function hilbish.history.add(cmd) end
--- Retrieves all history as a table.
function hilbish.history.all() end
--- Deletes all commands from the history.
function hilbish.history.clear() end
--- Retrieves a command from the history based on the `index`.
function hilbish.history.get(index) end
--- Returns the amount of commands in the history.
function hilbish.history.size() end
--- Creates a timer that runs based on the specified `time`.
function hilbish.timers.create(type, time, callback) end
--- Retrieves a timer via its ID.
function hilbish.timers.get(id) end
return hilbish

View File

@ -5,14 +5,14 @@ local terminal = {}
--- Restores the last saved state of the terminal
function terminal.restoreState() end
--- Saves the current state of the terminal
--- Saves the current state of the terminal.
function terminal.saveState() end
--- Puts the terminal in raw mode
--- Puts the terminal into raw mode.
function terminal.setRaw() end
--- 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
return terminal

97
exec.go
View File

@ -29,6 +29,12 @@ var errNotExec = errors.New("not executable")
var errNotFound = errors.New("not found")
var runnerMode rt.Value = rt.StringValue("hybrid")
type streams struct {
stdout io.Writer
stderr io.Writer
stdin io.Reader
}
type execError struct{
typ string
cmd string
@ -97,23 +103,23 @@ func runInput(input string, priv bool) {
if currentRunner.Type() == rt.StringType {
switch currentRunner.AsString() {
case "hybrid":
_, _, err = handleLua(cmdString)
_, _, err = handleLua(input)
if err == nil {
cmdFinish(0, input, priv)
return
}
input, exitCode, cont, err = handleSh(cmdString)
input, exitCode, cont, err = handleSh(input)
case "hybridRev":
_, _, _, err = handleSh(input)
if err == nil {
cmdFinish(0, input, priv)
return
}
input, exitCode, err = handleLua(cmdString)
input, exitCode, err = handleLua(input)
case "lua":
input, exitCode, err = handleLua(cmdString)
input, exitCode, err = handleLua(input)
case "sh":
input, exitCode, cont, err = handleSh(cmdString)
input, exitCode, cont, err = handleSh(input)
}
} else {
// can only be a string or function so
@ -142,9 +148,9 @@ func runInput(input string, priv bool) {
if err != nil {
if exErr, ok := isExecError(err); ok {
hooks.Emit("command." + exErr.typ, exErr.cmd)
err = exErr.sprint()
} else {
fmt.Fprintln(os.Stderr, err)
}
fmt.Fprintln(os.Stderr, err)
}
cmdFinish(exitCode, input, priv)
}
@ -176,6 +182,9 @@ func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8
runnerRet := term.Get(0)
if runner, ok = runnerRet.TryTable(); !ok {
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 {
@ -196,7 +205,8 @@ func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8
return
}
func handleLua(cmdString string) (string, uint8, error) {
func handleLua(input string) (string, uint8, error) {
cmdString := aliases.Resolve(input)
// First try to load input, essentially compiling to bytecode
chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv()))
if err != nil && noexecute {
@ -233,7 +243,7 @@ func handleSh(cmdString string) (input string, exitCode uint8, cont bool, runErr
}
func execSh(cmdString string) (string, uint8, bool, error) {
_, _, err := execCommand(cmdString, true)
_, _, err := execCommand(cmdString, nil)
if err != nil {
// If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) {
@ -254,7 +264,7 @@ func execSh(cmdString string) (string, uint8, bool, error) {
}
// 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), "")
if err != nil {
return nil, nil, err
@ -262,15 +272,24 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
runner, _ := interp.New()
var stdout io.Writer
var stderr io.Writer
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 == nil {
strms = &streams{}
}
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)
printer := syntax.NewPrinter()
@ -289,11 +308,11 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
interp.ExecHandler(execHandle(bg))(runner)
err = runner.Run(context.TODO(), stmt)
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 {
@ -323,30 +342,19 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
}
if commands[args[0]] != nil {
t := rt.NewThread(l)
sig := make(chan os.Signal)
exit := make(chan bool)
hc := interp.HandlerCtx(ctx)
if cmd := cmds.Commands[args[0]]; cmd != nil {
stdin := newSinkInput(hc.Stdin)
stdout := newSinkOutput(hc.Stdout)
stderr := newSinkOutput(hc.Stderr)
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
sinks := rt.NewTable()
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("err"), rt.UserDataValue(stderr.ud))
signal.Notify(sig, os.Interrupt)
select {
case <-sig:
t.KillContext()
return
case <-exit:
return
}
}()
luaexitcode, err := rt.Call1(t, rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs))
exit <- true
luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(cmd), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
if err != nil {
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
return interp.NewExitStatus(1)
@ -358,7 +366,7 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
exitcode = uint8(code)
} else if luaexitcode != rt.NilValue {
// 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])
}
@ -386,7 +394,6 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
killTimeout := 2 * time.Second
// from here is basically copy-paste of the default exec handler from
// sh/interp but with our job handling
hc := interp.HandlerCtx(ctx)
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
if err != nil {
fmt.Fprintln(hc.Stderr, err)
@ -574,7 +581,7 @@ func splitInput(input string) ([]string, string) {
}
func cmdFinish(code uint8, cmdstr string, private bool) {
util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)), "Exit code of last exected command")
util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)))
// using AsValue (to convert to lua type) on an interface which is an int
// results in it being unknown in lua .... ????
// so we allow the hook handler to take lua runtime Values

View File

@ -1,4 +1,4 @@
// +build linux darwin
//go:build unix
package main

View File

@ -1,4 +1,4 @@
// +build windows
//go:build windows
package main

BIN
gallery/tab.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

28
go.mod
View File

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

65
go.sum
View File

@ -1,30 +1,21 @@
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac h1:dtXrgjch8PQyf7C90anZUquB5U3dr8AcMGJofeuirrI=
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac/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/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/golua v0.0.0-20240427174124-d239074c1749 h1:jIFnWBTsYw8s7RX7H2AOXjDVhWP3ol7OzUVaPN2KnGI=
github.com/Rosettea/golua v0.0.0-20240427174124-d239074c1749/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b h1:s5eDMhBk6H1BgipgLub/gv9qeyBaTuiHM0k3h2/9TSE=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
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/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4KTKzJfWs=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c=
github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504 h1:R1/AOzdMbopSliUTTEHvHbyNmnZ3YxY5GvdhTkpPsSY=
github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504/go.mod h1:kHBCvAXJIatTX1pw6tLiOspjGc3MhUDRlog9yrCUS+k=
github.com/blackfireio/osinfo v1.0.5 h1:6hlaWzfcpb87gRmznVf7wSdhysGqLRz9V/xuSdCEXrA=
github.com/blackfireio/osinfo v1.0.5/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/evilsocket/islazy v1.10.6 h1:MFq000a1ByoumoJWlytqg0qon0KlBeUfPsDjY0hK0bo=
github.com/evilsocket/islazy v1.10.6/go.mod h1:OrwQGYg3DuZvXUfmH+KIZDjwTCbrjy48T24TUpGqVVw=
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/evilsocket/islazy v1.11.0 h1:B5w6uuS6ki6iDG+aH/RFeoMb8ijQh/pGabewqp2UeJ0=
github.com/evilsocket/islazy v1.11.0/go.mod h1:muYH4x5MB5YRdkxnrOtrXLIBX6LySj1uFIqys94LKdo=
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.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@ -32,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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
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/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
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-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
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/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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=
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=

View File

@ -1,6 +1,33 @@
// the event emitter
/*
Bait is the event emitter for Hilbish. Much like Node.js and
its `events` system, many actions in Hilbish emit events.
Unlike Node.js, Hilbish events are global. So make sure to
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
import (
"errors"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
@ -72,8 +99,12 @@ func (b *Bait) Emit(event string, args ...interface{}) {
}
_, err := rt.Call1(b.rtm.MainThread(), funcVal, luaArgs...)
if err != nil {
// panicking here won't actually cause hilbish to panic and instead will
// print the error and remove the hook. reference the recoverer function in lua.go
if event != "error" {
b.Emit("error", event, handle.luaCaller, err.Error())
return
}
// if there is an error in an error event handler, panic instead
// (calls the go recoverer function)
panic(err)
}
} else {
@ -187,19 +218,11 @@ func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
"catchOnce": util.LuaExport{b.bcatchOnce, 2, false},
"throw": util.LuaExport{b.bthrow, 1, true},
"release": util.LuaExport{b.brelease, 2, false},
"hooks": util.LuaExport{b.bhooks, 1, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
util.Document(mod,
`Bait is the event emitter for Hilbish. Why name it bait?
Because it throws hooks that you can catch (emits events
that you can listen to) and because why not, fun naming
is fun. This is what you will use if you want to listen
in on hooks to know when certain things have happened,
like when you've changed directory, a command has
failed, etc. To find all available hooks, see doc hooks.`)
return rt.TableValue(mod), nil
}
@ -224,10 +247,117 @@ func handleHook(t *rt.Thread, c *rt.GoCont, name string, catcher *rt.Closure, ar
}
}
// catch(name, cb)
// Catches an event. This function can be used to act on events.
// #param name string The name of the hook.
// #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) {
name, catcher, err := util.HandleStrCallback(t, c)
if err != nil {
return nil, err
}
b.OnLua(name, catcher)
return c.Next(), nil
}
// catchOnce(name, cb)
// Catches an event, but only once. This will remove the hook immediately after it runs for the first time.
// #param name string The name of the event
// #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) {
name, catcher, err := util.HandleStrCallback(t, c)
if err != nil {
return nil, err
}
b.OnceLua(name, catcher)
return c.Next(), nil
}
// hooks(name) -> table
// Returns a table of functions that are hooked on an event with the corresponding `name`.
// #param name string The name of the hook
// #returns table<function>
func (b *Bait) bhooks(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
evName, err := c.StringArg(0)
if err != nil {
return nil, err
}
noHooks := errors.New("no hooks for event " + evName)
handlers := b.handlers[evName]
if handlers == nil {
return nil, noHooks
}
luaHandlers := rt.NewTable()
for _, handler := range handlers {
if handler.typ != luaListener { continue }
luaHandlers.Set(rt.IntValue(luaHandlers.Len() + 1), rt.FunctionValue(handler.luaCaller))
}
if luaHandlers.Len() == 0 {
return nil, noHooks
}
return c.PushingNext1(t.Runtime, rt.TableValue(luaHandlers)), nil
}
// 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)
// Throws a hook with `name` with the provided `args`
// --- @param name string
// --- @vararg any
// #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
@ -244,48 +374,3 @@ func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// catch(name, cb)
// Catches a hook with `name`. Runs the `cb` when it is thrown
// --- @param name string
// --- @param cb function
func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
name, catcher, err := util.HandleStrCallback(t, c)
if err != nil {
return nil, err
}
b.OnLua(name, catcher)
return c.Next(), nil
}
// catchOnce(name, cb)
// Same as catch, but only runs the `cb` once and then removes the hook
// --- @param name string
// --- @param cb function
func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
name, catcher, err := util.HandleStrCallback(t, c)
if err != nil {
return nil, err
}
b.OnceLua(name, catcher)
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.
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
}

View File

@ -1,3 +1,35 @@
// library for custom commands
/*
Commander is the library which handles Hilbish commands. This makes
the user able to add Lua-written commands to their shell without making
a separate script in a bin folder. Instead, you may simply use the Commander
library in your Hilbish config.
```lua
local commander = require 'commander'
commander.register('hello', function(args, sinks)
sinks.out:writeln 'Hello world!'
end)
```
In this example, a command with the name of `hello` is created
that will print `Hello world!` to output. One question you may
have is: What is the `sinks` parameter?
The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
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 may use the read functions on this sink to get input from the user.
- `out` is standard output.
This is usually where command output should go.
- `err` is standard error.
This sink is for writing errors, as the name would suggest.
*/
package commander
import (
@ -11,11 +43,13 @@ import (
type Commander struct{
Events *bait.Bait
Loader packagelib.Loader
Commands map[string]*rt.Closure
}
func New(rtm *rt.Runtime) Commander {
c := Commander{
func New(rtm *rt.Runtime) *Commander {
c := &Commander{
Events: bait.New(rtm),
Commands: make(map[string]*rt.Closure),
}
c.Loader = packagelib.Loader{
Load: c.loaderFunc,
@ -29,32 +63,45 @@ func (c *Commander) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
exports := map[string]util.LuaExport{
"register": util.LuaExport{c.cregister, 2, false},
"deregister": util.LuaExport{c.cderegister, 1, false},
"registry": util.LuaExport{c.cregistry, 0, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
util.Document(mod, "Commander is Hilbish's custom command library, a way to write commands in Lua.")
return rt.TableValue(mod), nil
}
// register(name, cb)
// Register a command with `name` that runs `cb` when ran
// --- @param name string
// --- @param cb function
// 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.
// #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) {
cmdName, cmd, err := util.HandleStrCallback(t, ct)
if err != nil {
return nil, err
}
c.Events.Emit("commandRegister", cmdName, cmd)
c.Commands[cmdName] = cmd
return ct.Next(), err
}
// deregister(name)
// Deregisters any command registered with `name`
// --- @param name string
// Removes the named command. Note that this will only remove Commander-registered commands.
// #param name string Name of the command to remove.
func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
if err := ct.Check1Arg(); err != nil {
return nil, err
@ -64,7 +111,23 @@ func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
return nil, err
}
c.Events.Emit("commandDeregister", cmdName)
delete(c.Commands, cmdName)
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,3 +1,10 @@
// filesystem interaction and functionality library
/*
The fs module provides filesystem functions to Hilbish. While Lua's standard
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
import (
@ -11,6 +18,7 @@ import (
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
"github.com/arnodel/golua/lib/iolib"
)
var Loader = packagelib.Loader{
@ -29,22 +37,56 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
"dir": util.LuaExport{fdir, 1, false},
"glob": util.LuaExport{fglob, 1, false},
"join": util.LuaExport{fjoin, 0, true},
"pipe": util.LuaExport{fpipe, 0, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
mod.Set(rt.StringValue("pathSep"), rt.StringValue(string(os.PathSeparator)))
mod.Set(rt.StringValue("pathListSep"), rt.StringValue(string(os.PathListSeparator)))
util.Document(mod, `The fs module provides easy and simple access to
filesystem functions and other things, and acts an
addition to the Lua standard library's I/O and filesystem functions.`)
return rt.TableValue(mod), nil
}
// 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)
// Changes directory to `dir`
// --- @param dir string
// Changes Hilbish's directory to `dir`.
// #param dir string Path to change directory to.
func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -63,10 +105,103 @@ func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
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)
// Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
// --- @param name string
// --- @param recursive boolean
// Creates a new directory with the provided `name`.
// With `recursive`, mkdir will create parent directories.
// #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) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
@ -93,9 +228,74 @@ func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), err
}
// stat(path)
// Returns info about `path`
// --- @param path string
// 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) -> {}
// 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
// #param path string
// #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) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -119,120 +319,3 @@ func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), nil
}
// readdir(dir)
// Returns a table of files in `dir`
// --- @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)
// Gives an absolute version of `path`.
// --- @param path 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)
// Gives the basename of `path`. For the rules,
// see Go's filepath.Base
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)
// Returns the directory part of `path`. For the rules, see Go's
// filepath.Dir
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)
// Glob all files and directories that match the pattern.
// For the rules, see Go's filepath.Glob
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(paths...)
// Takes paths and joins them together with the OS's
// directory separator (forward or backward slash).
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

@ -1,3 +1,5 @@
// low level terminal library
// The terminal library is a simple and lower level library for certain terminal interactions.
package terminal
import (
@ -26,14 +28,13 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
util.Document(mod, "The terminal library is a simple and lower level library for certain terminal interactions.")
return rt.TableValue(mod), nil
}
// size()
// Gets the dimensions of the terminal. Returns a table with `width` and `height`
// Note: this is not the size in relation to the dimensions of the display
// 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) {
w, h, err := term.GetSize(int(os.Stdin.Fd()))
if err != nil {
@ -48,7 +49,7 @@ func termsize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// 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) {
state, err := term.GetState(int(os.Stdin.Fd()))
if err != nil {
@ -71,7 +72,7 @@ func termrestoreState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// setRaw()
// Puts the terminal in raw mode
// Puts the terminal into raw mode.
func termsetRaw(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
_, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {

59
hilbish-git.spec 100644
View File

@ -0,0 +1,59 @@
%global _missing_build_ids_terminate_build 0
%global debug_package %{nil}
Name: hilbish-git
Version: {{{ git_tag_version }}}.{{{ git_short_hash }}}
Release: 1%{?dist}
Summary: The flower shell. A comfy and nice little shell for Lua fans!
License: MIT
Source: {{{ git_dir_pack }}}
BuildRequires: git golang go-task
Requires: inspect succulent lunacolors
Url: https://github.com/Rosettea/Hilbish
VCS: {{{ git_dir_vcs }}}
%description
Hilbish is a extensible shell (framework). It was made to be very customizable
via the Lua programming language. It aims to be easy to use for the casual
people but powerful for those who want to tinker more with their shell,
the thing used to interface with most of the system.
The motivation for choosing Lua was that its simpler and better to use
than old shell script. It's fine for basic interactive shell uses,
but that's the only place Hilbish has shell script; everything else is Lua
and aims to be infinitely configurable. If something isn't, open an issue!
%prep
{{{ git_dir_setup_macro }}}
sed -i '\|/etc/shells|d' Taskfile.yaml
%build
go-task
%install
go-task install PREFIX=%{buildroot}/usr BINDIR=%{buildroot}/%{_bindir}
%post
if [ "$1" = 1 ]; then
if [ ! -f %{_sysconfdir}/shells ] ; then
echo "%{_bindir}/hilbish" > %{_sysconfdir}/shells
echo "/bin/hilbish" >> %{_sysconfdir}/shells
else
grep -q "^%{_bindir}/hilbish$" %{_sysconfdir}/shells || echo "%{_bindir}/hilbish" >> %{_sysconfdir}/shells
grep -q "^/bin/hilbish$" %{_sysconfdir}/shells || echo "/bin/hilbish" >> %{_sysconfdir}/shells
fi
fi
%postun
if [ "$1" = 0 ] && [ -f %{_sysconfdir}/shells ] ; then
sed -i '\!^%{_bindir}/hilbish$!d' %{_sysconfdir}/shells
sed -i '\!^/bin/hilbish$!d' %{_sysconfdir}/shells
fi
%files
%doc README.md
%license LICENSE
%{_bindir}/hilbish
%{_datadir}/hilbish

View File

@ -73,13 +73,13 @@ func newFileHistory(path string) *fileHistory {
}
}
itms := []string{""}
lines := strings.Split(string(data), "\n")
itms := make([]string, len(lines) - 1)
for i, l := range lines {
if i == len(lines) - 1 {
continue
}
itms = append(itms, l)
itms[i] = l
}
f, err := os.OpenFile(path, os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0755)
if err != nil {

View File

@ -1,4 +1,4 @@
// +build windows
//go:build windows
package main

63
job.go
View File

@ -18,6 +18,16 @@ import (
var jobs *jobHandler
var jobMetaKey = rt.StringValue("hshjob")
// #type
// #interface jobs
// #property cmd The user entered command string for the job.
// #property running Whether the job is running or not.
// #property id The ID of the job in the job table
// #property pid The Process ID
// #property exitCode The last exit code of the job.
// #property stdout The standard output of the job. This just means the normal logs of the process.
// #property stderr The standard error stream of the process. This (usually) includes error messages of the job.
// The Job type describes a Hilbish job.
type job struct {
cmd string
running bool
@ -110,6 +120,10 @@ func (j *job) getProc() *os.Process {
return nil
}
// #interface jobs
// #member
// start()
// Starts running the job.
func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -130,6 +144,10 @@ func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// #interface jobs
// #member
// stop()
// Stops the job from running.
func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -148,6 +166,11 @@ func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// #interface jobs
// #member
// foreground()
// Puts a job in the foreground. This will cause it to run like it was
// executed normally and wait for it to complete.
func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -180,6 +203,10 @@ func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// #interface jobs
// #member
// background()
// Puts a job in the background. This acts the same as initially running a job.
func luaBackgroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -276,6 +303,13 @@ func (j *jobHandler) stopAll() {
}
}
// #interface jobs
// background job management
/*
Manage interactive jobs in Hilbish via Lua.
Jobs are the name of background tasks/commands. A job can be started via
interactive usage or with the functions defined below for use in external runners. */
func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table {
jobMethods := rt.NewTable()
jFuncs := map[string]util.LuaExport{
@ -353,6 +387,11 @@ func jobUserData(j *job) *rt.UserData {
return rt.NewUserData(j, jobMeta.AsTable())
}
// #interface jobs
// get(id) -> @Job
// Get a job object via its ID.
// --- @param id number
// --- @returns Job
func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j.mu.RLock()
defer j.mu.RUnlock()
@ -373,6 +412,18 @@ func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext(t.Runtime, rt.UserDataValue(job.ud)), nil
}
// #interface jobs
// add(cmdstr, args, execPath)
// Creates a new job. This function does not run the job. This function is intended to be
// used by runners, but can also be used to create jobs via Lua. Commanders cannot be ran as jobs.
// #param cmdstr string String that a user would write for the job
// #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) {
if err := c.CheckNArgs(3); err != nil {
return nil, err
@ -402,6 +453,10 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.UserDataValue(jb.ud)), nil
}
// #interface jobs
// all() -> table[@Job]
// Returns a table of all job objects.
// #returns table[Job]
func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j.mu.RLock()
defer j.mu.RUnlock()
@ -414,6 +469,10 @@ func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil
}
// #interface jobs
// disown(id)
// Disowns a job. This simply deletes it from the list of jobs without stopping it.
// #param id number
func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -431,6 +490,10 @@ func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// #interface jobs
// last() -> @Job
// Returns the last added job to the table.
// #returns Job
func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j.mu.RLock()
defer j.mu.RUnlock()

View File

@ -1,4 +1,4 @@
// +build darwin linux
//go:build unix
package main

View File

@ -1,4 +1,4 @@
// +build windows
//go:build windows
package main

@ -1 +1 @@
Subproject commit 8467b87dd8d49c68b4100b2d129d5f071544b8cf
Subproject commit 34a57c964590f89aa065188a588c7b38aff99c28

19
lua.go
View File

@ -23,6 +23,7 @@ func luaInit() {
MessageHandler: debuglib.Traceback,
})
lib.LoadAll(l)
setupSinkType(l)
lib.LoadLibs(l, hilbishLoader)
// yes this is stupid, i know
@ -32,24 +33,12 @@ func luaInit() {
lib.LoadLibs(l, fs.Loader)
lib.LoadLibs(l, terminal.Loader)
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)
})
cmds = commander.New(l)
lib.LoadLibs(l, cmds.Loader)
hooks = bait.New(l)
hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) {
fmt.Println("Error in", event, "event:", err)
fmt.Println("Error in `error` hook handler:", err)
hooks.Off(event, handler)
})
@ -67,7 +56,7 @@ func luaInit() {
}
// 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 {
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
}

39
main.go
View File

@ -2,16 +2,20 @@ package main
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strings"
"syscall"
"hilbish/util"
"hilbish/golibs/bait"
"hilbish/golibs/commander"
rt "github.com/arnodel/golua/runtime"
"github.com/pborman/getopt"
@ -23,7 +27,6 @@ var (
l *rt.Runtime
lr *lineReader
commands = map[string]*rt.Closure{}
luaCompletions = map[string]*rt.Closure{}
confDir string
@ -31,6 +34,7 @@ var (
curuser *user.User
hooks *bait.Bait
cmds *commander.Commander
defaultConfPath string
defaultHistPath string
)
@ -88,7 +92,7 @@ func main() {
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
}
@ -106,18 +110,26 @@ func main() {
}
if *verflag {
fmt.Printf("Hilbish %s\n", getVersion())
fmt.Printf("Hilbish %s\nCompiled with %s\n", getVersion(), runtime.Version())
os.Exit(0)
}
// Set $SHELL if the user wants to
if *setshflag {
os.Setenv("SHELL", os.Args[0])
os.Setenv("SHELL", "hilbish")
path, err := exec.LookPath("hilbish")
if err == nil {
os.Setenv("SHELL", path)
}
}
go handleSignals()
lr = newLineReader("", false)
luaInit()
go handleSignals()
// If user's config doesn't exixt,
if _, err := os.Stat(defaultConfPath); os.IsNotExist(err) && *configflag == defaultConfPath {
// Read default from current directory
@ -181,11 +193,18 @@ input:
break
}
if err != nil {
if err != readline.CtrlC {
if err == readline.CtrlC {
fmt.Println("^C")
hooks.Emit("hilbish.cancel")
} else {
// If we get a completely random error, print
fmt.Fprintln(os.Stderr, err)
if errors.Is(err, syscall.ENOTTY) {
// what are we even doing here?
panic("not a tty")
}
<-make(chan struct{})
}
fmt.Println("^C")
continue
}
var priv bool
@ -284,7 +303,7 @@ func removeDupes(slice []string) []string {
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
if strings.ToLower(a) == strings.ToLower(e) {
return true
}
}
@ -319,3 +338,7 @@ func getVersion() 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

@ -1,15 +1,15 @@
local commander = require 'commander'
commander.register('bg', function()
commander.register('bg', function(_, sinks)
local job = hilbish.jobs.last()
if not job then
print 'bg: no last job'
sinks.out:writeln 'bg: no last job'
return 1
end
local err = job.background()
local err = job:background()
if err then
print('bg: ' .. err)
sinks.out:writeln('bg: ' .. err)
return 2
end
end)

View File

@ -0,0 +1,31 @@
local commander = require 'commander'
local fs = require 'fs'
commander.register('cat', function(args, sinks)
local exit = 0
if #args == 0 then
sinks.out:writeln [[
usage: cat [file]...]]
end
local chunkSize = 2^13 -- 8K buffer size
for _, fName in ipairs(args) do
local f = io.open(fName)
if f == nil then
exit = 1
sinks.out:writeln(string.format('cat: %s: no such file or directory', fName))
goto continue
end
while true do
local block = f:read(chunkSize)
if not block then break end
sinks.out:write(block)
end
::continue::
end
io.flush()
return exit
end)

View File

@ -4,32 +4,25 @@ local fs = require 'fs'
local dirs = require 'nature.dirs'
dirs.old = hilbish.cwd()
commander.register('cd', function (args)
commander.register('cd', function (args, sinks)
if #args > 1 then
print("cd: too many arguments")
sinks.out:writeln("cd: too many arguments")
return 1
elseif #args > 0 then
local path = args[1]:gsub('$%$','\0'):gsub('${([%w_]+)}', os.getenv)
:gsub('$([%w_]+)', os.getenv):gsub('%z','$'):gsub('^%s*(.-)%s*$', '%1')
if path == '-' then
path = dirs.old
print(path)
end
dirs.setOld(hilbish.cwd())
dirs.push(path)
local ok, err = pcall(function() fs.cd(path) end)
if not ok then
print(err)
return 1
end
bait.throw('cd', path)
return
end
fs.cd(hilbish.home)
bait.throw('cd', hilbish.home)
dirs.push(hilbish.home)
local path = args[1] and args[1] or hilbish.home
if path == '-' then
path = dirs.old
sinks.out:writeln(path)
end
dirs.setOld(hilbish.cwd())
dirs.push(path)
local ok, err = pcall(function() fs.cd(path) end)
if not ok then
sinks.out:writeln(err)
return 1
end
bait.throw('cd', path)
end)

View File

@ -3,35 +3,38 @@ local fs = require 'fs'
local lunacolors = require 'lunacolors'
local dirs = require 'nature.dirs'
commander.register('cdr', function(args)
commander.register('cdr', function(args, sinks)
if not args[1] then
print(lunacolors.format [[
sinks.out:writeln(lunacolors.format [[
cdr: change directory to one which has been recently visied
usage: cdr <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
end
if args[1] == 'list' then
local recentDirs = dirs.recentDirs
if #recentDirs == 0 then
print 'No directories have been visited.'
sinks.out:writeln 'No directories have been visited.'
return 1
end
print(table.concat(recentDirs, '\n'))
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
end
local index = tonumber(args[1])
if not index then
print(string.format('Received %s as index, which isn\'t a number.', index))
sinks.out:writeln(string.format('Received %s as index, which isn\'t a number.', index))
return 1
end
if not dirs.recent(index) then
print(string.format('No recent directory found at index %s.', index))
sinks.out:writeln(string.format('No recent directory found at index %s.', index))
return 1
end

View File

@ -0,0 +1,7 @@
local ansikit = require 'ansikit'
local commander = require 'commander'
commander.register('clear', function()
ansikit.clear(true)
ansikit.cursorTo(0, 0)
end)

View File

@ -1,8 +1,8 @@
local commander = require 'commander'
commander.register('disown', function(args)
commander.register('disown', function(args, sinks)
if #hilbish.jobs.all() == 0 then
print 'disown: no current job'
sinks.out:writeln 'disown: no current job'
return 1
end
@ -10,7 +10,7 @@ commander.register('disown', function(args)
if #args < 0 then
id = tonumber(args[1])
if not id then
print 'disown: invalid id for job'
sinks.out:writeln 'disown: invalid id for job'
return 1
end
else
@ -19,7 +19,7 @@ commander.register('disown', function(args)
local ok = pcall(hilbish.jobs.disown, id)
if not ok then
print 'disown: job does not exist'
sinks.out:writeln 'disown: job does not exist'
return 2
end
end)

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