Compare commits

...

487 Commits

Author SHA1 Message Date
sammyette ac7c97442e
chore: bump for bugfix release 2024-11-04 06:54:09 -04:00
sammyette 7fc3f4a569
chore: revert "chore: add 2.4 motd (work in progress)"
This reverts commit e6b88816fd.
2024-11-04 06:51:31 -04:00
sammyette dbb45a1947
chore: revert "chore: bump to 2.4"
This reverts commit db851cf4f8.
2024-11-04 06:51:20 -04:00
sammyette 3da150bb64
chore: merge 2024-11-03 23:24:32 -04:00
sammyette 46968e632b
fix: bump golua (closes #326) 2024-11-03 23:24:18 -04:00
ShalokShalom 1e01580d8f
docs: add info about runner mode (#325) 2024-08-31 18:05:24 -04:00
sammyette edbc758c67
docs: use only 1 screenshot 2024-08-31 16:50:58 -04:00
TorchedSammy 824f5bd06d docs: [ci] generate new docs 2024-08-16 19:26:30 +00:00
sammyette a7ba2fdf1a
fix: add newline at the end of input if in an unfinished heredoc (#322)
and define (or fix) the behavior of input ending with a slash
it will now add a newline at the end of input always
2024-08-16 15:26:10 -04:00
sammyette e6b88816fd
chore: add 2.4 motd (work in progress) 2024-08-16 15:25:35 -04:00
sammyette db851cf4f8
chore: bump to 2.4 2024-08-16 15:23:55 -04:00
sammyette fc6a9a33e1
chore: update dependencies (fixes #318) 2024-08-15 12:38:35 -04:00
sammyette 0582fbd30c
chore: prepare v2.3.2 2024-07-30 19:26:35 -04:00
sammyette 137efe5c62
fix: motd typo 2024-07-30 19:24:57 -04:00
sammyette 72324c27de
fix: command path searching 2024-07-30 17:57:59 -04:00
sammyette ea233facc8
fix: get absolute path in fs.cd function 2024-07-27 14:28:09 -04:00
sammyette 19feda919e
chore: update changelog 2024-07-27 14:27:54 -04:00
sammyette e4df61f020
fix(nature/tips): cleanup tips display 2024-07-27 14:19:53 -04:00
sammyette 54b85b1c99
fix: add 2.3 motd 2024-07-27 14:10:21 -04:00
sammyette ddf5117fd9
chore: bump version 2024-07-26 18:08:43 -04:00
sammyette 5b46158008
fix(commands/cd): use absolute paths to cd 2024-07-26 18:08:26 -04:00
sammyette a41a5504f4
fix(nature/tips): opt typo 2024-07-26 18:07:55 -04:00
Nathan J Helmig 35a8d0eaa4
feat: adds some friendly tips (#315)
Co-authored-by: Nathan Helmig <nathanhelmig@pop-os.attlocal.net>
2024-07-21 19:02:46 -04:00
sammyette 8a1614ab59
ci: use wildcard for refs on branch name 2024-07-20 10:24:56 -04:00
sammyette cc43cb2d6e
fix: make website build on tag pushes 2024-07-20 10:19:22 -04:00
sammyette 1ba0dd183c
blog: add v2.3 release blog 2024-07-20 10:14:16 -04:00
sammyette 44d63a398a
chore: update changelog 2024-07-20 10:03:36 -04:00
sammyette 92448eec67
ci: add workflow dispatch to website 2024-07-20 09:57:51 -04:00
sammyette 4e882b376b
chore: update version info 2024-07-20 09:36:42 -04:00
sammyette 5f8d942f0a
fix: reuse shell runner instance (#312) 2024-07-20 09:31:57 -04:00
sammyette 41e5e3f789
feat: commanders can exit via ctrl c (#313) 2024-07-19 18:46:46 -04:00
sammyette 826b0789cb
docs: promote midnight edition on main readme 2024-07-19 17:43:49 -04:00
sammyette bfa3b55542
chore: merge 2024-07-19 09:51:12 -04:00
sammyette f7e66bb957
docs: bump version requirement 2024-07-19 09:48:40 -04:00
sammyette d7ab887234
feat: allow builds for unix (#311) 2024-07-19 09:48:01 -04:00
youkwhd b24fc4a422
fix: check if no command passed to exec (#310)
* fix: check if no command passed to exec

* docs: add exec fixes

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

* feat: add fuzzy opt for fuzzy history searching

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

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

* docs: slight touch ups to readme

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

* website: fix docs page layout

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

* docs: update description, logo, dont center badges

* docs: fix badges

* website: fix padding

* website: add borders, add margins for sidenav

* website: update description on homepage

* website: add margins for blog list

* chore: use new logo

* docs: use logo and text combined for readme

* docs: make logo bigger

* website: use combined logo and text asset

* docs: remove hilbish-text asset

* website: fix navbar expand button

* website: add more padding on doc pages

* website(blog): add post about website improvements

* website(blog): add post about website improvements

* website: add more detail on the install page

* website: add screenshots section

* docs: fix up screenshots section

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

* feat: add manual flush method for sinks

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

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

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

* chore: update changelog
2023-03-25 17:42:25 -04:00
sammyette 3d0fffd49a
chore: merge from upstream 2023-02-19 15:12:34 -04:00
sammyette 013fa30658
fix: correct job management commands to call with colon 2023-02-19 15:11:55 -04:00
crumb 4e37e8ca47
fix: replace sed with vgrep (#238) 2023-02-18 20:02:52 -04:00
sammyette 26c8f28034
chore: bump version to 2.2 2023-02-18 12:30:15 -04:00
sammyette 6be65c04a6
website(blog/v2.1): link to release in blog 2023-02-10 17:11:44 -04:00
sammyette 7bfd7b12c4
fix(commands/doc): only highlight real headings 2023-02-10 17:03:03 -04:00
sammyette d48a66e393
docs: update changelog 2023-02-10 16:59:44 -04:00
sammyette d873f04f70
website(blog): add v2.1 release blog 2023-02-09 17:16:12 -04:00
sammyette 7961a441ad
chore: update release name 2023-02-07 18:27:33 -04:00
TorchedSammy c55cd345ba docs: [ci] generate new docs 2023-02-07 22:19:24 +00:00
sammyette 9e1ec1641c
docs: fix return types for functions (closes #229) 2023-02-07 18:18:03 -04:00
sammyette 2911257eb9
docs: cleanup completions doc to better fit 80 col terminals 2023-02-07 17:42:16 -04:00
sammyette f2ee600c28
fix: check for 0 length slice when splitting input for completions 2023-02-07 15:49:11 -04:00
sammyette 61914f8dc7
docs: add docs for sink type (closes #233) 2023-02-07 15:39:22 -04:00
sammyette 6dbb39d404
chore: change motd for 2.1 (closes #237) 2023-02-07 10:46:47 -04:00
sammyette 890f10e176
docs: update runner-mode doc (closes #230) 2023-02-07 10:46:29 -04:00
sammyette 6b27d14f45
fix: check for error on resolving symlink 2023-02-07 10:42:36 -04:00
sammyette efc69ab769
fix: resolve symlinks for completions
1k commits milestone!! 🎉
2023-02-06 17:54:27 -04:00
sammyette cdd07a4981
fix(commands/doc): run stat in pcall 2023-02-05 13:02:50 -04:00
sammyette e2cf417144
fix: initialize lua api before handling signals (fixes #236) 2023-02-01 19:56:27 -04:00
sammyette f97a04179d
chore: update license 2023-01-20 19:35:33 -04:00
sammyette 2f6ab5fd92
feat: add sink for commanders to write output/read input (#232)
to write output, you would usually just use the print builtin
since commanders are just lua custom commands but this does not
consider the fact of pipes or other shell operators being used
to redirect or whatever.

this adds readable/writable "sinks" which is a type for input
or output and is currently only used for commanders but can be
used for other hilbish things in the future
2023-01-20 19:07:42 -04:00
sammyette 088e326bd1
fix(commands/doc): properly block out index doc 2023-01-20 18:43:46 -04:00
TorchedSammy c87fbe2b99 docs: [ci] generate new docs 2023-01-18 10:59:10 +00:00
sammyette 5bb6ba4aee
docs: mark job.stop function as member 2023-01-18 06:58:47 -04:00
sammyette 8d20ad9eed
fix: make hilbish.which work properly with aliases 2023-01-18 06:52:22 -04:00
sammyette 71d23a4727
chore: remove hugo lock 2023-01-18 06:40:58 -04:00
sammyette 6530d48b00
docs: document types properly (#227) 2023-01-18 06:39:26 -04:00
sammyette d99e056a76
feat: add function to get current runner name 2023-01-17 22:04:54 -04:00
sammyette 109b5785af
chore: bump version to 2.1 2023-01-07 14:59:55 -04:00
sammyette a6bcfdfca9
feat(commands/doc): return 1 exit code when doc not found 2023-01-07 14:02:21 -04:00
sammyette 4e023703b6
feat(commands/doc): use local docs directory if hilbish branch is found 2023-01-07 14:01:57 -04:00
sammyette 5814462f5d
chore: merge from upstream 2023-01-07 13:56:02 -04:00
sammyette 354f257c03
feat(commands/doc): make it easier to get interface docs 2023-01-07 13:54:08 -04:00
TorchedSammy a2f0ff3e09 docs: [ci] generate new docs 2023-01-07 15:52:49 +00:00
sammyette 07a7a75b46
docs: fix return type for hilbish.aliases.list 2023-01-07 11:52:05 -04:00
sammyette 887260cd8a
fix: print error on event error and dont release hook (closes #226) 2023-01-07 07:39:36 -04:00
sammyette ef5363cb1f
website: dont set install as draft 2022-12-31 20:01:10 -04:00
sammyette 246fc4d7ee
website: add blog 2022-12-28 22:27:05 -04:00
sammyette 91ec0cf4a4
docs: remove api index (there is already _index) 2022-12-28 22:26:20 -04:00
sammyette 2a80795331
chore: update version 2022-12-28 22:23:47 -04:00
sammyette 72d6b0a4a0
chore: add 2.0.1 tag to changelog 2022-12-28 20:53:38 -04:00
sammyette 4eea793e69
chore: upload gallery image 2022-12-28 20:24:59 -04:00
sammyette b3410e85bc
docs: reorder screenshots, add showcase of history search menu 2022-12-28 20:21:11 -04:00
sammyette c12cb82fb5
docs: clean up install section on readme 2022-12-28 20:18:30 -04:00
TorchedSammy 68a37d8c79 docs: [ci] generate new docs 2022-12-28 23:33:44 +00:00
sammyette f107d1df71
docs: put all code in codeblock for timers example 2022-12-28 19:33:20 -04:00
sammyette 61f6337f5f
docs: improve docs for timers interface 2022-12-28 19:32:52 -04:00
sammyette 0f33f72de7
docs: fix docs for outdated hook 2022-12-28 19:28:16 -04:00
sammyette c2db9e65fa
fix(commands/cd): set oldpwd when cd-ing with no args (fixes #225) 2022-12-28 19:18:04 -04:00
sammyette 97188e73a5
ci: revert using task and set ldflags 2022-12-20 22:14:04 -04:00
sammyette 3cedff273a
ci: fix build command for release action 2022-12-20 22:05:24 -04:00
TorchedSammy a1ce4389e8 docs: [ci] generate new docs 2022-12-21 01:03:09 +00:00
sammyette 35e2a54172
docs: add emmy lua annotation for id in jobs.disown 2022-12-20 21:02:36 -04:00
TorchedSammy 7234111d6d docs: [ci] generate new docs 2022-12-21 01:00:31 +00:00
sammyette a105b8e38d
docs: improve emmy lua annotations 2022-12-20 20:59:55 -04:00
sammyette 5dc3b7d337
feat: add constants to hilbish.timers for timeout and interval times types 2022-12-20 20:55:56 -04:00
sammyette d5dffabcb2
website(docs/intoduction): change text of introduction 2022-12-20 19:42:44 -04:00
sammyette 5ea23d58d1
docs: change api index doc 2022-12-20 10:09:08 -04:00
sammyette 1a2d29aee7
fix: list subdocs for hilbish module 2022-12-20 01:42:58 -04:00
sammyette b46bd0059e
fix: fixes for doc command
- get docs properly for hilbish module and its interfaces
- deal with negative numbers causing yaml metadata parsing to fail
- change from index to _index to match hugo
2022-12-20 01:38:19 -04:00
sammyette 78c95de784
refactor: remove doc prop related code
should very very slightly improve startup. it's dead, obsolete,
and unused code now anyways with the docs refactor
2022-12-20 00:54:05 -04:00
sammyette e4d87afab3
website(faq): correct entry about api docs 2022-12-20 00:41:48 -04:00
sammyette 70594f4db2
website: remove auto collapse from doc navigation 2022-12-20 00:39:34 -04:00
sammyette f661db158b
chore: add missing entries to changelog 2022-12-20 00:27:47 -04:00
sammyette 787d6c082f
chore: prepare for v2.0 2022-12-19 23:46:45 -04:00
sammyette ad94be925c
chore: add rpm spec file (package for copr/fedora) (#220) 2022-12-19 23:19:48 -04:00
sammyette fc82d4f5eb
website: add doc navigation to navbar for collapsing 2022-12-19 00:03:13 -04:00
sammyette e5eefb1d2d
refactor!: rework docs and doc command (#218)
changes the actual file format of docs to markup since that's basically what we have been
using in the first place.

the docgen command has been modified to write markdown headings with the function name and
yaml metadata for easy consumption by hugo for the website.

all other docs have been moved to markdown as well this is the main reason this is a "breaking" change
users will have to reinstall hilbish (task uninstall and task install) to remove the old plaintext docs
2022-12-15 00:00:54 -04:00
sammyette 6525fa774e
ci: fix indents 2022-12-14 21:32:21 -04:00
sammyette 514cbe25c7
ci: set fetch-depth to 0 and checkout action to v3 2022-12-14 21:31:11 -04:00
sammyette 84dd939bbf
ci: replace website build branch with docs-refactor for website 2022-12-14 21:30:47 -04:00
sammyette f94b8ae59b
chore: update golua for popen (#172)
* chore: update golua to popen branch

* chore: fix go sum

* chore: update golua with popen branch
2022-12-13 15:37:01 -04:00
sammyette 7b3dc951c9
docs: add website (#176) 2022-12-13 15:14:48 -04:00
sammyette 0a49e1a4ef
feat: print command errors via hook
this allows users to remove the handlers and print
a custom message to their liking
2022-12-13 12:57:27 -04:00
TorchedSammy 6ca36847f1
docs: add bolded notes/changes for building and installing hilbish 2.0 2022-12-12 21:04:04 -04:00
TorchedSammy 5ca728ba06
docs: change task link and notice 2022-12-12 21:03:36 -04:00
sammyette 813354b662
build!: set datadir of hilbish and change default prefix to /usr/local (#221)
BREAKING (ish) CHANGE: Hilbish will by default install to `/usr/local`. To revert this, set `PREFIX="/usr/"`
2022-12-12 21:01:44 -04:00
TorchedSammy 8d40179a73
fix(readline): dont do anything if length of input rune slice is 0
this fixes an issue when readline splits multiline input.
sometimes, the multiple lines just end up being "stray?"
inputs of enter plus a newline. this can happen if a user attempts
to prefire the enter key while a command is running, for example
2022-12-10 17:27:42 -04:00
TorchedSammy f7e725b5b9
docs: make note on task 2022-12-09 21:49:58 -04:00
sammyette 4ee160fb66
fix: provide correct command when navigating history (#214)
fixes an issue of going up and down in history results in the incorrect order of commands being inserted (nothing happens to the order in the history itself, just when navigating via arrow keys)

* fix: provide correct command when navigating history

previously, the order while navigating history
with the arrow keys would be incorrect
meaning the command you expect if you go u
then go back down would not be there

* chore: update changelog
2022-12-09 21:45:52 -04:00
TorchedSammy 1024f93446
chore: update changelog 2022-12-06 12:54:56 -04:00
TorchedSammy 9c8d7692bc
chore: revert "build: apply hilbish datadir to allow changing install path"
This reverts commit 9131c72501.
temporary revert, will have to apply datadir eventually
2022-12-06 10:29:47 -04:00
TorchedSammy 9131c72501
build: apply hilbish datadir to allow changing install path 2022-12-05 23:38:52 -04:00
TorchedSammy 26ff6c9a46
fix(nature/completions): get command name properly for custom completions 2022-12-05 23:15:35 -04:00
TorchedSammy 604dedb36d
chore: update lunacolors 2022-12-05 14:16:43 -04:00
TorchedSammy 395f3c0742
ci: remove extra indent in release action 2022-12-02 09:42:06 -04:00
TorchedSammy 0c44531a7f
chore: merge from upstream 2022-12-02 09:39:48 -04:00
TorchedSammy 4e850bb322
ci: set build version to 1.18, use task for release builds 2022-12-02 09:39:22 -04:00
TorchedSammy 09d04a7850 docs: [ci] generate new docs 2022-12-02 00:05:24 +00:00
TorchedSammy 4df37b4341
chore: merge from upstream 2022-12-01 20:04:55 -04:00
TorchedSammy 5e2b3367de
docs(bait): add annotation strings for release and hooks functions 2022-12-01 20:04:24 -04:00
Daniel de Sá b6aecb12f6
chore: add .editorconfig file (#217) 2022-12-01 09:29:27 -04:00
TorchedSammy 3f9b230381
chore: update changelog 2022-11-30 14:29:46 -04:00
TorchedSammy b395b70ecd
fix: escape completion prefix if completions are escaped
fixes an issue with duplicated characters when
completing escaped paths
2022-11-30 14:26:43 -04:00
TorchedSammy 06102ebdae
perf: preallocate history slice 2022-11-30 13:20:00 -04:00
TorchedSammy bd4e0df7b3
feat: select 1st item on history find menu (closes #148)
cancels on escape unless the user moves to another item
2022-11-25 20:14:29 -04:00
TorchedSammy ebec585690
fix: binary completion with spaces (closes #210) 2022-11-25 19:35:26 -04:00
TorchedSammy ff4609e432
feat: add hilbish.cancel hook (closes #213) 2022-11-25 19:21:36 -04:00
TorchedSammy ef3e7d92bc
chore: update changelog 2022-11-25 19:13:32 -04:00
TorchedSammy d6338fc021
fix(readline): make completion search menu display 2022-11-25 19:08:38 -04:00
TorchedSammy 3eaeb6a5da
fix(readline): grip completion menu fixes
- dont print item left justified if the max number
of cells is 1 (this fixes issues in cjk as an example)
- trim items that are longer than the terminal width
2022-11-25 18:39:18 -04:00
TorchedSammy 8b547f2af0
feat: make tab completion work with spaces and escaped characters 2022-11-25 16:56:35 -04:00
TorchedSammy 1febe66f84
fix(readline): use uniseg to calculate width of virtual tab entry (closes #209) 2022-11-17 19:18:57 -04:00
TorchedSammy 6ffcc498ac
docs: update changelog for rc 2022-10-14 19:25:18 -04:00
TorchedSammy fe47c6c7a1
chore: change version to rc1 2022-10-14 19:16:24 -04:00
sammy 0d32a10ca3
feat: add builtins clear, exec and cat (#208)
* feat: add clear and exec command

* docs: add builtins to changelog

* feat: add cat command
2022-10-14 19:15:40 -04:00
TorchedSammy cc6e5d01dd docs: [ci] generate new docs 2022-10-11 21:43:22 +00:00
TorchedSammy 068a5b5149
feat(bait): add error hook and hooks function (closes #205)
an `error` hook is now thrown when an event in lua
throws an error (errors from go side should not happen)
it includes the event name, handler, and error message

a hooks function has also been added. it returns a table
of handlers for a specific event.
2022-10-11 17:41:13 -04:00
TorchedSammy 117a4580b4
chore: merge from upstream master 2022-10-10 18:55:28 -04:00
TorchedSammy 0db7f96fd7
build: disable cgo in builds 2022-10-10 18:55:03 -04:00
TorchedSammy 300248de54 docs: [ci] generate new docs 2022-10-10 22:41:10 +00:00
TorchedSammy 3ee2b03330
feat: make prompt optional in hilbish.read 2022-10-10 18:40:29 -04:00
TorchedSammy 3bec2c91a8
fix: create an empty line reader instance for hilbish.read (closes #190) 2022-10-10 18:26:54 -04:00
TorchedSammy b4ca5bfda3
fix(readline): put cursor at end of text when exiting editor 2022-10-10 18:19:24 -04:00
TorchedSammy 308e257872
fix(readline): take into account newlines when calculating amount of lines taken up by input
this does not really fix the issue of multiline input
being broken completely, but prevents the prompt
being reprinted on input
2022-10-10 18:17:58 -04:00
TorchedSammy 7db2a2c826
fix: check if there is cmd input before attempting to add to history (closes #206) 2022-10-10 18:11:09 -04:00
TorchedSammy 22f6ea8a3e
docs: remove getting started from readme toc 2022-10-10 17:34:08 -04:00
TorchedSammy 91596fa81c
docs: document drop in windows support 2022-10-10 17:33:36 -04:00
TorchedSammy e5c8e5eaff
fix!: pass non expanded input to builtin runners
fixes an issue with expanded aliases being added
to history with a recent commit (6th time now with
this issue?) and makes behavior with other runners
consistent

this can technically be a breaking change to people
overriding the sh runner function
2022-09-17 21:00:28 -04:00
TorchedSammy 8647dc57a1
fix: set cmdString after prompting for continue input
makes sure that the old + new input is actually used for
builtin runners
2022-09-17 20:31:19 -04:00
TorchedSammy 8f41005da7
chore: update changelog 2022-09-17 20:24:31 -04:00
TorchedSammy 7108523a4c
fix: remove logging of autostart modules 2022-09-17 14:08:15 -04:00
TorchedSammy ee34ccdbc3
fix: check if key in _G is string before trying to getenv 2022-09-17 10:48:34 -04:00
TorchedSammy 959030f70d
refactor: automatically load all nature commands 2022-09-02 23:01:39 -04:00
TorchedSammy 59cec0ffa5
fix: call hinter for hint text handler 2022-09-02 22:19:30 -04:00
TorchedSammy 1eed4cc7ee
fix: add back empty string in command line split
this fixes file completion in normal usage without
using quotes. it basically cut out the space at the
end which prevented normal usage without adding an
additional space or using quotes for file
completion
2022-08-30 23:38:46 -04:00
TorchedSammy c13889592f
fix: pass alias expanded string to sh runner (fixes #201)
i have no idea why it didnt before, it *shouldnt*
introduce any problems and fixes this one.
2022-08-30 23:10:47 -04:00
TorchedSammy 2e192be2e1
refactor: setup autocd opt in a better way
with the previous commit allowing users to override
hilbish.runner.sh and it being ran by hilbish, the
code for the autocd opt can just override that
function and do the autocd functionality instead
of reimplementing a hybrid runner.

this means that if any other custom runner wants
autocd functionality they can have it with the sh runner
2022-08-30 23:08:22 -04:00
TorchedSammy c96605e79c
feat: allow hilbish.runner.sh to be overridden 2022-08-30 23:07:24 -04:00
TorchedSammy a1ce2ecba6
fix(readline): correct function to count len of prompt to accomodate east asian characters 2022-08-30 22:37:21 -04:00
TorchedSammy a1410ae7ad
fix: set prompt to normal after ctrl d exit
this prevents the prompt being stuck at the multiline
prompt instead of normal prompt after exiting
via the ctrl d bind
2022-08-30 21:52:07 -04:00
TorchedSammy 20870b9004
fix: only print motd when interactive 2022-08-17 18:05:20 -04:00
TorchedSammy 3dcd99563a docs: [ci] generate new docs 2022-08-17 22:01:55 +00:00
sammy 2337f9ab60
refactor: use custom event emitter (#193)
* refactor: use custom event emitter

* fix: sigint hook emit on windows

* fix: restore correct hilbish conf file

* fix: call recoverer for go listeners

* refactor(golibs/bait): use 1 map for listeners

* feat: add once listeners, ability to remove listeners and remove listener on error

* perf: reslice listener slice instead of trying to do ordered move with append

* feat(bait): add release function to remove event listener

* perf: remove listener directly from once emit instead of using off function

* refactor: use bait event emitter on commander

* docs(golibs/bait): add doc strings for functions

* docs: set changelog

* docs(golibs/bait): add docs for lua release function
2022-08-17 18:01:32 -04:00
TorchedSammy 6ce4fb3973
fix: add dot to sample config path on windows 2022-08-16 12:30:39 -04:00
TorchedSammy 387d7d2243
fix: percentages in completion entries causing a problem in the completion menus 2022-07-26 19:24:02 -04:00
TorchedSammy 7de835fab4
chore: update lunacolors (adds blackBg format arg) 2022-07-26 10:41:12 -04:00
TorchedSammy 3e0a2d630b
feat(hilbish.editor): add getLine function to get contents of line 2022-07-19 17:55:03 -04:00
TorchedSammy 09a8b41063
chore!: remove guide command (closes #188) 2022-07-13 16:04:57 -04:00
sammy 349380ae6b
feat: lua backed history (#187)
* refactor: put file history handler in line reader instance instead of global

* feat: add lua history handler in go

* feat: use lua to retrieve readline history

* refactor: handle history in lua

this also introduces a new opt: history
if it is false, command history won't get added

* fix: remove nature.history require

* docs: add changes in changelog

* fix: add comma after history opt
2022-07-13 16:02:09 -04:00
TorchedSammy f7806f5479 docs: [ci] generate new docs 2022-07-13 19:46:40 +00:00
TorchedSammy c8c30e9861
docs: update changelog 2022-07-13 15:46:18 -04:00
TorchedSammy 083c266438
feat(golibs/fs): add join function to join path elements 2022-07-13 15:38:07 -04:00
TorchedSammy dd9bdca5e0
fix(readline): only call raw input callback if not nil 2022-07-13 15:35:33 -04:00
TorchedSammy 9902560061
fix: set vt in on stdin 2022-07-13 15:11:06 -04:00
TorchedSammy dd9aa4b6ea
fix: enable vt input for windows 2022-07-13 15:04:18 -04:00
TorchedSammy be8bdef9c8
style: use single line import instead of list syntax 2022-07-13 14:18:23 -04:00
TorchedSammy e185a32685
fix: expand tilde in dataDir on windows 2022-07-13 14:10:29 -04:00
TorchedSammy 2b480e50e6
feat: print tracebacks for errors 2022-07-13 10:08:23 -04:00
TorchedSammy b65acca903
fix: initialize line reader before lua init 2022-07-10 22:07:01 -04:00
TorchedSammy 08e2951513
feat: add raw input hook (closes #180) 2022-07-10 20:34:00 -04:00
TorchedSammy 83a2ce38ea
docs: remove getting started section on readme 2022-07-09 18:39:21 -04:00
TorchedSammy 60dd5f598a
docs: replace make with task on readme 2022-07-09 18:38:57 -04:00
TorchedSammy 6eea5bce47
feat: add motd (closes #185) 2022-07-09 17:54:21 -04:00
TorchedSammy a106f4aea0
refactor!: move hilbish.greeting to an opt (closes #184) 2022-07-09 17:15:13 -04:00
TorchedSammy 90ed12d551
feat: add hilbish.init hook (closes #186) 2022-07-09 17:04:50 -04:00
sammy e0694c8862
build!: replace make with task (#171)
* build!: replace make with task

allows easy windows building (besides literally go build)
down the line because getting make on windows is dumb
and even if you do it probably wouldnt work as intended
on there

(i also find task more intuitive for simple things)

* ci: use task in build workflow instead of make

* style: remove whitespace errors in task file

* docs: add task in changelog

* docs: fix link to task

* docs: change message for task notice
2022-07-09 11:44:53 -04:00
TorchedSammy d27ce26be0
feat: complete files plainly if tab query begins with quotes (workaround #183) 2022-07-09 10:45:42 -04:00
TorchedSammy 587d08773f
chore: merge from remote 2022-06-20 17:16:59 -04:00
TorchedSammy 362bb14d7e
feat: auto start lua modules (closes #167) 2022-06-20 17:07:15 -04:00
TorchedSammy 153a89b188 docs: [ci] generate new docs 2022-06-20 20:47:56 +00:00
TorchedSammy 453ba9f8ad
feat(fs): add some functions and properties (closes #168) 2022-06-20 16:47:16 -04:00
TorchedSammy 3e807f5235
fix: handle lua eval errors and normal runner errors separately 2022-06-20 15:47:19 -04:00
TorchedSammy 7e57b3de7d
fix: check if input is 0 on multiline splitting (closes #170) 2022-06-20 15:35:25 -04:00
sammyette 226605a996
feat: allow runners to specify if they want more input (#162)
* refactor!: make runners require returning a table

allows for more options for runners in the future,
and makes it so that you can avoid passing
certain args more easily.

* feat: allow runners to specify continue in return to prompt for more input

* docs: update changelog

* refactor: reorder returns of handleSh function

* refactor: move out reprompting and runner handling to functions

makes codefactor happy hopefully. this commit includes
a fix to check if after reprompt the user hits ctrl d
and just exits cleanly
2022-06-02 22:33:30 -04:00
TorchedSammy 1b4b41846f
chore: merge from remote branch 2022-05-31 15:17:50 -04:00
TorchedSammy 3196f0dcb9
fix: don't refetch tab completion on refresh if its already open (closes #163) 2022-05-31 15:16:32 -04:00
TorchedSammy 3f745e7e4d
docs: update contributing info to include writing changes to changelog 2022-05-30 18:16:36 -04:00
TorchedSammy f9a2a981b4
fix: revert "fix: multiline cursor redraw"
This reverts commit 1339dc4a2f.
this caused a few issues, including
history search messing up previous lines
what this commit was trying to fix is unimportant anyway
2022-05-29 19:02:20 -04:00
TorchedSammy 1339dc4a2f
fix: multiline cursor redraw 2022-05-28 21:05:03 -04:00
TorchedSammy b4a5746093
ci: use make to build for artifacts 2022-05-28 20:21:57 -04:00
TorchedSammy 2faaf4e045
fix: print err don't panic and in the case of regex compile failing on menu find 2022-05-28 20:19:49 -04:00
sammyette 3990d370fa
feat: extend hilbish.runner interface to allow multiple runners (#159) 2022-05-28 19:06:18 -04:00
sammyette b28a2c24c1
refactor!: use userdata where appropriate (#157)
any interface which has lua side objects that are from go side for its api (namely jobs) they will use userdata instead of always creating a table to represent the object. this might or might not bring lower ram usage since there is now only 1 single reference to an object instead of always creating a table to expose on lua

breaking change since methods need to be called with a colon instead of dot
2022-05-28 19:03:44 -04:00
TorchedSammy e3c25586e4
fix: broken ansi escape codes on windows cmd 2022-05-28 09:07:28 -04:00
TorchedSammy 9d385efbfc
docs: add new methods for job objects 2022-05-24 19:32:03 -04:00
TorchedSammy d99c24842f
docs: update changelog 2022-05-24 19:31:51 -04:00
TorchedSammy 61271dc70c
fix: load bg command on init 2022-05-24 19:26:35 -04:00
sammyette 8697344f98
feat: implement job foregrounding/backgrounding (#155) 2022-05-24 19:15:02 -04:00
TorchedSammy d65b574cb2
docs: fix typo in job hook docs 2022-05-22 07:29:01 -04:00
TorchedSammy d808534da6
perf: use waitgroup instead of loop to wait on timers 2022-05-21 21:58:58 -04:00
TorchedSammy a4b358fd9c
perf: use struct{} for done channel in timer instead of bool 2022-05-21 21:39:14 -04:00
TorchedSammy 8f6f27e647
docs: add docs for lunacolors (closes #144) 2022-05-21 21:28:06 -04:00
TorchedSammy f888aabc80
chore!: update lunacolors 2022-05-21 21:26:34 -04:00
TorchedSammy 2572d0ea9e
docs: update changelog with latest changes 2022-05-21 21:11:59 -04:00
sammyette d2f16dfbbf
feat: job enhancements (#153)
* feat: add hilbish.job.add function

this is mainly to accomodate for the employer
handler (#152)

* feat!: add start function to jobs

the commit itself adds a few things but the
main purpose is to facilitate a lua side start
function that can restart the job

there is a breaking change in the hilbish.job.add
function; it is now required to provide an extra
table for arguments, since the first cmd
table isnt really what's actually ran

* fix: reuse standard files for jobs

* fix: deadlock in lua job add function and not taking proper amount of args

* fix: assign binary path to job

* feat: emit job.add hook when job is added

* chore: update modules

* fix: use setpgid on cmd procattr for background jobs

* fix: use right procattr on correct os

* fix: set bg proc attr in build tagged file

* feat: add disown function

* fix: stop jobs on exit

* feat: add disown command

* feat: add jobs.last function to get last job

* feat: make disown command get last job if id isnt suppied as arg

* chore: remove unused code

* feat: add job output

* chore: fix comments

* fix!: make exec path in job add explicit in lua side

* docs: add docs and changelogs relating to jobs
2022-05-21 20:53:36 -04:00
TorchedSammy 392cb66382
fix: handle sigterm and remove obsolete code 2022-05-21 20:53:02 -04:00
TorchedSammy 0b07b93de2
docs: move nature docs into a folder 2022-05-19 21:56:03 -04:00
TorchedSammy 8b6506b36c
refactor: move out home abbreviating code to a util function 2022-05-17 21:37:42 -04:00
TorchedSammy ab8b9c8376
feat: add hilbish.version interface 2022-05-17 06:39:30 -04:00
TorchedSammy 5dd2af7250
fix: add release name to version 2022-05-17 06:39:05 -04:00
TorchedSammy ac21109307
fix: change how version is retrieved 2022-05-16 19:36:34 -04:00
TorchedSammy cb88db8cfc
feat: add hilbish.vim interface (closes #114) 2022-05-13 20:52:48 -04:00
TorchedSammy 960909b81d
fix: typo in editor interface 2022-05-13 20:52:30 -04:00
TorchedSammy dc933934eb
feat: add hilbish.editor interface
this is going to be a middle level interface
which brings functions for interacting
with the line editor used in hilbish

adds the following functions:
setVimRegister(reg, text) - sets text in a vim register
getVimRegister(reg) - gets text from a vim register
insert(text) - inserts text into the line
accessible via hilbish.editor
2022-05-13 20:43:40 -04:00
TorchedSammy 827c25fb57
feat: add hilbish.job.add function
this is mainly to accomodate for the employer
handler (#152)
2022-05-13 17:08:15 -04:00
TorchedSammy 3374dfd28f
fix: always start job in exec handler
makes sure that if any errors occur the job
will still throw the start hook
2022-05-13 16:59:16 -04:00
TorchedSammy 9a87d76969
fix: finish job on lua job stop function
makesdoc  sure that a job is finalized by throwing
the job.done hook and setting its running status
to false
2022-05-12 22:29:07 -04:00
TorchedSammy 200fe1054f
fix: pass user input to runner function instead of expanded alias 2022-05-11 21:13:29 -04:00
TorchedSammy 52a58b14c3
docs: update nature doc to have new correct info 2022-05-08 16:03:44 -04:00
TorchedSammy 15035c02cb
fix(doc): check if modmt exists before trying to get module prop docs 2022-05-08 16:02:24 -04:00
Renzix dd678a4a75
fix: Not loading nature module on some systems (#150) 2022-05-08 16:01:06 -04:00
TorchedSammy 0a01ed862c
feat: print error if nature is missing 2022-05-06 19:21:32 -04:00
TorchedSammy 200babf831
chore: merge 2022-05-06 19:06:57 -04:00
TorchedSammy d6bc8b51de
ci: fix missing files on either oses 2022-05-06 19:06:06 -04:00
sammyette 86700c0a7b
ci: add codeql code scanning 2022-05-06 12:20:41 -04:00
TorchedSammy 30b07bc98b
fix: check if path has tilde prefix when trying to expand home 2022-05-01 07:20:40 -04:00
TorchedSammy db437905e0
fix: implicitly expand tilde on args to all fs functions 2022-05-01 00:49:59 -04:00
TorchedSammy c890b86e08
feat: add hilbish.opts and autocd opt
this adds `hilbish.opts`, a table to set
simple options akin to shopt or setopt on
other shells. this commit specifically also
includes the autocd opt, which functions the
way you expect it to

to set opts, simply do `hilbish.opts.name = val`,
where `name` is the opt you want to set and `val`
being the opt setting.

ie: `hilbish.opts.autocd = true` to turn on autocd
2022-04-30 21:22:37 -04:00
TorchedSammy a8406657f9
fix: remove nature hook handles
since it was only for command exec errors,
and theyre handled runner side now, we dont
have to handle it via hooks anymore
2022-04-30 21:05:21 -04:00
TorchedSammy 4178b78341
fix: return other errors from sh runner
makes it so that the sh runner function will return
command not found and not executable errors,
which makes them able to be handled via lua properly
2022-04-30 21:02:36 -04:00
TorchedSammy c4eb3ad368
chore: change lua init errors to make more sense 2022-04-30 20:31:37 -04:00
TorchedSammy 0642ddda36
fix: push home dir to recent dirs with correct function 2022-04-30 20:28:47 -04:00
TorchedSammy b210378380
ci: remove .exe from windows artifact names 2022-04-30 12:08:54 -04:00
TorchedSammy 03cb9c06f3
ci: fix release builds; make them include lua libs and nature lib 2022-04-30 12:08:39 -04:00
TorchedSammy c929c7602f
ci: checkout submodules 2022-04-30 12:00:41 -04:00
TorchedSammy e678ef66d8
docS: update changelogs for master
800th commit!
2022-04-30 12:00:01 -04:00
TorchedSammy d053b58204
ci: indent list of files 2022-04-30 10:50:06 -04:00
TorchedSammy eb3123405a
ci: fix list of files for artifact build 2022-04-30 10:48:32 -04:00
TorchedSammy f12f88ed00
ci: include all required files in build artifacts 2022-04-30 10:45:55 -04:00
TorchedSammy 3bad452f95
fix: load completions by default 2022-04-24 09:00:06 -04:00
TorchedSammy 2790982ad1
fix: no command completions if query/line is an alias
basically, i have a `c` alias which is `git commit`,
this would resolve to `git commit` literally to try
and complete `commit`, which wouldnt match.
this fixes that, and instead itll suggest commands
that start with `c`. if there is a space after and
completion is requested, itll use the alias properly
2022-04-24 00:06:19 -04:00
TorchedSammy 4e5f8b5c80
docs: add docs for nature module 2022-04-24 00:00:18 -04:00
TorchedSammy 4a4c4d8c74
refactor: rewrite completion handler in lua 2022-04-23 23:59:03 -04:00
TorchedSammy bcf02a6b0e
fix: make custom hist dir match default by adding hilbish to the path 2022-04-23 23:55:04 -04:00
TorchedSammy 984b8a4308
feat: add hilbish.aliases.resolve to resolve an alias 2022-04-23 12:28:28 -04:00
TorchedSammy 3db6334445
docs: [ci] generate new docs 2022-04-23 00:03:50 -04:00
TorchedSammy 9e596dc271
refactor: (re)organize and change prelude path and structure
prelude is no longer. it is now nature.
organized the single file prelude into multiple
source files and renamed it to nature. this is coming
after thought that it can turn into a general hilbish
lua core, with user facing modules as well.

this introduces the `nature.dirs` module, to interact
and get recently changed to directories and last/old
cwd.
2022-04-23 00:03:50 -04:00
TorchedSammy f003249632
docs: update doc reference for vim mode actions 2022-04-23 00:03:49 -04:00
TorchedSammy 1714aeac36
feat: add fs.abs 2022-04-23 00:03:49 -04:00
TorchedSammy bca89197ad
fix: add io flush after doc help message 2022-04-23 00:03:49 -04:00
TorchedSammy 27ac60b856
fix: remove unused var 2022-04-23 00:03:48 -04:00
TorchedSammy dbb27f7739
docs: [ci] generate new docs 2022-04-23 00:03:48 -04:00
TorchedSammy 03a57fce5b
docs: add more documentation for completions 2022-04-23 00:03:48 -04:00
TorchedSammy abfbeb5f84
feat: allow overwrite of completion handler (closes #122)
this also makes the completion functions `bins`
and `files` also return the prefix to pass
to the completion handler.

this is an overhaul to the completion system,
which gets the completion handler from lua
instead of being made to only have lua provided
*command* completions.

it does not have any performance deficit, even
though it calls in to golua for completions.
2022-04-23 00:03:47 -04:00
TorchedSammy 3194add3dc
fix: restore doc related metafields on hilbish table 2022-04-23 00:03:47 -04:00
TorchedSammy 1ba88fea88
fix: correct custom runner mode handling with recent changes 2022-04-23 00:03:47 -04:00
TorchedSammy 1274811739
docs: [ci] generate new docs 2022-04-23 00:03:46 -04:00
TorchedSammy 0af36db6ff
fix!: change the way highlighter and hinter are set
with the change of blocking changes to the
hilbish table, i took an opportunity
to make the highlighter and hinter callbacks
set in a more natural way. instead of being
a function which takes a callback, you set
the function itself.
2022-04-23 00:03:38 -04:00
TorchedSammy 1a4008fcfb docs: [ci] generate new docs 2022-04-22 02:17:00 +00:00
TorchedSammy 57d7527356
fix: make hilbish.which work with aliases and commanders 2022-04-21 22:16:04 -04:00
TorchedSammy 3d525aa7da
fix: dont allow overrides on hilbish table 2022-04-21 20:39:38 -04:00
TorchedSammy 37e1b12b81
feat: add hilbish.completion.call to call a completer 2022-04-21 20:39:06 -04:00
TorchedSammy abfd4e5196
fix(util): SetField on a table with a metatable causing panic 2022-04-21 20:33:32 -04:00
TorchedSammy bd35e3b871
refactor: use foreach function to loop over lua tables 2022-04-21 14:01:59 -04:00
TorchedSammy 86aa40af64 docs: [ci] generate new docs 2022-04-21 16:19:31 +00:00
TorchedSammy a89d3e59d6
docs: fix typo in hilbish.inputMode doc (closes #147) 2022-04-21 12:19:11 -04:00
TorchedSammy 1458ecdcab
fix: file completions changes
changed the way file completions are handed
completely, which fixes #130 and makes the
full name appear in the completion menu instead
of it being cut off
2022-04-20 13:06:46 -04:00
ym555 6ccb9ebeff
fix(readline): take into account character width (#145)
first step towards unicode support yay
2022-04-20 08:18:34 -04:00
Will Eccles e34ab5314a
fix: make macOS config path match Linux (#143)
Also moves some variables around in vars_*.go to accommodate the fix.
2022-04-19 22:12:05 -04:00
TorchedSammy 0ae31123b9
fix: make expand home actually expand and not do the opposite 2022-04-19 22:05:48 -04:00
TorchedSammy 7a17d7931f
fix: use load from source in DoFile function instead of load chunk 2022-04-19 21:25:52 -04:00
TorchedSammy d4084a82ba
fix: expandHome function using hist dir instead of passed arg 2022-04-19 21:23:48 -04:00
TorchedSammy 9d69b63a0f
chore: update golua (closes #142) 2022-04-19 14:37:57 -04:00
TorchedSammy b83c09a2b3
fix(readline): check stdin error properly 2022-04-19 10:30:04 -04:00
TorchedSammy bee8d6e9e6
fix(readline): input going to next line if its longer than terminal width 2022-04-18 22:42:27 -04:00
TorchedSammy 48cb62282d
fix(readline): input getting cut off on enter 2022-04-18 22:36:54 -04:00
TorchedSammy 1e48a3e03d
fix(readline): home and end buttons not putting the cursor in the right place 2022-04-18 16:04:56 -04:00
TorchedSammy 4e8aa7ed1d
fix(readline): use invert for completion highlight instead of hardcoded colors 2022-04-17 23:39:53 -04:00
TorchedSammy 919a52a630
fix: handle syntax error in aliased command
if an alias is something which isn't valid syntax,
specifically if hilbish cant split up the input
properly to execute, it will report the error to
the user. the previous behaviour was a panic since
on error the args slice will be of length 0

this is basically an edge case and fixes a bug
which shouldnt happen normally
2022-04-17 22:58:29 -04:00
TorchedSammy b0c950a96a
fix: make sure user input is saved to history without alias expansion (4th same regression woo) 2022-04-17 22:57:31 -04:00
TorchedSammy c4438579f6
build: remove all from install target 2022-04-14 10:11:20 -04:00
buffet 59e81d3996
build: improve makefile 2022-04-14 10:05:35 -04:00
Renzix 7f161e6683
feat: added forward/backward word keybinds (#139)
Added emacsForwardWord and emacsBackwardWord which is used by M-f and
M-b directly. Also added M-d to ctrl delete and removed the bad old
funcion in favor of the fancy new one. Lastly I added alt delete which
deletes with emacsBackwardWord. Works identically to gnu readline

Co-authored-by: Renzix <DanielDeBruno@renzix.com>
2022-04-14 07:42:46 -04:00
TorchedSammy ded0be275f
chore: bump version to v2.0.0 2022-04-13 20:50:29 -04:00
TorchedSammy e3fdf84f5c
fix!: make path to script the 0th arg instead of 1st
makes more sense, brings some lua parity
this means that user passed args start from 1
instead of 2
2022-04-13 20:19:28 -04:00
TorchedSammy 194e4e01b7
fix: don't insert any unhandled control keys 2022-04-13 19:36:18 -04:00
TorchedSammy e5c9b85008
feat: add ctrl _ to undo 2022-04-13 16:58:36 -04:00
TorchedSammy e044aeb5ed docs: [ci] generate new docs 2022-04-13 14:14:06 +00:00
sammyette 0a2046e985
feat: add right prompt (#140)
* feat: add right prompt (closes #111)

* chore: add comment for set right prompt function

* fix: add 1 space at the end of right prompt to fix character cut off

* docs: update doc for prompt function
2022-04-13 10:13:46 -04:00
TorchedSammy 626b036b4b
fix!: add complete input to history, including continued input
this introduces a breaking change to runner functions.
they are now required to return 3 values, the first
being the user's input, and the 2 others that it was
before. the `hilbish.runner` functions respectively
have been updated, so if you just return from those
there will be no difference
2022-04-13 10:12:17 -04:00
sammyette ce625aca0c
feat: add ctrl delete to forward delete word (#138)
* feat: add ctrl delete to forward delete word (closes #124)

* fix: make delete word function accurately

* fix: make ctrl delete work on st
2022-04-12 23:08:44 -04:00
TorchedSammy 1715a1f626
feat: make ctrl d delete char below cursor if line isnt empty 2022-04-12 21:02:01 -04:00
TorchedSammy f002eca258
fix: dont prompt for continued input on incomplete input when not interactive (closes #137) 2022-04-12 19:43:12 -04:00
TorchedSammy 2814f44163
fix: typo in timer create function 2022-04-12 19:41:50 -04:00
TorchedSammy ea7517be05 docs: [ci] generate new docs 2022-04-12 23:37:39 +00:00
TorchedSammy 508fd5f8a2
docs: update docs for timer related functions 2022-04-12 19:37:15 -04:00
TorchedSammy c95ff42dee
feat: add timer pool and api (closes #135)
adds a map (but lets call it a pool) of all
running timers. this makes us able to keep
track of all running intervals and timeouts.
it also means hilbish can wait for them to
be done before exiting (it only waits when
non interactive).

this introduces the `hilbish.timers` interface,
documented by `doc timers`. the `hilbish.interval`
and `hilbish.timeout` functions return a timer
object now.
2022-04-12 19:31:48 -04:00
TorchedSammy c342f4f6f5
fix: handle when stdin is in nonblocking mode (closes #136) 2022-04-08 10:46:25 -04:00
TorchedSammy 393fe3962f
chore: update golua 2022-04-05 22:50:14 -04:00
TorchedSammy 8ae22127c0
fix: remove virt g handling at command exit 2022-04-05 07:41:11 -04:00
TorchedSammy 8f942f6f60 docs: [ci] generate new docs 2022-04-05 01:35:10 +00:00
TorchedSammy b712efd278
fix(docgen): make functions that take varargs have the signature 2022-04-04 21:34:46 -04:00
TorchedSammy ee4d97ff9a
fix: put input in history instead of resolved input ran by hilbish
ive fixed this like 3 times and regressed it
2022-04-04 21:21:46 -04:00
TorchedSammy 9ce861b080
refactor: set runner options in a better way and move out exec handler 2022-04-04 21:20:02 -04:00
TorchedSammy 69d38d7048 docs: [ci] generate new docs 2022-04-04 10:40:25 +00:00
sammyette 0fc5f457ad
refactor!: support lua 5.4 (#129)
major rewrite which changes the library hilbish uses for it's lua vm
this one implements lua 5.4, and since that's a major version bump,
it's a breaking change. introduced here also is a fix for `hilbish.login`
not being the right value

* refactor: start work on lua 5.4

lots of commented out code

ive found a go lua library which implements lua 5.4
and found an opportunity to start working on it.
this commit basically removes everything and just leaves
enough for the shell to be "usable" and able to start.
there are no builtins or libraries (besides the `hilbish` global)

* fix: call cont next in prompt function

this continues execution of lua, very obvious
fixes an issue with code stopping at the prompt function

* fix: handle errors in user config

* fix: handle panic in lua input if it is incorrect

* feat: implement bait

* refactor: use util funcs to run lua where possible

* refactor: move arg handle function to util

* feat: implement commander

* feat: implement fs

* feat: add hilbish module functions used by prelude

* chore: use custom fork of golua

* fix: make sure args to setenv are strings in prelude

* feat: implement completions

* chore: remove comment

* feat: implement terminal

* feat: implement hilbish.interval

* chore: update lunacolors

* chore: update golua

* feat: implement aliases

* feat: add input mode

* feat: implement runner mode

* style: use comma separated cases instead of fallthrough

* feat: implement syntax highlight and hints

* chore: add comments to document util functions

* chore: fix dofile comment doc

* refactor: make loader functions for go modules unexported

* feat: implement job management

* feat: add hilbish properties

* feat: implement all hilbish module functions

* feat: implement history interface

* feat: add completion interface

* feat: add module description docs

* feat: implement os interface

* refactor: use hlalias for add function in hilbish.alias interface

* feat: make it so hilbish.run can return command output

* fix: set hilbish.exitCode to last command exit code

* fix(ansikit): flush on io.write

* fix: deregister commander if return isnt number

* feat: run script when provided path

* fix: read file manually in DoFile to avoid shebang

* chore: add comment for reason of unreading byte

* fix: remove prelude error printing

* fix: add names at chunk load for context in errors

* fix: add newline at the beginning of file buffer when there is shebang

this makes the line count in error messages line up properly

* fix: remove extra newline after error
2022-04-04 06:40:02 -04:00
TorchedSammy 64bf7024d2
docs: fix hilbish typo 2022-04-03 21:43:13 -04:00
Renzix 0ebd8d9035
feat: added alt backspace keybinding (#132)
Co-authored-by: Renzix <DanielDeBruno@renzix.com>
2022-03-29 22:15:23 -04:00
Renzix 52caedc1f1
feat: delete key on st and fix: delete key crash on xterm (#131)
* fix: delete key on st

* fix: delete key crash on xterm

Co-authored-by: Renzix <DanielDeBruno@renzix.com>
2022-03-29 20:35:51 -04:00
TorchedSammy 34ae8ade7b
chore: tidy go modules 2022-03-29 15:28:39 -04:00
TorchedSammy 9ff6e5879f
chore: bump go version to 1.17 2022-03-29 13:31:16 -04:00
TorchedSammy 20fae8a870
fix: prompt refresh (closes #116) 2022-03-29 13:07:27 -04:00
TorchedSammy eff942433d
fix!: remove complete global (was supposed to be gone in 1.0) 2022-03-27 21:10:13 -04:00
Renzix 61c9e12a4a
feat: control k to delete the rest of the line (#128)
Co-authored-by: Renzix <DanielDeBruno@renzix.com>
2022-03-26 23:43:30 -04:00
TorchedSammy 0aba60b5de docs: [ci] generate new docs 2022-03-26 22:28:27 +00:00
TorchedSammy 62a6cc56b9
docs: document hilbish.highlighter 2022-03-26 18:28:01 -04:00
TorchedSammy e5d841a0a7 docs: [ci] generate new docs 2022-03-26 22:26:10 +00:00
TorchedSammy 3e50e608c1
chore: merge from remote 2022-03-26 18:25:35 -04:00
TorchedSammy 76f100ca77
feat: expose syntax highlighting (closes #125) 2022-03-26 18:25:19 -04:00
TorchedSammy 0cad0e7e66
fix: only try to run hinter function if it isnt nil 2022-03-26 18:24:49 -04:00
TorchedSammy 2fb481c4cb docs: [ci] generate new docs 2022-03-26 21:34:42 +00:00
TorchedSammy 6ea25a22b3
feat: add inline hint text and change what were hints previously to info (closes #126) 2022-03-26 17:34:09 -04:00
TorchedSammy 577f00dfef
fix(readline): make forward delete work properly 2022-03-23 21:11:24 -04:00
TorchedSammy 722bd1cd80
fix(readline): insert text in replace mode if cursor is at end of text 2022-03-23 21:10:04 -04:00
TorchedSammy 1ba314d961
chore: prepare for v1.2.0 release 2022-03-22 22:11:46 -04:00
sammyette 84dce8c537
docs: change short description back to block quote 2022-03-22 22:04:33 -04:00
TorchedSammy 3345c51064
docs: add newline after short description 2022-03-22 22:01:22 -04:00
TorchedSammy a7e450904c
docs: add more info to readme 2022-03-22 21:59:34 -04:00
TorchedSammy 23efc8e54d
docs: update image for readme gallery 2022-03-22 21:19:36 -04:00
TorchedSammy 1d4c8a7645
feat: make history clear function work again 2022-03-22 19:01:29 -04:00
TorchedSammy 7272e035d9
ci: remove fetch-depth 0 setting for checkout 2022-03-22 18:41:26 -04:00
TorchedSammy 8a215ad742
docs: rename vimMode to vim-mode 2022-03-22 18:39:22 -04:00
TorchedSammy 6e69ee20f6
chore: merge 2022-03-22 18:38:26 -04:00
TorchedSammy bc15da2f1a
docs: add more docs for runner mode interface 2022-03-22 18:38:13 -04:00
TorchedSammy dd9e827735 docs: [ci] generate new docs 2022-03-22 22:33:52 +00:00
TorchedSammy 3636efe7f8
docs: add doc for mode param of runnerMode function 2022-03-22 18:33:11 -04:00
TorchedSammy 053914ec45
docs: fix dates for changelog versions 2022-03-22 17:19:23 -04:00
TorchedSammy 1e884e7c89
fix: handle job being nil (first sh exec case) 2022-03-21 21:25:43 -04:00
TorchedSammy f27d60f827
fix: move cursor to end of line on history search (closes #121) 2022-03-21 06:47:14 -04:00
TorchedSammy 754a63c74b
chore: merge 2022-03-20 19:10:41 -04:00
TorchedSammy 2fe888e186
feat: add hilbish.jobs interface and add stop function to job in hooks (closes #109) 2022-03-20 19:10:12 -04:00
TorchedSammy 0d4143582f docs: [ci] generate new docs 2022-03-20 21:54:55 +00:00
TorchedSammy 654ca4b527
docs: fix hilbish.alias doc 2022-03-20 17:54:02 -04:00
TorchedSammy 802f444ba6 docs: [ci] generate new docs 2022-03-20 19:16:13 +00:00
TorchedSammy 86a15e6363
feat: add configurable runner mode (closes #110) 2022-03-20 15:15:44 -04:00
TorchedSammy 96c1487bfa
fix: make sure complete input is added to history 2022-03-19 18:48:03 -04:00
TorchedSammy 1e899bf18e
chore: set name of history in menu to History instead of file 2022-03-19 13:24:12 -04:00
TorchedSammy f03f8c0da1
docs: add exitCode to job docs 2022-03-19 13:14:12 -04:00
TorchedSammy 1378a74e87
feat: add job hooks (part of #109) 2022-03-19 13:10:50 -04:00
TorchedSammy 63bc398f1c
fix: use unexported alias handler init function 2022-03-19 12:44:26 -04:00
TorchedSammy 579a0cd0ce
refactor: rename hilbishAliases to aliasHandler for clarity 2022-03-19 12:43:48 -04:00
TorchedSammy f433ab8a6f
docs(guide): mention that users can copy the default dir from dataDir 2022-03-19 09:50:51 -04:00
TorchedSammy eb0a81f7a2
chore: prepare for v1.1.0 release 2022-03-17 20:56:19 -04:00
TorchedSammy 24b88a0483
docs: add docs for vim mode 2022-03-17 20:25:38 -04:00
TorchedSammy f73c6d4aa8
fix: completions of executables and running absolute paths on windows 2022-03-17 20:22:30 -04:00
TorchedSammy 925ded6cea
fix(readline): remove duplicate code 2022-03-17 19:57:57 -04:00
TorchedSammy 92d0e195ab
fix: change prompt back to user's prompt on contine prompt exit 2022-03-17 19:49:33 -04:00
TorchedSammy 4da82e872c
fix: completions on files/folders starting with a dot not having it 2022-03-17 19:41:37 -04:00
TorchedSammy b0ece71de3
fix: use not-executable in prelude instead of no-perm 2022-03-17 19:29:27 -04:00
TorchedSammy 8b5dc69950
feat: add command.not-executable hook (closes #119) 2022-03-16 19:45:55 -04:00
sammyette 20a4cdb505
fix: handle path binaries properly on windows (closes #117, #118) (#120)
* fix: handle path binaries properly on windows (closes #117, #118)

* refactor: dont return exec name since it isnt needed

* fix: return correct error in find exec function and stat always

* fix: remove filepath import for exec file check on unix
2022-03-16 19:44:32 -04:00
TorchedSammy 01d937afd8
fix: correct username in greeting on windows 2022-03-16 18:42:38 -04:00
sammyette 32b421d402
docs: remove support notice 2022-03-15 22:20:23 -04:00
TorchedSammy 0ee47cc6f0
fix(readline): clear history filter on Readline, fixes filtering after ctrl-c 2022-03-15 16:27:12 -04:00
TorchedSammy b8e0874ab0
fix: catch and ignore sigquit signal (closes #112) 2022-03-14 20:55:00 -04:00
TorchedSammy ae449b89eb
fix(readline): dont show completions menu if there is only 1 entry (closes #107) 2022-03-14 20:17:45 -04:00
TorchedSammy 91222f9d74
fix(readline): dont reduce pos if its at 0 2022-03-13 20:00:11 -04:00
TorchedSammy feb2ea18fb
docs: document hilbish.vimAction hook 2022-03-13 17:44:54 -04:00
TorchedSammy 726d265cca
feat: add hilbish.vimAction hook (closes #108) 2022-03-13 16:44:11 -04:00
TorchedSammy 7d9c7ddf18
fix: empty multiline prompt if entire prompt is single line 2022-03-13 16:42:14 -04:00
TorchedSammy 5e505d0a8d
fix: increate pos by 1 at paste instead of 2 2022-03-13 16:40:58 -04:00
TorchedSammy e5b6a2586b
chore: update lunacolors 2022-03-13 16:37:33 -04:00
TorchedSammy c5dd05bb1e
refactor: remove mode from prompt func in default conf and dont refresh in vim mode 2022-03-13 16:12:34 -04:00
TorchedSammy 0113a4e0b4
fix(readline): make esc escape any vim mode 2022-03-13 14:26:44 -04:00
TorchedSammy 5a2e3e4700
chore: bring readline in repo for easier maintenance 2022-03-13 13:48:49 -04:00
TorchedSammy b05cb30ed7
fix: check if interactive *and* input starts with space to determine if to add to history 2022-03-13 12:51:27 -04:00
TorchedSammy dc53eef829
ci: add checkout for create release 2022-03-12 22:32:32 -04:00
TorchedSammy 2f728de6a1
ci: fix checkout 2022-03-12 22:30:16 -04:00
TorchedSammy 4dc5ff60fd
ci: fix submodule clone 2022-03-12 22:22:15 -04:00
TorchedSammy a414c87e06
chore: bump version to v1.0.4 2022-03-12 22:15:31 -04:00
TorchedSammy b3db55708a
ci: clone submodules 2022-03-12 22:14:49 -04:00
TorchedSammy 7d7ccb5c9b
chore: prepare changelog for v1.0.4 2022-03-12 21:59:45 -04:00
TorchedSammy ece5f92307
fix: create history dir if it doesnt exist (fixes #113) 2022-03-12 21:50:57 -04:00
TorchedSammy 0e4f552be2
refactor: panic in history.go for more context 2022-03-12 21:43:02 -04:00
TorchedSammy 23902ea25c
chore: prepare for v1.0.3 release 2022-03-12 20:10:39 -04:00
TorchedSammy a7f0457dab
ci: include lua libs in release 2022-03-12 19:42:42 -04:00
TorchedSammy 4a4cb3409f
fix: set running to true before throwing exit hook on no input
this fixes an issue with the prompt being kind of a mess when
enter alone is pressed (and hilbish doesn't try to run anything)
2022-03-07 21:01:21 -04:00
TorchedSammy 853dfa369f
fix: add user input to history instead of hilbish expanded (closes #103) 2022-03-07 19:10:21 -04:00
TorchedSammy 738939e4c9
fix: remove duplicate binary suggestions (fixes #105) 2022-03-07 18:56:22 -04:00
TorchedSammy 3babf36c43
fix: remove newline from prompt 2022-03-06 22:23:38 -04:00
TorchedSammy b7478c3c1a
chore: prepare for v1.0.2 release 2022-03-06 21:48:24 -04:00
TorchedSammy 605574f262
fix: create directory for hist dir if it doesnt exist 2022-03-06 21:44:46 -04:00
TorchedSammy 9165e0bdf9
chore: prepare for 1.0.1 release 2022-03-06 17:23:44 -04:00
TorchedSammy ec886e17fa
fix: dont refresh prompt at startup 2022-03-06 17:20:41 -04:00
TorchedSammy ba67278fad
fix: dont print types of args in appendPath 2022-03-06 17:19:47 -04:00
TorchedSammy 539cf402ca
fix: dont double set prompt 2022-03-06 17:08:53 -04:00
246 changed files with 21728 additions and 1684 deletions

6
.editorconfig 100644
View File

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

View File

@ -1,5 +1,5 @@
---
- [ ] I have reviewed CONTRIBUTING.md.
- [ ] My commits and title use the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) format.
- [ ] I have documented any breaking changes according to [SemVer](https://semver.org/).
- [ ] I have documented changes and additions in the CHANGELOG.md.
---

View File

@ -1,8 +1,12 @@
name: Build
on:
- push
- pull_request
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
@ -12,29 +16,49 @@ jobs:
matrix:
goos: [linux, windows, darwin]
goarch: ["386", amd64, arm64]
exclude:
exclude:
- goarch: "386"
goos: darwin
goos: darwin
- goarch: arm64
goos: windows
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
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 }} go build
- uses: actions/upload-artifact@v2
run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} ./bin/task
- uses: actions/upload-artifact@v4
if: matrix.goos == 'windows'
with:
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}.exe
path: hilbish.exe
- uses: actions/upload-artifact@v2
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
hilbish.exe
LICENSE
README.md
CHANGELOG.md
.hilbishrc.lua
nature
libs
docs
emmyLuaDocs
- uses: actions/upload-artifact@v4
if: matrix.goos != 'windows'
with:
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
path: hilbish
path: |
hilbish
LICENSE
README.md
CHANGELOG.md
.hilbishrc.lua
nature
libs
docs
emmyLuaDocs

View File

@ -0,0 +1,60 @@
name: CodeQL
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '38 2 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

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,11 +30,17 @@ jobs:
- goarch: arm64
goos: windows
steps:
- uses: actions/checkout@v2
- uses: wangyoucao577/go-release-action@v1.25
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- name: Download Task
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
- uses: wangyoucao577/go-release-action@v1
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 prelude docs emmyLuaDocs
extra_files: LICENSE README.md CHANGELOG.md .hilbishrc.lua nature libs docs emmyLuaDocs

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

@ -0,0 +1,54 @@
name: Build website
on:
push:
branches:
- master
tags:
- v[0-9]+.*
pull_request:
branches:
- master
workflow_dispatch:
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/*/}}" >> "$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

2
.gitmodules vendored
View File

@ -1,6 +1,6 @@
[submodule "libs/lunacolors"]
path = libs/lunacolors
url = https://github.com/Hilbis/Lunacolors
url = https://github.com/Rosettea/Lunacolors
[submodule "libs/succulent"]
path = libs/succulent
url = https://github.com/Rosettea/Succulent

View File

@ -1,20 +1,39 @@
-- Default Hilbish config
local hilbish = require 'hilbish'
local lunacolors = require 'lunacolors'
local bait = require 'bait'
local ansikit = require 'ansikit'
local function doPrompt(fail, mode)
local unreadCount = 0
local running = false
local function doPrompt(fail)
hilbish.prompt(lunacolors.format(
'{blue}%u {cyan}%d ' .. (fail and '{red}' or '{green}') .. ''
))
end
print(lunacolors.format(hilbish.greeting))
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)
@ -23,6 +42,8 @@ bait.catch('hilbish.vimMode', function(mode)
else
ansikit.cursorStyle(ansikit.lineCursor)
end
doPrompt(false, mode)
end)
bait.catch('hilbish.notification', function(notif)
doNotifyPrompt()
end)

View File

@ -1,6 +1,413 @@
# 🎀 Changelog
## [1.0.0] - 2021-03-06
## [2.3.3] - 2024-11-04
### Fixed
- Heredocs having issues
### Added
- Adding `\` at the end of input will add a newline and prompt for more input.
## [2.3.2] - 2024-07-30
### Fixed
- Command path searching due to 2.3 changes to the shell interpreter
## [2.3.1] - 2024-07-27
[hehe when you see it release](https://youtu.be/AaAF51Gwbxo?si=rhj2iYuQRkqDa693&t=64)
### Added
- `hilbish.opts.tips` was added to display random tips on start up.
Displayed tips can be modified via the `hilbish.tips` table.
### Fixed
- Fix a minor regression related to the cd command not working with relative paths
- Updated the motd for 2.3
## [2.3.0] - 2024-07-20
### Added
- `commander.registry` function to get all registered commanders.
- `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
})
```
### Changed
- The `-S` flag will be set to Hilbish's absolute path
- Hilbish now builds on any Unix (if any dependencies also work, which should.)
### Fixed
- Fix ansi attributes causing issues with text when cut off in greenhouse
- Fix greenhouse appearing on terminal resize
- Fix crashes when history goes out of bounds when using history navigation
- `exec` command should return if no arg presented
- Commanders can now be cancelled by Ctrl-C and wont hang the shell anymore.
See [issue 198](https://github.com/Rosettea/Hilbish/issues/198).
- Shell interpreter can now preserve its environment and set PWD properly.
## [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/#/
### Added
- Inline hints, akin to fish and the others.
To make a handler for hint text, you can set the `hilbish.hinter` function.
For more info, look at its docs with the `doc hilbish` command.
- Syntax highlighting function. To make a handler for it, set
`hilbish.highlighter`. Same thing as the hinter, check `doc hilbish` for
more info/docs.
- Ctrl+K deletes from the cursor to the end of the line. ([#128](https://github.com/Rosettea/Hilbish/pull/128))
- Alt+Backspace as an alternative of Ctrl+W to delete a word. ([#132](https://github.com/Rosettea/Hilbish/pull/132))
- Enhanced timer API (`doc timers`)
- Don't exit until intervals are stopped/finished when running a non interactive script.
- Ctrl+D deletes character below cursor if line isn't empty instead of exiting.
- Ctrl+Delete to forward delete a word.
- Right prompt ([#140](https://github.com/Rosettea/Hilbish/pull/140))
- Ctrl+_ to undo in Emacs input mode.
- Emacs style forward/backward word keybinds ([#139](https://github.com/Rosettea/Hilbish/pull/139))
- `hilbish.completion.call` to call a completion handler (`doc completions`)
- `hilbish.completion.handler` to set a custom handler for completions. This
is for everything/anything as opposed to just adding a single command completion.
[#122](https://github.com/Rosettea/Hilbish/issues/122)
- `fs.abs(path)` to get absolute path.
- Nature module (`doc nature`)
- `hilbish.jobs.add(cmdstr, args, execPath)` to add a job to the job table.
`cmdstr` would be user input, `args` is the args for the command (includes arg0)
and `execPath` is absolute path to command executable
- `job.add` hook is thrown when a job is added. acts as a unique hook for
jobs
- `hilbish.jobs.disown(id)` and `disown` builtin to disown a job. `disown`
without arguments will disown the last job.
- `hilbish.jobs.last()` returns the last added job.
- Job output (stdout/stderr) can now be obtained via the `stdout` and `stderr`
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.
- `hilbish.editor` interface for interacting with the line editor that
Hilbish uses.
- `hilbish.vim` interface to dynamically get/set vim registers.
Example usage: `hilbish.vim.registers['a'] = 'hello'`. You can also
get the mode with it via `hilbish.vim.mode`
- `hilbish.version` interface for more info about Hilbish's version. This
includes git commit, branch, and (new!!) release name.
- Added `fg` and `bg` builtins
- `job.foreground()` and `job.background()`, when `job` is a job object,
foreground and backgrounds a job respectively.
- Friendlier functions to the `hilbish.runner` interface, which also allow
having and using multiple runners.
- A few new functions to the `fs` module:
- `fs.basename(path)` gets the basename of path
- `fs.dir(path)` gets the directory part of path
- `fs.glob(pattern)` globs files and directories based on patterns
- `fs.join(dirs...)` joins directories by OS dir separator
- .. and 2 properties
- `fs.pathSep` is the separator for filesystem paths and directories
- `fs.pathListSep` is the separator for $PATH env entries
- Lua modules located in `hilbish.userDir.data .. '/hilbish/start'` (like `~/.local/share/hilbish/start/foo/init.lua`)
will be ran on startup
- `hilbish.init` hook, thrown after Hilbish has initialized Lua side
- Message of the day on startup (`hilbish.motd`), mainly intended as quick
small news pieces for releases. It is printed by default. To disable it,
set `hilbish.opts.motd` to false.
- `history` opt has been added and is true by default. Setting it to false
disables commands being added to history.
- `hilbish.rawInput` hook for input from the readline library
- Completion of files in quotes
- 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.
It can (at the moment) have 4 variables:
- `input` (user input)
- `exitCode` (exit code)
- `error` (error message)
- `continue` (whether to prompt for more input)
User input has been added to the return to account for runners wanting to
prompt for continued input, and to add it properly to history. `continue`
got added so that it would be easier for runners to get continued input
without having to actually handle it at all.
- **Breaking Change:** Job objects and timers are now Lua userdata instead
of a table, so their functions require you to call them with a colon instead
of a dot. (ie. `job.stop()` -> `job:stop()`)
- All `fs` module functions which take paths now implicitly expand ~ to home.
- **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/)
for general tips and documentation
### Fixed
- If in Vim replace mode, input at the end of the line inserts instead of
replacing the last character.
- Make forward delete work how its supposed to.
- Prompt refresh not working properly.
- Crashing on input in xterm. ([#131](https://github.com/Rosettea/Hilbish/pull/131))
- Make delete key work on st ([#131](https://github.com/Rosettea/Hilbish/pull/131))
- `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 [#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
- Use invert for completion menu selection highlight instead of specific
colors. Brings an improvement on light themes, or themes that don't follow
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 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))
for explanation.
- Jobs now throw `job.done` and set running to false when stopped via
Lua `job.stop` function.
- Jobs are always started in sh exec handler now instead of only successful start.
- SIGTERM is handled properly now, which means stopping jobs and timers.
- Fix panic on trailing newline on pasted multiline text.
- Completions will no longer be refreshed if the prompt refreshes while the
menu is open.
- Print error on search fail instead of panicking
- Windows related fixes:
- `hilbish.dataDir` now has tilde (`~`) expanded.
- Arrow keys now work on Windows terminals.
- 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
- Job Management additions
- `job.start` and `job.done` hooks (`doc hooks job`)
- `hilbish.jobs` interface (`get(id)` function gets a job object via `id`, `all()` gets all)
- Customizable runner/exec mode
- However Hilbish runs interactive user input can now be changed Lua side (`doc runner-mode`)
### Changed
- `vimMode` doc is now `vim-mode`
### Fixed
- Make sure input which is supposed to go in history goes there
- Cursor is right at the end of input on history search
## [1.1.0] - 2022-03-17
### Added
- `hilbish.vimAction` hook (`doc vimMode actions`)
- `command.not-executable` hook (will replace `command.no-perm` in a future release)
### Fixed
- Check if interactive before adding to history
- Escape in vim mode exits all modes and not only insert
- Make 2nd line in prompt empty if entire prompt is 1 line
- Completion menu doesnt appear if there is only 1 result
- Ignore SIGQUIT, which caused a panic unhandled
- Remove hostname in greeting on Windows
- Handle PATH binaries properly on Windows
- Fix removal of dot in the beginning of folders/files that have them for file complete
- Fix prompt being set to the continue prompt even when exited
## [1.0.4] - 2022-03-12
### Fixed
- Panic when history directory doesn't exist
## [1.0.3] - 2022-03-12
### Fixed
- Removed duplicate executable suggestions
- User input is added to history now instead of what's ran by Hilbish
- Formatting issue with prompt on no input
## [1.0.2] - 2022-03-06
### Fixed
- Cases where Hilbish's history directory doesn't exist will no longer cause a panic
## [1.0.1] - 2022-03-06
### Fixed
- Using `hilbish.appendPath` will no longer result in string spam (debugging thing left being)
- Prompt gets set properly on startup
## [1.0.0] - 2022-03-06
### Added
- MacOS is now officialy supported, default compile time vars have been added
for it
@ -373,6 +780,26 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish.
[2.3.3]: https://github.com/Rosettea/Hilbish/compare/v2.3.2...v2.3.3
[2.3.2]: https://github.com/Rosettea/Hilbish/compare/v2.3.1...v2.3.2
[2.3.1]: https://github.com/Rosettea/Hilbish/compare/v2.3.0...v2.3.1
[2.3.0]: https://github.com/Rosettea/Hilbish/compare/v2.2.3...v2.3.0
[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
[1.0.2]: https://github.com/Rosettea/Hilbish/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/Rosettea/Hilbish/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/Rosettea/Hilbish/compare/v0.7.1...v1.0.0
[0.7.1]: https://github.com/Rosettea/Hilbish/compare/v0.7.0...v0.7.1
[0.7.0]: https://github.com/Rosettea/Hilbish/compare/v0.6.1...v0.7.0

View File

@ -29,8 +29,9 @@ For any code contributions (Lua and/or Go), you should follow these rules:
1. Ensure that any new install or build dependencies are documented in
the README.md and pull request.
2. Mention any and all changes, this includes useful file locations and
breaking changes.
2. Mention any and all changes, feature additons, removals, etc. This includes
useful file locations and breaking changes. Document them in the [changelog](CHANGELOG.md)
in the pull request.
3. We use [Semver](http://semver.org/) for versioning and
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)

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,31 +0,0 @@
PREFIX ?= /usr
DESTDIR ?=
BINDIR ?= $(PREFIX)/bin
LIBDIR ?= $(PREFIX)/share/hilbish
build:
@go build -ldflags "-s -w"
dev:
@go build -ldflags "-s -w -X main.version=$(shell git describe --tags)"
install:
@install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
@mkdir -p "$(DESTDIR)$(LIBDIR)"
@cp libs docs emmyLuaDocs prelude .hilbishrc.lua "$(DESTDIR)$(LIBDIR)" -r
@grep "$(DESTDIR)$(BINDIR)/hilbish" -qxF /etc/shells || echo "$(DESTDIR)$(BINDIR)/hilbish" >> /etc/shells
@echo "Hilbish Installed"
uninstall:
@rm -vrf \
"$(DESTDIR)$(BINDIR)/hilbish" \
"$(DESTDIR)$(LIBDIR)"
@sed -i '/hilbish/d' /etc/shells
@echo "Hilbish Uninstalled"
clean:
@go clean
all: build install
.PHONY: install uninstall build dev clean

102
README.md
View File

@ -1,62 +1,52 @@
<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 users and 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>
> [!TIP]
> Check out [Hilbish: Midnight Edition](https://github.com/Rosettea/Hilbish/tree/midnight-edition) if you want to use C Lua, LuaJIT or anything related!
Hilbish is a Unix-y shell which uses Lua for scripting. Things like the prompt,
general configuration and such are done with Lua.
<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>
For interactive use, it uses a library to run sh which works on all
platforms Hilbish can be compiled for. It can also act as a Lua REPL if you want
it to be.
<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, and powerful enough for
those who need more.
The motivation for choosing Lua was that its simpler and better to use
than old shell scripts. It's fine for basic interactive shell uses,
and supports [both Lua and Sh interactively](https://rosettea.github.io/Hilbish/docs/features/runner-mode/).
That's the only place Hilbish can use traditional shell syntax though;
everything else is Lua and aims to be infinitely configurable.
If something isn't, open an issue!
# Screenshots
<div align="center">
<img src="gallery/default.png"><br><br>
<img src="gallery/terminal.png"><br><br>
<img src="gallery/pillprompt.png">
<img src="gallery/tab.png">
</div>
# Installation
**NOTE:** Hilbish is currently only officially supported and tested on Linux
# 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.
## Prebuilt binaries
Go [here](https://nightly.link/Rosettea/Hilbish/workflows/build/master) for
builds on the master branch.
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
[![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
```
## 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.**)
[![AUR maintainer](https://img.shields.io/aur/maintainer/hilbish?logo=arch-linux&style=flat-square)](https://aur.archlinux.org/packages/hilbish-git)
Or from the latest `master` commit with:
```sh
yay -S hilbish-git
```
## Nixpkgs
Nix/NixOS users can install Hilbish from the central repository, nixpkgs, through the usual ways.
If you're new to nix you should probably read up on how to do that [here](https://nixos.wiki/wiki/Cheatsheet).
## Manual Build
### Prerequisites
- [Go 1.17+](https://go.dev)
#### Build
## Build
First, clone Hilbish. The recursive is required, as some Lua libraries
are submodules.
```sh
@ -67,24 +57,24 @@ go get -d ./...
To build, run:
```
make dev
task
```
Or, if you want a stable branch, run these commands:
```
git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
make build
task build
```
After you did all that, run `sudo make install` to install Hilbish globally.
After you did all that, run `sudo task install` to install Hilbish globally.
# Contributing
Any kind of contributions to Hilbish are welcome!
Read [CONTRIBUTING.md](CONTRIBUTING.md) before getting started.
Any kind of contributions are welcome! Hilbish is very easy to contribute to.
Read [CONTRIBUTING.md](CONTRIBUTING.md) as a guideline to doing so.
**Thanks to everyone below who's contributed!**
<a href="https://github.com/Hilbis/Hilbish/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Hilbis/Hilbish" />
<a href="https://github.com/Rosettea/Hilbish/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Rosettea/Hilbish" />
</a>
*Made with [contributors-img](https://contrib.rocks).*

47
Taskfile.yaml 100644
View File

@ -0,0 +1,47 @@
# https://taskfile.dev
version: '3'
vars:
PREFIX: '{{default "/usr/local" .PREFIX}}'
bindir__: '{{.PREFIX}}/bin'
BINDIR: '{{default .bindir__ .BINDIR}}'
libdir__: '{{.PREFIX}}/share/hilbish'
LIBDIR: '{{default .libdir__ .LIBDIR}}'
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.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"
- mkdir -p "{{.DESTDIR}}{{.LIBDIR}}"
- cp -r libs docs emmyLuaDocs nature .hilbishrc.lua {{.DESTDIR}}{{.LIBDIR}}
- grep -qxF "{{.DESTDIR}}{{.BINDIR}}/hilbish" /etc/shells || echo "{{.DESTDIR}}{{.BINDIR}}/hilbish" >> /etc/shells
uninstall:
cmds:
- rm -vrf
"{{.DESTDIR}}{{.BINDIR}}/hilbish"
"{{.DESTDIR}}{{.LIBDIR}}"
- grep -v 'hilbish' /etc/shells > /tmp/shells.hilbish_uninstall && mv /tmp/shells.hilbish_uninstall /etc/shells

View File

@ -1,60 +1,87 @@
package main
import (
"regexp"
"strconv"
"strings"
"sync"
"github.com/yuin/gopher-lua"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
)
var aliases *hilbishAliases
var aliases *aliasModule
type hilbishAliases struct {
type aliasModule struct {
aliases map[string]string
mu *sync.RWMutex
}
// initialize aliases map
func NewAliases() *hilbishAliases {
return &hilbishAliases{
func newAliases() *aliasModule {
return &aliasModule{
aliases: make(map[string]string),
mu: &sync.RWMutex{},
}
}
func (h *hilbishAliases) Add(alias, cmd string) {
h.mu.Lock()
defer h.mu.Unlock()
func (a *aliasModule) Add(alias, cmd string) {
a.mu.Lock()
defer a.mu.Unlock()
h.aliases[alias] = cmd
a.aliases[alias] = cmd
}
func (h *hilbishAliases) All() map[string]string {
return h.aliases
func (a *aliasModule) All() map[string]string {
return a.aliases
}
func (h *hilbishAliases) Delete(alias string) {
h.mu.Lock()
defer h.mu.Unlock()
func (a *aliasModule) Delete(alias string) {
a.mu.Lock()
defer a.mu.Unlock()
delete(h.aliases, alias)
delete(a.aliases, alias)
}
func (h *hilbishAliases) Resolve(cmdstr string) string {
h.mu.RLock()
defer h.mu.RUnlock()
func (a *aliasModule) Resolve(cmdstr string) string {
a.mu.RLock()
defer a.mu.RUnlock()
args := strings.Split(cmdstr, " ")
for h.aliases[args[0]] != "" {
alias := h.aliases[args[0]]
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
if h.aliases[args[0]] == alias {
if a.aliases[args[0]] == alias {
break
}
if h.aliases[args[0]] != "" {
if a.aliases[args[0]] != "" {
continue
}
}
@ -64,41 +91,83 @@ func (h *hilbishAliases) Resolve(cmdstr string) string {
// lua section
func (h *hilbishAliases) Loader(L *lua.LState) *lua.LTable {
// #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]lua.LGFunction{
"add": h.luaAdd,
"list": h.luaList,
"del": h.luaDelete,
hshaliasesLua := map[string]util.LuaExport{
"add": util.LuaExport{hlalias, 2, false},
"list": util.LuaExport{a.luaList, 0, false},
"del": util.LuaExport{a.luaDelete, 1, false},
"resolve": util.LuaExport{a.luaResolve, 1, false},
}
mod := L.SetFuncs(L.NewTable(), hshaliasesLua)
mod := rt.NewTable()
util.SetExports(rtm, mod, hshaliasesLua)
return mod
}
func (h *hilbishAliases) luaAdd(L *lua.LState) int {
alias := L.CheckString(1)
cmd := L.CheckString(2)
h.Add(alias, cmd)
// #interface aliases
// add(alias, cmd)
// This is an alias (ha) for the [hilbish.alias](../#alias) function.
// --- @param alias string
// --- @param cmd string
func _hlalias() {}
return 0
}
// #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')
func (h *hilbishAliases) luaList(L *lua.LState) int {
aliasesList := L.NewTable()
for k, v := range h.All() {
aliasesList.RawSetString(k, lua.LString(v))
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))
}
L.Push(aliasesList)
return 1
return c.PushingNext1(t.Runtime, rt.TableValue(aliasesList)), nil
}
func (h *hilbishAliases) luaDelete(L *lua.LState) int {
alias := L.CheckString(1)
h.Delete(alias)
// #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
}
alias, err := c.StringArg(0)
if err != nil {
return nil, err
}
a.Delete(alias)
return 0
return c.Next(), nil
}
// #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
}
alias, err := c.StringArg(0)
if err != nil {
return nil, err
}
resolved := a.Resolve(alias)
return c.PushingNext1(t.Runtime, rt.StringValue(resolved)), nil
}

932
api.go

File diff suppressed because it is too large Load Diff

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,114 +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])
}
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])
}
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

@ -1,57 +1,122 @@
package main
import (
"errors"
"path/filepath"
"strings"
"os"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
)
func fileComplete(query, ctx string, fields []string) []string {
var completions []string
var charEscapeMap = []string{
"\"", "\\\"",
"'", "\\'",
"`", "\\`",
" ", "\\ ",
"(", "\\(",
")", "\\)",
"[", "\\[",
"]", "\\]",
"$", "\\$",
"&", "\\&",
"*", "\\*",
">", "\\>",
"<", "\\<",
"|", "\\|",
}
var charEscapeMapInvert = invert(charEscapeMap)
var escapeReplaer = strings.NewReplacer(charEscapeMap...)
var escapeInvertReplaer = strings.NewReplacer(charEscapeMapInvert...)
prefixes := []string{"./", "../", "/", "~/"}
for _, prefix := range prefixes {
if strings.HasPrefix(query, prefix) {
completions, _ = matchPath(strings.Replace(query, "~", curuser.HomeDir, 1), query)
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]
}
}
if len(completions) == 0 && len(fields) > 1 {
completions, _ = matchPath("./" + query, query)
return newM
}
func splitForFile(str string) []string {
split := []string{}
sb := &strings.Builder{}
quoted := false
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()
} else {
sb.WriteRune(r)
}
}
if strings.HasSuffix(str, " ") {
split = append(split, "")
}
return completions
if sb.Len() > 0 {
split = append(split, sb.String())
}
return split
}
func fileComplete(query, ctx string, fields []string) ([]string, string) {
q := splitForFile(ctx)
path := ""
if len(q) != 0 {
path = 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{"./", "../", "/", "~/"}
for _, prefix := range prefixes {
if strings.HasPrefix(query, prefix) {
fileCompletions := fileComplete(query, ctx, fields)
fileCompletions, filePref := matchPath(query)
if len(fileCompletions) != 0 {
for _, f := range fileCompletions {
name := strings.Replace(query + f, "~", curuser.HomeDir, 1)
if info, err := os.Stat(name); err == nil && info.Mode().Perm() & 0100 == 0 {
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
if err := findExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
continue
}
completions = append(completions, f)
}
}
return completions, ""
return completions, filePref
}
}
// 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
for _, match := range matches {
// check if we have execute permissions for our match
if info, err := os.Stat(match); err == nil && info.Mode().Perm() & 0100 == 0 {
err := findExecutable(match, true, false)
if err != nil {
continue
}
// get basename from match
@ -63,46 +128,246 @@ 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)
}
}
completions = removeDupes(completions)
return completions, query
}
func matchPath(path, pref string) ([]string, error) {
func matchPath(query string) ([]string, string) {
oldQuery := query
query = strings.TrimPrefix(query, "\"")
var entries []string
matches, err := filepath.Glob(path + "*")
if err == nil {
args := []string{
"\"", "\\\"",
"'", "\\'",
"`", "\\`",
" ", "\\ ",
"(", "\\(",
")", "\\)",
"[", "\\[",
"]", "\\]",
}
var baseName string
r := strings.NewReplacer(args...)
for _, match := range matches {
name := filepath.Base(match)
p := filepath.Base(pref)
if pref == "" {
p = ""
}
name = strings.TrimPrefix(name, p)
matchFull, _ := filepath.Abs(match)
if info, err := os.Stat(matchFull); err == nil && info.IsDir() {
name = name + string(os.PathSeparator)
}
name = r.Replace(name)
entries = append(entries, name)
}
query = escapeInvertReplaer.Replace(query)
path, _ := filepath.Abs(util.ExpandHome(filepath.Dir(query)))
if string(query) == "" {
// filepath base below would give us "."
// which would cause a match of only dotfiles
path, _ = filepath.Abs(".")
} else if !strings.HasSuffix(query, string(os.PathSeparator)) {
baseName = filepath.Base(query)
}
return entries, err
files, _ := os.ReadDir(path)
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() {
entry = entry + string(os.PathSeparator)
}
if !strings.HasPrefix(oldQuery, "\"") {
entry = escapeFilename(entry)
}
entries = append(entries, entry)
}
}
if !strings.HasPrefix(oldQuery, "\"") {
baseName = escapeFilename(baseName)
}
return entries, baseName
}
func escapeFilename(fname string) string {
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{
"bins": {hcmpBins, 3, false},
"call": {hcmpCall, 4, false},
"files": {hcmpFiles, 3, false},
"handler": {hcmpHandler, 2, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
return mod
}
// #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
}
// #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
}
completer, err := c.StringArg(0)
if err != nil {
return nil, err
}
query, err := c.StringArg(1)
if err != nil {
return nil, err
}
ctx, err := c.StringArg(2)
if err != nil {
return nil, err
}
fields, err := c.TableArg(3)
if err != nil {
return nil, err
}
var completecb *rt.Closure
var ok bool
if completecb, ok = luaCompletions[completer]; !ok {
return nil, errors.New("completer " + completer + " does not exist")
}
// we must keep the holy 80 cols
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 cont, nil
}
// #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
}
completions, pfx := fileComplete(query, ctx, fds)
luaComps := rt.NewTable()
for i, comp := range completions {
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
}
return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil
}
// #interface 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]
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
}
query, err := c.StringArg(0)
if err != nil {
return "", "", []string{}, err
}
ctx, err := c.StringArg(1)
if err != nil {
return "", "", []string{}, err
}
fields, err := c.TableArg(2)
if err != nil {
return "", "", []string{}, err
}
var fds []string
util.ForEach(fields, func(k rt.Value, v rt.Value) {
if v.Type() == rt.StringType {
fds = append(fds, v.AsString())
}
})
return query, ctx, fds, 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,116 @@
---
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): 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
- `newline` (boolean): Whether a newline should be added at the end of `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,6 +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
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
```

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,53 @@
---
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.
## 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,8 +0,0 @@
cd(dir) > Changes directory to `dir`
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,43 +0,0 @@
alias(cmd, orig) > Sets an alias of `orig` to `cmd`
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."
A completion group is a table with the keys `items` and `type`.
`items` being a table of items and `type` being the display type of
`grid` (the normal file completion display) or `list` (with a description)
cwd() > Returns the current directory of the shell
exec(cmd) > Replaces running hilbish with `cmd`
goro(fn) > Puts `fn` in a goroutine
inputMode(mode) > Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
interval(cb, time) > Runs the `cb` function every `time` milliseconds
multiprompt(str) > Changes the continued line prompt to `str`
prependPath(dir) > Prepends `dir` to $PATH
prompt(str) > 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) > Runs `cmd` in Hilbish's sh interpreter.
timeout(cb, time) > Runs the `cb` function after `time` in milliseconds
which(binName) > Searches for an executable called `binName` in the directories of $PATH

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,3 +0,0 @@
+ `hilbish.exit` > Sent when Hilbish is about to exit.
+ `hilbish.vimMode` > Sent when Hilbish's Vim mode is changed (example insert to normal mode)

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,7 @@
Note: `job` refers to a job object. You can check `doc jobs` for more
detail.
+ `job.start` -> job > Thrown when a new background job starts.
+ `job.done` -> job > Thrown when a background jobs exits.

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`

48
docs/jobs.md 100644
View File

@ -0,0 +1,48 @@
(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.
You can add jobs, stop and delete (disown) them and even get output.
# Job Interface
The job interface refers to `hilbish.jobs`.
## Functions
(Note that in the list here, they're called from `hilbish.jobs`, so
a listing of `foo` would mean `hilbish.jobs.foo`)
- `all()` -> {jobs}: Returns a table of all jobs.
- `last()` -> job: Returns the last added job.
- `get(id)` -> job: Get a job by its ID.
- `add(cmdstr, args, execPath)` -> job: Adds a new job to the job table.
Note that this does not run the command; You have to start it manually.
`cmdstr` is the user's input for the job, `args` is a table of arguments
for the command. It includes arg0 (don't set it as entry 0 in the table)
and `execPath` is an absolute path for the command executable.
- `disown(id)`: Removes a job by ID from the job table.
# Job Object
A job object is a piece of `userdata`. All the functions of a job require
you to call them with a colon, since they are *methods* for the job object.
Example: hilbish.jobs.last():foreground()
Which will foreground the last job.
You can still have a job object for a disowned job,
it just won't be *working* anywhere. :^)
## Properties
- `cmd`: command string
- `running`: boolean whether the job is running
- `id`: unique id for the job
- `pid`: process id for the job
- `exitCode`: exit code of the job
In ordinary cases you'd prefer to use the `id` instead of `pid`.
The `id` is unique to Hilbish and is how you get jobs with the
`hilbish.jobs` interface. It may also not describe the job entirely.
## Functions
- `stop()`: Stops the job.
- `start()`: Starts the job.
- `foreground()`: Set the job as the current running foreground process, or
run it in the foreground after it has been suspended.
- `background()`: Run the job in the background after it has been suspended.

41
docs/lunacolors.md 100644
View File

@ -0,0 +1,41 @@
---
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.
For simple usage, a single color or style is enough. For example,
you can just use `lunacolors.blue 'Hello world'` and that'll return
blue text which you can print. This includes styles like bold,
underline, etc.
In other usage, you may want to use a format string instead of having
multiple nested functions for different styles. This is where the format
function comes in. You can used named keywords to style a section of text.
The list of arguments are:
Colors:
- black
- red
- green
- yellow
- blue
- magenta
- cyan
- white
Styles:
- bold
- dim
- italic
- underline
- invert
For the colors, there are background and bright variants. The background
color variants have a suffix of `Bg` and bright has a prefix of `bright`.
Note that appropriate camel casing has to be applied to them. So bright
blue would be `brightBlue` and background cyan would be `cyanBg`.

View File

@ -0,0 +1,19 @@
---
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 runs after Hilbish's Go core does.
# Nature Modules
Currently, `nature` provides 1 intended public module: `nature.dirs`.
It is a simple API for managing recent directories and old
current working directory.

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,9 +0,0 @@
setRaw() > Puts the terminal in raw mode
restoreState() > Restores the last saved state of the terminal
saveState() > Saves the current state of the terminal
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

@ -0,0 +1,11 @@
---
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`).
This is documentation for everything relating to it.

View File

@ -0,0 +1,25 @@
---
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.
The `hilbish.vimAction` hook is thrown whenever a Vim action occurs.
It passes 2 arguments: the action name, and an array (table) of args
relating to it.
Here is documentation for what the table of args will hold for an
appropriate Vim action.
- `yank`: register, yankedText
The first argument for the yank action is the register yankedText goes to.
- `paste`: register, pastedText
The first argument for the paste action is the register pastedText is taken from.

108
editor.go 100644
View File

@ -0,0 +1,108 @@
package main
import (
"hilbish/util"
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()
util.SetExports(rtm, mod, exports)
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
}
text, err := c.StringArg(0)
if err != nil {
return nil, err
}
lr.rl.Insert(text)
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
}
register, err := c.StringArg(0)
if err != nil {
return nil, err
}
text, err := c.StringArg(1)
if err != nil {
return nil, err
}
lr.rl.SetRegisterBuf(register, []rune(text))
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
}
register, err := c.StringArg(0)
if err != nil {
return nil, err
}
buf := lr.rl.GetFromRegister(register)
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,19 +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
--- Throws a hook with `name` with the provided `args`
--- @param name string
--- @vararg any
function bait.throw(name) end
--- 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(name, catcher) 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,22 +2,53 @@
local fs = {}
--- Changes directory to `dir`
--- @param dir 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
--- Returns the "basename," or the last part of the provided `path`. If path is empty,
--- `.` will be returned.
function fs.basename(path) end
--- Changes Hilbish's directory to `dir`.
function fs.cd(dir) end
--- Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
--- @param name string
--- @param recursive boolean
--- 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
--- 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 any list of paths and joins them based on the operating system's path separator.
---
---
function fs.join(...path) end
--- 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,80 +2,264 @@
local hilbish = {}
--- Sets an alias of `orig` to `cmd`
--- 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."
--- A completion group is a table with the keys `items` and `type`.
--- `items` being a table of items and `type` being the display type of
--- `grid` (the normal file completion display) or `list` (with a description)
--- @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
--- Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
--- @param mode 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
--- 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.
---
---
function hilbish.hinter(line, pos) end
--- 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
--- @param cb function
--- @param time number
--- 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
function hilbish.mlprompt(str) end
--- 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
function hilbish.prompt(str) end
---
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.
--- @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
--- Runs the `cb` function after `time` in milliseconds
--- @param cb function
--- @param time number
--- 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.
function hilbish.runnerMode(mode) end
--- Executed the `cb` function after a period of `time`.
--- This creates a Timer that starts ticking immediately.
function hilbish.timeout(cb, time) end
--- Searches for an executable called `binName` in the directories of $PATH
--- @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

@ -2,17 +2,17 @@
local terminal = {}
--- Puts the terminal in raw mode
function terminal.raw() end
--- 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 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

569
exec.go
View File

@ -1,30 +1,221 @@
package main
import (
"bytes"
"context"
"errors"
"os/exec"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"hilbish/util"
"github.com/yuin/gopher-lua"
rt "github.com/arnodel/golua/runtime"
"mvdan.cc/sh/v3/shell"
//"github.com/yuin/gopher-lua/parse"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"mvdan.cc/sh/v3/expand"
)
func runInput(input, origInput string) {
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
code int
colon bool
err error
}
func (e execError) Error() string {
return fmt.Sprintf("%s: %s", e.cmd, e.typ)
}
func (e execError) sprint() error {
sep := " "
if e.colon {
sep = ": "
}
return fmt.Errorf("hilbish: %s%s%s", e.cmd, sep, e.err.Error())
}
func isExecError(err error) (execError, bool) {
if exErr, ok := err.(execError); ok {
return exErr, true
}
fields := strings.Split(err.Error(), ": ")
knownTypes := []string{
"not-found",
"not-executable",
}
if len(fields) > 1 && contains(knownTypes, fields[1]) {
var colon bool
var e error
switch fields[1] {
case "not-found":
e = errNotFound
case "not-executable":
colon = true
e = errNotExec
}
return execError{
cmd: fields[0],
typ: fields[1],
colon: colon,
err: e,
}, true
}
return execError{}, false
}
func runInput(input string, priv bool) {
running = true
cmdString := aliases.Resolve(input)
hooks.Emit("command.preexec", input, cmdString)
hooks.Em.Emit("command.preexec", input, cmdString)
rerun:
var exitCode uint8
var err error
var cont bool
var newline bool
// save incase it changes while prompting (For some reason)
currentRunner := runnerMode
if currentRunner.Type() == rt.StringType {
switch currentRunner.AsString() {
case "hybrid":
_, _, err = handleLua(input)
if err == nil {
cmdFinish(0, input, priv)
return
}
input, exitCode, cont, newline, err = handleSh(input)
case "hybridRev":
_, _, _, _, err = handleSh(input)
if err == nil {
cmdFinish(0, input, priv)
return
}
input, exitCode, err = handleLua(input)
case "lua":
input, exitCode, err = handleLua(input)
case "sh":
input, exitCode, cont, newline, err = handleSh(input)
}
} else {
// can only be a string or function so
var runnerErr error
input, exitCode, cont, newline, runnerErr, err = runLuaRunner(currentRunner, input)
if err != nil {
fmt.Fprintln(os.Stderr, err)
cmdFinish(124, input, priv)
return
}
// yep, we only use `err` to check for lua eval error
// our actual error should only be a runner provided error at this point
// command not found type, etc
err = runnerErr
}
if cont {
input, err = continuePrompt(input, newline)
if err == nil {
goto rerun
} else if err == io.EOF {
lr.SetPrompt(fmtPrompt(prompt))
}
}
if err != nil && err != io.EOF {
if exErr, ok := isExecError(err); ok {
hooks.Emit("command." + exErr.typ, exErr.cmd)
} else {
fmt.Fprintln(os.Stderr, err)
}
}
cmdFinish(exitCode, input, priv)
}
func reprompt(input string, newline bool) (string, error) {
for {
/*
if strings.HasSuffix(input, "\\") {
input = strings.TrimSuffix(input, "\\") + "\n"
}
*/
in, err := continuePrompt(input, newline)
if err != nil {
lr.SetPrompt(fmtPrompt(prompt))
return input, err
}
return in, nil
}
}
func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, newline bool, runnerErr, err error) {
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term)
if err != nil {
return "", 124, false, false, nil, err
}
var runner *rt.Table
var ok bool
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 {
exitCode = uint8(code)
}
if inp, ok := runner.Get(rt.StringValue("input")).TryString(); ok {
input = inp
}
if errStr, ok := runner.Get(rt.StringValue("err")).TryString(); ok {
runnerErr = fmt.Errorf("%s", errStr)
}
if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok {
continued = c
}
if nl, ok := runner.Get(rt.StringValue("newline")).TryBool(); ok {
newline = nl
}
return
}
func handleLua(input string) (string, uint8, error) {
cmdString := aliases.Resolve(input)
// First try to load input, essentially compiling to bytecode
fn, err := l.LoadString(cmdString)
chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv()))
if err != nil && noexecute {
fmt.Println(err)
/* if lerr, ok := err.(*lua.ApiError); ok {
@ -33,62 +224,110 @@ func runInput(input, origInput string) {
}
}
*/
return
return cmdString, 125, err
}
// And if there's no syntax errors and -n isnt provided, run
if !noexecute {
l.Push(fn)
err = l.PCall(0, lua.MultRet, nil)
if chunk != nil {
_, err = rt.Call1(l.MainThread(), rt.FunctionValue(chunk))
}
}
if err == nil {
cmdFinish(0, cmdString, origInput)
return
return cmdString, 0, nil
}
// Last option: use sh interpreter
err = execCommand(cmdString, origInput)
return cmdString, 125, err
}
func handleSh(cmdString string) (input string, exitCode uint8, cont bool, newline bool, runErr error) {
shRunner := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("sh"))
var err error
input, exitCode, cont, newline, runErr, err = runLuaRunner(shRunner, cmdString)
if err != nil {
runErr = err
}
return
}
func execSh(cmdString string) (input string, exitcode uint8, cont bool, newline bool, e error) {
_, _, err := execCommand(cmdString, nil)
if err != nil {
// If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) {
for {
cmdString, err = continuePrompt(strings.TrimSuffix(cmdString, "\\"))
if err != nil {
break
}
err = execCommand(cmdString, origInput)
if syntax.IsIncomplete(err) || strings.HasSuffix(input, "\\") {
continue
} else if code, ok := interp.IsExitStatus(err); ok {
cmdFinish(code, cmdString, origInput)
} else if err != nil {
fmt.Fprintln(os.Stderr, err)
cmdFinish(1, cmdString, origInput)
} else {
cmdFinish(0, cmdString, origInput)
}
break
if !interactive {
return cmdString, 126, false, false, err
}
newline := false
if strings.Contains(err.Error(), "unclosed here-document") {
newline = true
}
return cmdString, 126, true, newline, err
} else {
if code, ok := interp.IsExitStatus(err); ok {
cmdFinish(code, cmdString, origInput)
return cmdString, code, false, false, nil
} else {
cmdFinish(126, cmdString, origInput)
fmt.Fprintln(os.Stderr, err)
return cmdString, 126, false, false, err
}
}
} else {
cmdFinish(0, cmdString, origInput)
}
return cmdString, 0, false, false, nil
}
// Run command in sh interpreter
func execCommand(cmd, old string) error {
func execCommand(cmd string, strms *streams) (io.Writer, io.Writer, error) {
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
if err != nil {
return err
return nil, nil, err
}
exechandle := func(ctx context.Context, args []string) error {
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)
interp.Env(nil)(runner)
buf := new(bytes.Buffer)
printer := syntax.NewPrinter()
var bg bool
for _, stmt := range file.Stmts {
bg = false
if stmt.Background {
bg = true
printer.Print(buf, stmt.Cmd)
stmtStr := buf.String()
buf.Reset()
jobs.add(stmtStr, []string{}, "")
}
interp.ExecHandler(execHandle(bg))(runner)
err = runner.Run(context.TODO(), stmt)
if err != nil {
return strms.stdout, strms.stderr, err
}
}
return strms.stdout, strms.stderr, nil
}
func execHandle(bg bool) interp.ExecHandlerFunc {
return func(ctx context.Context, args []string) error {
_, argstring := splitInput(strings.Join(args, " "))
// i dont really like this but it works
if aliases.All()[args[0]] != "" {
@ -101,92 +340,231 @@ func execCommand(cmd, old string) error {
// If alias was found, use command alias
argstring = aliases.Resolve(argstring)
args, _ = shell.Fields(argstring, nil)
var err error
args, err = shell.Fields(argstring, nil)
if err != nil {
return err
}
}
// If command is defined in Lua then run it
luacmdArgs := l.NewTable()
for _, str := range args[1:] {
luacmdArgs.Append(lua.LString(str))
luacmdArgs := rt.NewTable()
for i, str := range args[1:] {
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
}
if commands[args[0]] != nil {
err := l.CallByParam(lua.P{
Fn: commands[args[0]],
NRet: 1,
Protect: true,
}, luacmdArgs)
hc := interp.HandlerCtx(ctx)
if cmd := cmds.Commands[args[0]]; cmd != nil {
stdin := newSinkInput(hc.Stdin)
stdout := newSinkOutput(hc.Stdout)
stderr := newSinkOutput(hc.Stderr)
sinks := rt.NewTable()
sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.ud))
sinks.Set(rt.StringValue("input"), rt.UserDataValue(stdin.ud))
sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.ud))
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.ud))
t := rt.NewThread(l)
sig := make(chan os.Signal)
exit := make(chan bool)
luaexitcode := rt.IntValue(63)
var err error
go func() {
defer func() {
if r := recover(); r != nil {
exit <- true
}
}()
signal.Notify(sig, os.Interrupt)
select {
case <-sig:
t.KillContext()
return
}
}()
go func() {
luaexitcode, err = rt.Call1(t, rt.FunctionValue(cmd), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
exit <- true
}()
<-exit
if err != nil {
fmt.Fprintln(os.Stderr,
"Error in command:\n\n" + err.Error())
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
return interp.NewExitStatus(1)
}
luaexitcode := l.Get(-1)
var exitcode uint8
l.Pop(1)
if code, ok := luaexitcode.(lua.LNumber); luaexitcode != lua.LNil && ok {
if code, ok := luaexitcode.TryInt(); ok {
exitcode = uint8(code)
} else if luaexitcode != rt.NilValue {
// deregister commander
delete(cmds.Commands, args[0])
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
}
cmdFinish(exitcode, argstring, old)
return interp.NewExitStatus(exitcode)
}
err := lookpath(args[0])
if err == os.ErrPermission {
hooks.Em.Emit("command.no-perm", args[0])
return interp.NewExitStatus(126)
path, err := lookpath(args[0])
if err == errNotExec {
return execError{
typ: "not-executable",
cmd: args[0],
code: 126,
colon: true,
err: errNotExec,
}
} else if err != nil {
hooks.Em.Emit("command.not-found", args[0])
return interp.NewExitStatus(127)
return execError{
typ: "not-found",
cmd: args[0],
code: 127,
err: errNotFound,
}
}
return interp.DefaultExecHandler(2 * time.Second)(ctx, args)
}
runner, _ := interp.New(
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
interp.ExecHandler(exechandle),
)
err = runner.Run(context.TODO(), file)
killTimeout := 2 * time.Second
// from here is basically copy-paste of the default exec handler from
// sh/interp but with our job handling
return err
env := hc.Env
envList := make([]string, 0, 64)
env.Each(func(name string, vr expand.Variable) bool {
if name == "PATH" {
pathEnv := os.Getenv("PATH")
envList = append(envList, "PATH="+pathEnv)
return true
}
if !vr.IsSet() {
// If a variable is set globally but unset in the
// runner, we need to ensure it's not part of the final
// list. Seems like zeroing the element is enough.
// This is a linear search, but this scenario should be
// rare, and the number of variables shouldn't be large.
for i, kv := range envList {
if strings.HasPrefix(kv, name+"=") {
envList[i] = ""
}
}
}
if vr.Exported && vr.Kind == expand.String {
envList = append(envList, name+"="+vr.String())
}
return true
})
cmd := exec.Cmd{
Path: path,
Args: args,
Env: envList,
Dir: hc.Dir,
Stdin: hc.Stdin,
Stdout: hc.Stdout,
Stderr: hc.Stderr,
}
var j *job
if bg {
j = jobs.getLatest()
j.setHandle(&cmd)
err = j.start()
} else {
err = cmd.Start()
}
if err == nil {
if done := ctx.Done(); done != nil {
go func() {
<-done
if killTimeout <= 0 || runtime.GOOS == "windows" {
cmd.Process.Signal(os.Kill)
return
}
// TODO: don't temporarily leak this goroutine
// if the program stops itself with the
// interrupt.
go func() {
time.Sleep(killTimeout)
cmd.Process.Signal(os.Kill)
}()
cmd.Process.Signal(os.Interrupt)
}()
}
err = cmd.Wait()
}
exit := handleExecErr(err)
if bg {
j.exitCode = int(exit)
j.finish()
}
return interp.NewExitStatus(exit)
}
}
// custom lookpath function so we know if a command is found *and* has execute permission
func lookpath(file string) error {
skip := []string{"./", "/", "../", "~/"}
func handleExecErr(err error) (exit uint8) {
ctx := context.TODO()
switch x := err.(type) {
case *exec.ExitError:
// started, but errored - default to 1 if OS
// doesn't have exit statuses
if status, ok := x.Sys().(syscall.WaitStatus); ok {
if status.Signaled() {
if ctx.Err() != nil {
return
}
exit = uint8(128 + status.Signal())
return
}
exit = uint8(status.ExitStatus())
return
}
exit = 1
return
case *exec.Error:
// did not start
//fmt.Fprintf(hc.Stderr, "%v\n", err)
exit = 127
default: return
}
return
}
func lookpath(file string) (string, error) { // custom lookpath function so we know if a command is found *and* is executable
var skip []string
if runtime.GOOS == "windows" {
skip = []string{"./", "../", "~/", "C:"}
} else {
skip = []string{"./", "/", "../", "~/"}
}
for _, s := range skip {
if strings.HasPrefix(file, s) {
err := findExecutable(file)
return err
return file, findExecutable(file, false, false)
}
}
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
path := filepath.Join(dir, file)
err := findExecutable(path)
if err == os.ErrPermission {
return err
err := findExecutable(path, true, false)
if err == errNotExec {
return "", err
} else if err == nil {
return nil
return path, nil
}
}
return os.ErrNotExist
}
func findExecutable(name string) error {
f, err := os.Stat(name)
if err != nil {
return err
}
if m := f.Mode(); !m.IsDir() && m & 0111 != 0 {
return nil
}
return os.ErrPermission
return "", os.ErrNotExist
}
func splitInput(input string) ([]string, string) {
@ -242,11 +620,10 @@ func splitInput(input string) ([]string, string) {
return cmdArgs, cmdstr.String()
}
func cmdFinish(code uint8, cmdstr, oldInput string) {
// if input has space at the beginning, dont put in history
if !strings.HasPrefix(oldInput, " ") || interactive {
handleHistory(cmdstr)
}
util.SetField(l, hshMod, "exitCode", lua.LNumber(code), "Exit code of last exected command")
hooks.Em.Emit("command.exit", code, cmdstr)
func cmdFinish(code uint8, cmdstr string, private bool) {
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
hooks.Emit("command.exit", rt.IntValue(int64(code)), cmdstr, private)
}

29
execfile_unix.go 100644
View File

@ -0,0 +1,29 @@
//go:build unix
package main
import (
"os"
"syscall"
)
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
func findExecutable(path string, inPath, dirs bool) error {
f, err := os.Stat(path)
if err != nil {
return err
}
if dirs {
if m := f.Mode(); m & 0111 != 0 {
return nil
}
} else {
if m := f.Mode(); !m.IsDir() && m & 0111 != 0 {
return nil
}
}
return errNotExec
}

View File

@ -0,0 +1,42 @@
//go:build windows
package main
import (
"path/filepath"
"os"
"syscall"
)
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
}
func findExecutable(path string, inPath, dirs bool) error {
nameExt := filepath.Ext(path)
pathExts := filepath.SplitList(os.Getenv("PATHEXT"))
if inPath {
if nameExt == "" {
for _, ext := range pathExts {
_, err := os.Stat(path + ext)
if err == nil {
return nil
}
}
} else {
_, err := os.Stat(path)
if err == nil {
if contains(pathExts, nameExt) { return nil }
return errNotExec
}
}
} else {
_, err := os.Stat(path)
if err == nil {
if contains(pathExts, nameExt) { return nil }
return errNotExec
}
}
return os.ErrNotExist
}

BIN
gallery/tab.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 99 KiB

38
go.mod
View File

@ -1,21 +1,37 @@
module hilbish
go 1.16
go 1.21
toolchain go1.22.2
require (
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
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
layeh.com/gopher-luar v1.0.10
mvdan.cc/sh/v3 v3.4.3
github.com/sahilm/fuzzy v0.1.1
golang.org/x/sys v0.22.0
golang.org/x/term v0.22.0
mvdan.cc/sh/v3 v3.8.0
)
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e
require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/arnodel/strftime v0.1.6 // indirect
github.com/evilsocket/islazy v1.11.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // 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 github.com/maxlandon/readline => github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73
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-20241104031959-5551ea280f23

98
go.sum
View File

@ -1,68 +1,46 @@
github.com/Rosettea/readline-1 v0.0.0-20220302012429-9ce5d23760f7 h1:LoY+kBKqMQqBcilRpVvifBTVve84asa3btpx3D/+IvM=
github.com/Rosettea/readline-1 v0.0.0-20220302012429-9ce5d23760f7/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119 h1:rGsc30WTD5hk+oiXrAKsAIwZn5qBeTAdr29y3HhJh9E=
github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93 h1:SmOkAEm3O7si8CURZSsSN0ZxCQ8IGiiulw8LMZ1V1Yc=
github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.1.0-beta.0.20211207003625-341c7985ad7d h1:KBttN41h/tPahmpaZavviwQ8q4rCkt5CD0HdVmfgPVA=
github.com/Rosettea/readline-1 v0.1.0-beta.0.20211207003625-341c7985ad7d/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.1.0-beta.0.20220228022904-61f5e4493011 h1:+a61iNamZiO3Xru+l/1qtpKqqltVfWEm2r/rxH9hXxY=
github.com/Rosettea/readline-1 v0.1.0-beta.0.20220228022904-61f5e4493011/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20211022004519-f67a49cb50f5 h1:ygwVRX8gf5MHA0VzSgOdscCEoAJLjM8joEotfQPgAd0=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20211022004519-f67a49cb50f5/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs=
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-20241104031959-5551ea280f23 h1:mUZnT0gmDEmTkqXsbnDbuJ3CNil7DCOMiCQYgjbKIdI=
github.com/Rosettea/golua v0.0.0-20241104031959-5551ea280f23/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73 h1:zTTUJqNnrF2qf4LgygN8Oae5Uxn6ewH0hA8jyTCHfXw=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73/go.mod h1:YZalN5H7WNQw3DGij6IvHsEhn5YMW7M2FCwG6gnfKy4=
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/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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
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/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=
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/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw=
github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4KTKzJfWs=
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.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
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/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/layeh/gopher-luar v1.0.10 h1:8NIv4MX1Arz96kK4buGK1D87DyDxKZyq6KKvJ2diHp0=
github.com/layeh/gopher-luar v1.0.10/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
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/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
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.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 h1:BXxu8t6QN0G1uff4bzZzSkpsax8+ALqTGUtz08QrV00=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/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/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=
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=
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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

View File

@ -1,70 +1,376 @@
// 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 (
"fmt"
"errors"
"hilbish/util"
"github.com/chuckpreslar/emission"
"github.com/yuin/gopher-lua"
"layeh.com/gopher-luar"
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
)
type Bait struct{
Em *emission.Emitter
type listenerType int
const (
goListener listenerType = iota
luaListener
)
// Recoverer is a function which is called when a panic occurs in an event.
type Recoverer func(event string, handler *Listener, err interface{})
// Listener is a struct that holds the handler for an event.
type Listener struct{
typ listenerType
once bool
caller func(...interface{})
luaCaller *rt.Closure
}
func New() Bait {
emitter := emission.NewEmitter()
emitter.RecoverWith(func(hookname, hookfunc interface{}, err error) {
emitter.Off(hookname, hookfunc)
fmt.Println(err)
})
return Bait{
Em: emitter,
type Bait struct{
Loader packagelib.Loader
recoverer Recoverer
handlers map[string][]*Listener
rtm *rt.Runtime
}
// New creates a new Bait instance.
func New(rtm *rt.Runtime) *Bait {
b := &Bait{
handlers: make(map[string][]*Listener),
rtm: rtm,
}
b.Loader = packagelib.Loader{
Load: b.loaderFunc,
Name: "bait",
}
return b
}
// Emit throws an event.
func (b *Bait) Emit(event string, args ...interface{}) {
handles := b.handlers[event]
if handles == nil {
return
}
for idx, handle := range handles {
defer func() {
if err := recover(); err != nil {
b.callRecoverer(event, handle, err)
}
}()
if handle.typ == luaListener {
funcVal := rt.FunctionValue(handle.luaCaller)
var luaArgs []rt.Value
for _, arg := range args {
var luarg rt.Value
switch arg.(type) {
case rt.Value: luarg = arg.(rt.Value)
default: luarg = rt.AsValue(arg)
}
luaArgs = append(luaArgs, luarg)
}
_, err := rt.Call1(b.rtm.MainThread(), funcVal, luaArgs...)
if err != nil {
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 {
handle.caller(args...)
}
if handle.once {
b.removeListener(event, idx)
}
}
}
func (b *Bait) Loader(L *lua.LState) int {
mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{})
// On adds a Go function handler for an event.
func (b *Bait) On(event string, handler func(...interface{})) *Listener {
listener := &Listener{
typ: goListener,
caller: handler,
}
util.Document(L, 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.`)
L.SetField(mod, "throw", luar.New(L, b.bthrow))
L.SetField(mod, "catch", luar.New(L, b.bcatch))
L.SetField(mod, "catchOnce", luar.New(L, b.bcatchOnce))
L.Push(mod)
return 1
b.addListener(event, listener)
return listener
}
// throw(name, ...args)
// Throws a hook with `name` with the provided `args`
// --- @param name string
// --- @vararg any
func (b *Bait) bthrow(name string, args ...interface{}) {
b.Em.Emit(name, args...)
// OnLua adds a Lua function handler for an event.
func (b *Bait) OnLua(event string, handler *rt.Closure) *Listener {
listener :=&Listener{
typ: luaListener,
luaCaller: handler,
}
b.addListener(event, listener)
return listener
}
// Off removes a Go function handler for an event.
func (b *Bait) Off(event string, listener *Listener) {
handles := b.handlers[event]
for i, handle := range handles {
if handle == listener {
b.removeListener(event, i)
}
}
}
// OffLua removes a Lua function handler for an event.
func (b *Bait) OffLua(event string, handler *rt.Closure) {
handles := b.handlers[event]
for i, handle := range handles {
if handle.luaCaller == handler {
b.removeListener(event, i)
}
}
}
// Once adds a Go function listener for an event that only runs once.
func (b *Bait) Once(event string, handler func(...interface{})) *Listener {
listener := &Listener{
typ: goListener,
once: true,
caller: handler,
}
b.addListener(event, listener)
return listener
}
// OnceLua adds a Lua function listener for an event that only runs once.
func (b *Bait) OnceLua(event string, handler *rt.Closure) *Listener {
listener := &Listener{
typ: luaListener,
once: true,
luaCaller: handler,
}
b.addListener(event, listener)
return listener
}
// SetRecoverer sets the function to be executed when a panic occurs in an event.
func (b *Bait) SetRecoverer(recoverer Recoverer) {
b.recoverer = recoverer
}
func (b *Bait) addListener(event string, listener *Listener) {
if b.handlers[event] == nil {
b.handlers[event] = []*Listener{}
}
b.handlers[event] = append(b.handlers[event], listener)
}
func (b *Bait) removeListener(event string, idx int) {
b.handlers[event][idx] = b.handlers[event][len(b.handlers[event]) - 1]
b.handlers[event] = b.handlers[event][:len(b.handlers[event]) - 1]
}
func (b *Bait) callRecoverer(event string, handler *Listener, err interface{}) {
if b.recoverer == nil {
panic(err)
}
b.recoverer(event, handler, err)
}
func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
exports := map[string]util.LuaExport{
"catch": util.LuaExport{b.bcatch, 2, false},
"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)
return rt.TableValue(mod), nil
}
func handleHook(t *rt.Thread, c *rt.GoCont, name string, catcher *rt.Closure, args ...interface{}) {
funcVal := rt.FunctionValue(catcher)
var luaArgs []rt.Value
for _, arg := range args {
var luarg rt.Value
switch arg.(type) {
case rt.Value: luarg = arg.(rt.Value)
default: luarg = rt.AsValue(arg)
}
luaArgs = append(luaArgs, luarg)
}
_, err := rt.Call1(t, funcVal, luaArgs...)
if err != nil {
e := rt.NewError(rt.StringValue(err.Error()))
e = e.AddContext(c.Next(), 1)
// panicking here won't actually cause hilbish to panic and instead will
// print the error and remove the hook (look at emission recover from above)
panic(e)
}
}
// 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(name string, catcher func(...interface{})) {
b.Em.On(name, catcher)
// 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)
// Same as catch, but only runs the `cb` once and then removes the hook
// --- @param name string
// --- @param cb function
func (b *Bait) bcatchOnce(name string, catcher func(...interface{})) {
b.Em.Once(name, catcher)
// 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)
// #param name string The name of the hook.
// #param args ...any The arguments to pass to the hook.
// Throws a hook with `name` with the provided `args`.
/*
#example
bait.throw('greeting', 'world')
-- This can then be listened to via
bait.catch('gretting', function(greetTo)
print('Hello ' .. greetTo)
end)
#example
*/
func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
name, err := c.StringArg(0)
if err != nil {
return nil, err
}
ifaceSlice := make([]interface{}, len(c.Etc()))
for i, v := range c.Etc() {
ifaceSlice[i] = v
}
b.Emit(name, ifaceSlice...)
return c.Next(), nil
}

View File

@ -1,54 +1,133 @@
// 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 (
"hilbish/util"
"hilbish/golibs/bait"
"github.com/chuckpreslar/emission"
"github.com/yuin/gopher-lua"
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
)
type Commander struct{
Events *emission.Emitter
Events *bait.Bait
Loader packagelib.Loader
Commands map[string]*rt.Closure
}
func New() Commander {
return Commander{
Events: emission.NewEmitter(),
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,
Name: "commander",
}
return c
}
func (c *Commander) Loader(L *lua.LState) int {
exports := map[string]lua.LGFunction{
"register": c.cregister,
"deregister": c.cderegister,
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 := L.SetFuncs(L.NewTable(), exports)
util.Document(L, mod, "Commander is Hilbish's custom command library, a way to write commands in Lua.")
L.Push(mod)
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
return 1
return rt.TableValue(mod), nil
}
// register(name, cb)
// Register a command with `name` that runs `cb` when ran
// --- @param name string
// --- @param cb function
func (c *Commander) cregister(L *lua.LState) int {
cmdName := L.CheckString(1)
cmd := L.CheckFunction(2)
// 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
c.Events.Emit("commandRegister", cmdName, cmd)
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
}
return 0
c.Commands[cmdName] = cmd
return ct.Next(), err
}
// deregister(name)
// Deregisters any command registered with `name`
// --- @param name string
func (c *Commander) cderegister(L *lua.LState) int {
cmdName := L.CheckString(1)
// 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
}
cmdName, err := ct.StringArg(0)
if err != nil {
return nil, err
}
c.Events.Emit("commandDeregister", cmdName)
delete(c.Commands, cmdName)
return 0
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,58 +1,235 @@
// 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 fs functions.
// 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 (
"fmt"
"path/filepath"
"strconv"
"os"
"strings"
"hilbish/util"
"github.com/yuin/gopher-lua"
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
"github.com/arnodel/golua/lib/iolib"
"mvdan.cc/sh/v3/interp"
)
func Loader(L *lua.LState) int {
mod := L.SetFuncs(L.NewTable(), exports)
util.Document(L, 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 fs functions.`)
L.Push(mod)
return 1
type fs struct{
runner *interp.Runner
Loader packagelib.Loader
}
var exports = map[string]lua.LGFunction{
"cd": fcd,
"mkdir": fmkdir,
"stat": fstat,
"readdir": freaddir,
func New(runner *interp.Runner) *fs {
f := &fs{
runner: runner,
}
f.Loader = packagelib.Loader{
Load: f.loaderFunc,
Name: "fs",
}
return f
}
func (f *fs) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
exports := map[string]util.LuaExport{
"cd": util.LuaExport{f.fcd, 1, false},
"mkdir": util.LuaExport{f.fmkdir, 2, false},
"stat": util.LuaExport{f.fstat, 1, false},
"readdir": util.LuaExport{f.freaddir, 1, false},
"abs": util.LuaExport{f.fabs, 1, false},
"basename": util.LuaExport{f.fbasename, 1, false},
"dir": util.LuaExport{f.fdir, 1, false},
"glob": util.LuaExport{f.fglob, 1, false},
"join": util.LuaExport{f.fjoin, 0, true},
"pipe": util.LuaExport{f.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)))
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 (f *fs) 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 (f *fs) 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
func fcd(L *lua.LState) int {
path := L.CheckString(1)
err := os.Chdir(strings.TrimSpace(path))
// Changes Hilbish's directory to `dir`.
// #param dir string Path to change directory to.
func (f *fs) fcd(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 {
e := err.(*os.PathError).Err.Error()
L.RaiseError(e + ": " + path)
return nil, err
}
path = util.ExpandHome(strings.TrimSpace(path))
abspath, _ := filepath.Abs(path)
err = os.Chdir(path)
if err != nil {
return nil, err
}
interp.Dir(abspath)(f.runner)
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 (f *fs) 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 0
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 (f *fs) 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 (f *fs) 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
func fmkdir(L *lua.LState) int {
dirname := L.CheckString(1)
recursive := L.ToBool(2)
path := strings.TrimSpace(dirname)
var err error
// 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 (f *fs) fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
}
path, err := c.StringArg(0)
if err != nil {
return nil, err
}
recursive, err := c.BoolArg(1)
if err != nil {
return nil, err
}
path = util.ExpandHome(strings.TrimSpace(path))
if recursive {
err = os.MkdirAll(path, 0744)
@ -60,51 +237,100 @@ func fmkdir(L *lua.LState) int {
err = os.Mkdir(path, 0744)
}
if err != nil {
L.RaiseError(err.Error() + ": " + path)
return nil, err
}
return 0
return c.Next(), err
}
// stat(path)
// Returns info about `path`
// --- @param path string
func fstat(L *lua.LState) int {
path := L.CheckString(1)
pathinfo, err := os.Stat(path)
// 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 (f *fs) fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
rf, wf, err := os.Pipe()
if err != nil {
L.RaiseError(err.Error() + ": " + path)
return 0
return nil, err
}
statTbl := L.NewTable()
L.SetField(statTbl, "name", lua.LString(pathinfo.Name()))
L.SetField(statTbl, "size", lua.LNumber(pathinfo.Size()))
L.SetField(statTbl, "mode", lua.LString("0" + strconv.FormatInt(int64(pathinfo.Mode().Perm()), 8)))
L.SetField(statTbl, "isDir", lua.LBool(pathinfo.IsDir()))
L.Push(statTbl)
return 1
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(dir)
// Returns a table of files in `dir`
// --- @param dir string
// --- @return table
func freaddir(L *lua.LState) int {
dir := L.CheckString(1)
names := L.NewTable()
// readdir(path) -> table[string]
// Returns a list of all files and directories in the provided path.
// #param dir string
// #returns table
func (f *fs) 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 {
L.RaiseError(err.Error() + ": " + dir)
return 0
return nil, err
}
for _, entry := range dirEntries {
names.Append(lua.LString(entry.Name()))
for i, entry := range dirEntries {
names.Set(rt.IntValue(int64(i + 1)), rt.StringValue(entry.Name()))
}
L.Push(names)
return 1
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 (f *fs) fstat(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
}
path = util.ExpandHome(path)
pathinfo, err := os.Stat(path)
if err != nil {
return nil, err
}
statTbl := rt.NewTable()
statTbl.Set(rt.StringValue("name"), rt.StringValue(pathinfo.Name()))
statTbl.Set(rt.StringValue("size"), rt.IntValue(pathinfo.Size()))
statTbl.Set(rt.StringValue("mode"), rt.StringValue("0" + strconv.FormatInt(int64(pathinfo.Mode().Perm()), 8)))
statTbl.Set(rt.StringValue("isDir"), rt.BoolValue(pathinfo.IsDir()))
return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), 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 (
@ -5,76 +7,77 @@ import (
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
"golang.org/x/term"
"github.com/yuin/gopher-lua"
)
var termState *term.State
func Loader(L *lua.LState) int {
mod := L.SetFuncs(L.NewTable(), exports)
util.Document(L, mod, "The terminal library is a simple and lower level library for certain terminal interactions.")
L.Push(mod)
return 1
var Loader = packagelib.Loader{
Load: loaderFunc,
Name: "terminal",
}
var exports = map[string]lua.LGFunction{
"setRaw": termraw,
"restoreState": termrestoreState,
"size": termsize,
"saveState": termsaveState,
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
exports := map[string]util.LuaExport{
"setRaw": util.LuaExport{termsetRaw, 0, false},
"restoreState": util.LuaExport{termrestoreState, 0, false},
"size": util.LuaExport{termsize, 0, false},
"saveState": util.LuaExport{termsaveState, 0, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
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
func termsize(L *lua.LState) int {
// 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 {
L.RaiseError(err.Error())
return 0
return nil, err
}
dimensions := L.NewTable()
L.SetField(dimensions, "width", lua.LNumber(w))
L.SetField(dimensions, "height", lua.LNumber(h))
L.Push(dimensions)
return 1
dimensions := rt.NewTable()
dimensions.Set(rt.StringValue("width"), rt.IntValue(int64(w)))
dimensions.Set(rt.StringValue("height"), rt.IntValue(int64(h)))
return c.PushingNext1(t.Runtime, rt.TableValue(dimensions)), nil
}
// saveState()
// Saves the current state of the terminal
func termsaveState(L *lua.LState) int {
// 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 {
L.RaiseError(err.Error())
return 0
return nil, err
}
termState = state
return 0
return c.Next(), nil
}
// restoreState()
// Restores the last saved state of the terminal
func termrestoreState(L *lua.LState) int {
func termrestoreState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := term.Restore(int(os.Stdin.Fd()), termState)
if err != nil {
L.RaiseError(err.Error())
return nil, err
}
return 0
return c.Next(), nil
}
// setRaw()
// Puts the terminal in raw mode
func termraw(L *lua.LState) int {
// 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 {
L.RaiseError(err.Error())
return nil, err
}
return 0
return c.Next(), 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

@ -4,33 +4,86 @@ import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
rt "github.com/arnodel/golua/runtime"
)
type luaHistory struct {}
func (h *luaHistory) Write(line string) (int, error) {
histWrite := hshMod.Get(rt.StringValue("history")).AsTable().Get(rt.StringValue("add"))
ln, err := rt.Call1(l.MainThread(), histWrite, rt.StringValue(line))
var num int64
if ln.Type() == rt.IntType {
num = ln.AsInt()
}
return int(num), err
}
func (h *luaHistory) GetLine(idx int) (string, error) {
histGet := hshMod.Get(rt.StringValue("history")).AsTable().Get(rt.StringValue("get"))
lcmd, err := rt.Call1(l.MainThread(), histGet, rt.IntValue(int64(idx)))
var cmd string
if lcmd.Type() == rt.StringType {
cmd = lcmd.AsString()
}
return cmd, err
}
func (h *luaHistory) Len() int {
histSize := hshMod.Get(rt.StringValue("history")).AsTable().Get(rt.StringValue("size"))
ln, _ := rt.Call1(l.MainThread(), histSize)
var num int64
if ln.Type() == rt.IntType {
num = ln.AsInt()
}
return int(num)
}
func (h *luaHistory) Dump() interface{} {
// hilbish.history interface already has all function, this isnt used in readline
return nil
}
type fileHistory struct {
items []string
f *os.File
}
func newFileHistory() (*fileHistory, error) {
data, err := os.ReadFile(defaultHistPath)
func newFileHistory(path string) *fileHistory {
dir := filepath.Dir(path)
err := os.MkdirAll(dir, 0755)
if err != nil {
panic(err)
}
data, err := os.ReadFile(path)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, err
panic(err)
}
}
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(defaultHistPath, os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0755)
f, err := os.OpenFile(path, os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0755)
if err != nil {
return nil, err
panic(err)
}
fh := &fileHistory{
@ -38,7 +91,7 @@ func newFileHistory() (*fileHistory, error) {
f: f,
}
return fh, nil
return fh
}
func (h *fileHistory) Write(line string) (int, error) {
@ -73,3 +126,9 @@ func (h *fileHistory) Len() int {
func (h *fileHistory) Dump() interface{} {
return h.items
}
func (h *fileHistory) clear() {
h.items = []string{}
h.f.Truncate(0)
h.f.Sync()
}

17
init_windows.go 100644
View File

@ -0,0 +1,17 @@
//go:build windows
package main
import "golang.org/x/sys/windows"
func init() {
// vt output (escape codes)
var outMode uint32
windows.GetConsoleMode(windows.Stdout, &outMode)
windows.SetConsoleMode(windows.Stdout, outMode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
// vt input
var inMode uint32
windows.GetConsoleMode(windows.Stdin, &inMode)
windows.SetConsoleMode(windows.Stdin, inMode | windows.ENABLE_VIRTUAL_TERMINAL_INPUT)
}

507
job.go 100644
View File

@ -0,0 +1,507 @@
package main
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"sync"
"syscall"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
)
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
id int
pid int
exitCode int
once bool
args []string
// save path for a few reasons, one being security (lmao) while the other
// would just be so itll be the same binary command always (path changes)
path string
handle *exec.Cmd
cmdout io.Writer
cmderr io.Writer
stdout *bytes.Buffer
stderr *bytes.Buffer
ud *rt.UserData
}
func (j *job) start() error {
if j.handle == nil || j.once {
// cmd cant be reused so make a new one
cmd := exec.Cmd{
Path: j.path,
Args: j.args,
}
j.setHandle(&cmd)
}
// bgProcAttr is defined in execfile_<os>.go, it holds a procattr struct
// in a simple explanation, it makes signals from hilbish (sigint)
// not go to it (child process)
j.handle.SysProcAttr = bgProcAttr
// reset output buffers
j.stdout.Reset()
j.stderr.Reset()
// make cmd write to both standard output and output buffers for lua access
j.handle.Stdout = io.MultiWriter(j.cmdout, j.stdout)
j.handle.Stderr = io.MultiWriter(j.cmderr, j.stderr)
if !j.once {
j.once = true
}
err := j.handle.Start()
proc := j.getProc()
j.pid = proc.Pid
j.running = true
hooks.Emit("job.start", rt.UserDataValue(j.ud))
return err
}
func (j *job) stop() {
// finish will be called in exec handle
proc := j.getProc()
if proc != nil {
proc.Kill()
}
}
func (j *job) finish() {
j.running = false
hooks.Emit("job.done", rt.UserDataValue(j.ud))
}
func (j *job) wait() {
j.handle.Wait()
}
func (j *job) setHandle(handle *exec.Cmd) {
j.handle = handle
j.args = handle.Args
j.path = handle.Path
if handle.Stdout != nil {
j.cmdout = handle.Stdout
}
if handle.Stderr != nil {
j.cmderr = handle.Stderr
}
}
func (j *job) getProc() *os.Process {
handle := j.handle
if handle != nil {
return handle.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
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
if !j.running {
err := j.start()
exit := handleExecErr(err)
j.exitCode = int(exit)
j.finish()
}
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
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
if j.running {
j.stop()
j.finish()
}
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
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
if !j.running {
return nil, errors.New("job not running")
}
// lua code can run in other threads and goroutines, so this exists
jobs.foreground = true
// this is kinda funny
// background continues the process incase it got suspended
err = j.background()
if err != nil {
return nil, err
}
err = j.foreground()
if err != nil {
return nil, err
}
jobs.foreground = false
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
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
if !j.running {
return nil, errors.New("job not running")
}
err = j.background()
if err != nil {
return nil, err
}
return c.Next(), nil
}
type jobHandler struct {
jobs map[int]*job
latestID int
foreground bool // if job currently in the foreground
mu *sync.RWMutex
}
func newJobHandler() *jobHandler {
return &jobHandler{
jobs: make(map[int]*job),
latestID: 0,
mu: &sync.RWMutex{},
}
}
func (j *jobHandler) add(cmd string, args []string, path string) *job {
j.mu.Lock()
defer j.mu.Unlock()
j.latestID++
jb := &job{
cmd: cmd,
running: false,
id: j.latestID,
args: args,
path: path,
cmdout: os.Stdout,
cmderr: os.Stderr,
stdout: &bytes.Buffer{},
stderr: &bytes.Buffer{},
}
jb.ud = jobUserData(jb)
j.jobs[j.latestID] = jb
hooks.Emit("job.add", rt.UserDataValue(jb.ud))
return jb
}
func (j *jobHandler) getLatest() *job {
j.mu.RLock()
defer j.mu.RUnlock()
return j.jobs[j.latestID]
}
func (j *jobHandler) disown(id int) error {
j.mu.RLock()
if j.jobs[id] == nil {
return errors.New("job doesnt exist")
}
j.mu.RUnlock()
j.mu.Lock()
delete(j.jobs, id)
j.mu.Unlock()
return nil
}
func (j *jobHandler) stopAll() {
j.mu.RLock()
defer j.mu.RUnlock()
for _, jb := range j.jobs {
// on exit, unix shell should send sighup to all jobs
if jb.running {
proc := jb.getProc()
proc.Signal(syscall.SIGHUP)
jb.wait() // waits for program to exit due to sighup
}
}
}
// #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{
"stop": {luaStopJob, 1, false},
"start": {luaStartJob, 1, false},
"foreground": {luaForegroundJob, 1, false},
"background": {luaBackgroundJob, 1, false},
}
util.SetExports(l, jobMethods, jFuncs)
jobMeta := rt.NewTable()
jobIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j, _ := jobArg(c, 0)
arg := c.Arg(1)
val := jobMethods.Get(arg)
if val != rt.NilValue {
return c.PushingNext1(t.Runtime, val), nil
}
keyStr, _ := arg.TryString()
switch keyStr {
case "cmd": val = rt.StringValue(j.cmd)
case "running": val = rt.BoolValue(j.running)
case "id": val = rt.IntValue(int64(j.id))
case "pid": val = rt.IntValue(int64(j.pid))
case "exitCode": val = rt.IntValue(int64(j.exitCode))
case "stdout": val = rt.StringValue(string(j.stdout.Bytes()))
case "stderr": val = rt.StringValue(string(j.stderr.Bytes()))
}
return c.PushingNext1(t.Runtime, val), nil
}
jobMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(jobIndex, "__index", 2, false)))
l.SetRegistry(jobMetaKey, rt.TableValue(jobMeta))
jobFuncs := map[string]util.LuaExport{
"all": {j.luaAllJobs, 0, false},
"last": {j.luaLastJob, 0, false},
"get": {j.luaGetJob, 1, false},
"add": {j.luaAddJob, 3, false},
"disown": {j.luaDisownJob, 1, false},
}
luaJob := rt.NewTable()
util.SetExports(rtm, luaJob, jobFuncs)
return luaJob
}
func jobArg(c *rt.GoCont, arg int) (*job, error) {
j, ok := valueToJob(c.Arg(arg))
if !ok {
return nil, fmt.Errorf("#%d must be a job", arg + 1)
}
return j, nil
}
func valueToJob(val rt.Value) (*job, bool) {
u, ok := val.TryUserData()
if !ok {
return nil, false
}
j, ok := u.Value().(*job)
return j, ok
}
func jobUserData(j *job) *rt.UserData {
jobMeta := l.Registry(jobMetaKey)
return rt.NewUserData(j, jobMeta.AsTable())
}
// #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()
if err := c.Check1Arg(); err != nil {
return nil, err
}
jobID, err := c.IntArg(0)
if err != nil {
return nil, err
}
job := j.jobs[int(jobID)]
if job == nil {
return c.Next(), nil
}
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
}
cmd, err := c.StringArg(0)
if err != nil {
return nil, err
}
largs, err := c.TableArg(1)
if err != nil {
return nil, err
}
execPath, err := c.StringArg(2)
if err != nil {
return nil, err
}
var args []string
util.ForEach(largs, func(k rt.Value, v rt.Value) {
if v.Type() == rt.StringType {
args = append(args, v.AsString())
}
})
jb := j.add(cmd, args, execPath)
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()
jobTbl := rt.NewTable()
for id, job := range j.jobs {
jobTbl.Set(rt.IntValue(int64(id)), rt.UserDataValue(job.ud))
}
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
}
jobID, err := c.IntArg(0)
if err != nil {
return nil, err
}
err = j.disown(int(jobID))
if err != nil {
return nil, err
}
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()
job := j.jobs[j.latestID]
if job == nil { // incase we dont have any jobs yet
return c.Next(), nil
}
return c.PushingNext1(t.Runtime, rt.UserDataValue(job.ud)), nil
}

38
job_unix.go 100644
View File

@ -0,0 +1,38 @@
//go:build unix
package main
import (
"errors"
"os"
"syscall"
"golang.org/x/sys/unix"
)
func (j *job) foreground() error {
if jobs.foreground {
return errors.New("(another) job already foregrounded")
}
pgid, _ := syscall.Getpgid(j.pid)
// tcsetpgrp
unix.IoctlSetPointerInt(0, unix.TIOCSPGRP, pgid)
proc, _ := os.FindProcess(j.pid)
proc.Wait()
hshPgid, _ := syscall.Getpgid(os.Getpid())
unix.IoctlSetPointerInt(0, unix.TIOCSPGRP, hshPgid)
return nil
}
func (j *job) background() error {
proc := j.handle.Process
if proc == nil {
return nil
}
proc.Signal(syscall.SIGCONT)
return nil
}

15
job_windows.go 100644
View File

@ -0,0 +1,15 @@
//go:build windows
package main
import (
"errors"
)
func (j *job) foreground() error {
return errors.New("not supported on windows")
}
func (j *job) background() error {
return errors.New("not supported on windows")
}

View File

@ -89,21 +89,25 @@ end
ansikit.print = function(text)
io.write(ansikit.format(text))
io.flush()
return ansikit
end
ansikit.printCode = function(code, terminate)
io.write(ansikit.getCode(code, terminate))
io.flush()
return ansikit
end
ansikit.printCSI = function(code, endc)
io.write(ansikit.getCSI(code, endc))
io.flush()
return ansikit
end
ansikit.println = function(text)
print(ansikit.print(text))
io.write(ansikit.format(text) .. "\n")
io.flush()
return ansikit
end

@ -1 +1 @@
Subproject commit d200fca13df1c47e133b269420223d7e257dbc41
Subproject commit 34a57c964590f89aa065188a588c7b38aff99c28

75
lua.go
View File

@ -4,71 +4,82 @@ import (
"fmt"
"os"
"hilbish/util"
"hilbish/golibs/bait"
"hilbish/golibs/commander"
"hilbish/golibs/fs"
"hilbish/golibs/terminal"
"github.com/yuin/gopher-lua"
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib"
"github.com/arnodel/golua/lib/debuglib"
)
var minimalconf = `hilbish.prompt '& '`
func luaInit() {
l = lua.NewState()
l.OpenLibs()
l = rt.New(os.Stdout)
l.PushContext(rt.RuntimeContextDef{
MessageHandler: debuglib.Traceback,
})
lib.LoadAll(l)
setupSinkType(l)
lib.LoadLibs(l, hilbishLoader)
// yes this is stupid, i know
l.PreloadModule("hilbish", hilbishLoader)
l.DoString("hilbish = require 'hilbish'")
util.DoString(l, "hilbish = require 'hilbish'")
// Add fs and terminal module module to Lua
l.PreloadModule("fs", fs.Loader)
l.PreloadModule("terminal", terminal.Loader)
f := fs.New(runner)
lib.LoadLibs(l, f.Loader)
lib.LoadLibs(l, terminal.Loader)
cmds := commander.New()
// When a command from Lua is added, register it for use
cmds.Events.On("commandRegister", func(cmdName string, cmd *lua.LFunction) {
commands[cmdName] = cmd
})
cmds.Events.On("commandDeregister", func(cmdName string) {
delete(commands, cmdName)
})
l.PreloadModule("commander", cmds.Loader)
cmds = commander.New(l)
lib.LoadLibs(l, cmds.Loader)
hooks = bait.New()
l.PreloadModule("bait", hooks.Loader)
hooks = bait.New(l)
hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) {
fmt.Println("Error in `error` hook handler:", err)
hooks.Off(event, handler)
})
lib.LoadLibs(l, hooks.Loader)
// Add Ctrl-C handler
hooks.Em.On("signal.sigint", func() {
hooks.On("signal.sigint", func(...interface{}) {
if !interactive {
os.Exit(0)
}
})
l.SetGlobal("complete", l.NewFunction(hlcomplete))
lr.rl.RawInputCallback = func(r []rune) {
hooks.Emit("hilbish.rawInput", string(r))
}
// Add more paths that Lua can require from
l.DoString("package.path = package.path .. " + requirePaths)
err := l.DoFile("prelude/init.lua")
_, err := util.DoString(l, "package.path = package.path .. " + requirePaths)
if err != nil {
err = l.DoFile(preloadPath)
if err != nil {
fmt.Fprintln(os.Stderr,
"Missing preload file, builtins may be missing.")
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
}
err1 := util.DoFile(l, "nature/init.lua")
if err1 != nil {
err2 := util.DoFile(l, preloadPath)
if err2 != nil {
fmt.Fprintln(os.Stderr, "Missing nature module, some functionality and builtins will be missing.")
fmt.Fprintln(os.Stderr, "local error:", err1)
fmt.Fprintln(os.Stderr, "global install error:", err2)
}
}
}
func runConfig(confpath string) {
if !interactive {
return
}
err := l.DoFile(confpath)
err := util.DoFile(l, confpath)
if err != nil {
fmt.Fprintln(os.Stderr, err,
"\nAn error has occured while loading your config! Falling back to minimal default config.")
l.DoString(minimalconf)
fmt.Fprintln(os.Stderr, err, "\nAn error has occured while loading your config! Falling back to minimal default config.")
util.DoString(l, minimalconf)
}
}

175
main.go
View File

@ -2,39 +2,47 @@ 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"
"github.com/yuin/gopher-lua"
"github.com/maxlandon/readline"
"golang.org/x/term"
"mvdan.cc/sh/v3/interp"
)
var (
l *lua.LState
l *rt.Runtime
lr *lineReader
commands = map[string]*lua.LFunction{}
luaCompletions = map[string]*lua.LFunction{}
luaCompletions = map[string]*rt.Closure{}
confDir string
userDataDir string
curuser *user.User
hooks bait.Bait
hooks *bait.Bait
cmds *commander.Commander
defaultConfPath string
defaultHistPath string
runner *interp.Runner
)
func main() {
runner, _ = interp.New()
curuser, _ = user.Current()
homedir := curuser.HomeDir
confDir, _ = os.UserConfigDir()
@ -43,7 +51,7 @@ func main() {
// i honestly dont know what directories to use for this
switch runtime.GOOS {
case "linux":
case "linux", "darwin":
userDataDir = getenv("XDG_DATA_HOME", curuser.HomeDir + "/.local/share")
default:
// this is fine on windows, dont know about others
@ -52,16 +60,18 @@ func main() {
if defaultConfDir == "" {
// we'll add *our* default if its empty (wont be if its changed comptime)
defaultConfPath = filepath.Join(confDir, "hilbish", "init.lua")
defaultConfDir = filepath.Join(confDir, "hilbish")
} else {
// else do ~ substitution
defaultConfPath = filepath.Join(strings.Replace(defaultConfDir, "~", homedir, 1), "init.lua")
defaultConfDir = filepath.Join(util.ExpandHome(defaultConfDir), "hilbish")
}
defaultConfPath = filepath.Join(defaultConfDir, "init.lua")
if defaultHistDir == "" {
defaultHistPath = filepath.Join(userDataDir, "hilbish", ".hilbish-history")
defaultHistDir = filepath.Join(userDataDir, "hilbish")
} else {
defaultHistPath = filepath.Join(strings.Replace(defaultHistDir, "~", homedir, 1), ".hilbish-history")
defaultHistDir = filepath.Join(util.ExpandHome(defaultHistDir), "hilbish")
}
defaultHistPath = filepath.Join(defaultHistDir, ".hilbish-history")
helpflag := getopt.BoolLong("help", 'h', "Prints Hilbish flags")
verflag := getopt.BoolLong("version", 'v', "Prints Hilbish version")
setshflag := getopt.BoolLong("setshellenv", 'S', "Sets $SHELL to Hilbish's executed path")
@ -85,7 +95,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
}
@ -103,18 +113,26 @@ func main() {
}
if *verflag {
fmt.Printf("Hilbish %s\n", version)
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()
luaInit()
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
@ -135,66 +153,82 @@ func main() {
} else {
runConfig(*configflag)
}
hooks.Emit("hilbish.init")
if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
scanner := bufio.NewScanner(bufio.NewReader(os.Stdin))
for scanner.Scan() {
text := scanner.Text()
runInput(text, text)
runInput(text, true)
}
exit(0)
}
if *cmdflag != "" {
runInput(*cmdflag, *cmdflag)
runInput(*cmdflag, true)
}
if getopt.NArgs() > 0 {
luaArgs := l.NewTable()
for _, arg := range getopt.Args() {
luaArgs.Append(lua.LString(arg))
luaArgs := rt.NewTable()
for i, arg := range getopt.Args() {
luaArgs.Set(rt.IntValue(int64(i)), rt.StringValue(arg))
}
l.SetGlobal("args", luaArgs)
err := l.DoFile(getopt.Arg(0))
l.GlobalEnv().Set(rt.StringValue("args"), rt.TableValue(luaArgs))
err := util.DoFile(l, getopt.Arg(0))
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
exit(1)
}
os.Exit(0)
exit(0)
}
initialized = true
input:
for interactive {
lr.SetPrompt(fmtPrompt(prompt))
running = false
input, err := lr.Read()
if err == io.EOF {
// Exit if user presses ^D (ctrl + d)
hooks.Em.Emit("hilbish.exit")
hooks.Emit("hilbish.exit")
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
}
oldInput := input
var priv bool
if strings.HasPrefix(input, " ") {
priv = true
}
input = strings.TrimSpace(input)
if len(input) == 0 {
hooks.Em.Emit("command.exit", 0)
running = true
hooks.Emit("command.exit", 0)
continue
}
if strings.HasSuffix(input, "\\") {
print("\n")
for {
input, err = continuePrompt(input)
input, err = continuePrompt(strings.TrimSuffix(input, "\\") + "\n", false)
if err != nil {
running = true
lr.SetPrompt(fmtPrompt(prompt))
goto input // continue inside nested loop
}
if !strings.HasSuffix(input, "\\") {
@ -203,7 +237,7 @@ input:
}
}
runInput(input, oldInput)
runInput(input, priv)
termwidth, _, err := term.GetSize(0)
if err != nil {
@ -211,18 +245,28 @@ input:
}
fmt.Printf("\u001b[7m∆\u001b[0m" + strings.Repeat(" ", termwidth - 1) + "\r")
}
exit(0)
}
func continuePrompt(prev string) (string, error) {
hooks.Em.Emit("multiline", nil)
func continuePrompt(prev string, newline bool) (string, error) {
hooks.Emit("multiline", nil)
lr.SetPrompt(multilinePrompt)
cont, err := lr.Read()
if err != nil {
return "", err
}
cont = strings.TrimSpace(cont)
return prev + strings.TrimSuffix(cont, "\n"), nil
if newline {
cont = "\n" + cont
}
if strings.HasSuffix(cont, "\\") {
cont = strings.TrimSuffix(cont, "\\") + "\n"
}
return prev + cont, nil
}
// This semi cursed function formats our prompt (obviously)
@ -230,9 +274,7 @@ func fmtPrompt(prompt string) string {
host, _ := os.Hostname()
cwd, _ := os.Getwd()
if strings.HasPrefix(cwd, curuser.HomeDir) {
cwd = "~" + strings.TrimPrefix(cwd, curuser.HomeDir)
}
cwd = util.AbbrevHome(cwd)
username := curuser.Username
// this will be baked into binary since GOOS is a constant
if runtime.GOOS == "windows" {
@ -258,8 +300,57 @@ func fmtPrompt(prompt string) string {
return nprompt
}
func handleHistory(cmd string) {
lr.AddHistory(cmd)
// TODO: load history again (history shared between sessions like this ye)
func removeDupes(slice []string) []string {
all := make(map[string]bool)
newSlice := []string{}
for _, item := range slice {
if _, val := all[item]; !val {
all[item] = true
newSlice = append(newSlice, item)
}
}
return newSlice
}
func contains(s []string, e string) bool {
for _, a := range s {
if strings.ToLower(a) == strings.ToLower(e) {
return true
}
}
return false
}
func exit(code int) {
jobs.stopAll()
// wait for all timers to finish before exiting.
// only do that when not interactive
if !interactive {
timers.wait()
}
os.Exit(code)
}
func getVersion() string {
v := strings.Builder{}
v.WriteString(ver)
if gitBranch != "" && gitBranch != "HEAD" {
v.WriteString("-" + gitBranch)
}
if gitCommit != "" {
v.WriteString("." + gitCommit)
}
v.WriteString(" (" + releaseName + ")")
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

@ -0,0 +1,15 @@
local commander = require 'commander'
commander.register('bg', function(_, sinks)
local job = hilbish.jobs.last()
if not job then
sinks.out:writeln 'bg: no last job'
return 1
end
local err = job:background()
if err then
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

@ -0,0 +1,28 @@
local bait = require 'bait'
local commander = require 'commander'
local fs = require 'fs'
local dirs = require 'nature.dirs'
dirs.old = hilbish.cwd()
commander.register('cd', function (args, sinks)
if #args > 1 then
sinks.out:writeln("cd: too many arguments")
return 1
end
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)

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