2
2
mirror of https://github.com/Hilbis/Hilbish synced 2025-04-22 13:33:23 +00:00

Compare commits

..

No commits in common. "883aa0bd807af58157eebc4bb4a8d2c25f9e8101" and "0e3fd1ff49416924832b4b3514f95819affaa6b6" have entirely different histories.

40 changed files with 246 additions and 686 deletions

1
.gitignore vendored
View File

@ -6,4 +6,3 @@ docgen
.vim
petals/
.hugo_build.lock

View File

@ -1,21 +1,5 @@
# 🎀 Changelog
## Unreleased
### Added
- Documented custom userdata types (Job and Timer Objects)
- Coming with fix is also adding the return types for some functions that were missing it
- Added a dedicated input and dedicated outputs for commanders.
### Fixed
- `hilbish.which` not working correctly with aliases
- Commanders not being able to pipe with commands or any related operator.
## [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.
@ -627,7 +611,6 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish.
[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

View File

@ -26,30 +26,52 @@ and aims to be infinitely configurable. If something isn't, open an issue!
# Table of Contents
- [Screenshots](#Screenshots)
- [Getting Hilbish](#Getting-Hilbish)
- [Installation](#Installation)
- [Prebuilt Bins](#Prebuilt-binaries)
- [AUR](#AUR)
- [Nixpkgs](#Nixpkgs)
- [Manual Build](#Manual-Build)
- [Contributing](#Contributing)
# Screenshots
<div align="center">
<img src="gallery/default.png"><br><br>
<img src="gallery/terminal.png"><br><br>
<img src="gallery/tab.png"><br><br>
<img src="gallery/pillprompt.png">
</div>
# Getting Hilbish
# Installation
**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.
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.
## Prebuilt binaries
Go [here](https://nightly.link/Rosettea/Hilbish/workflows/build/master) for
builds on the master branch.
## Prerequisites
## 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
```
[![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)
- [Task](https://taskfile.dev/installation/) (**Go on the hyperlink here to see Task's install method for your OS.**)
## Build
### Build
First, clone Hilbish. The recursive is required, as some Lua libraries
are submodules.
```sh

View File

@ -92,9 +92,9 @@ func (a *aliasModule) Loader(rtm *rt.Runtime) *rt.Table {
func _hlalias() {}
// #interface aliases
// list() -> table<string, string>
// list() -> aliases (table)
// Get a table of all aliases, with string keys as the alias and the value as the command.
// --- @returns table<string, string>
// @returns table<string, string>
func (a *aliasModule) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
aliasesList := rt.NewTable()
for k, v := range a.All() {

28
api.go
View File

@ -231,9 +231,8 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
}
// cwd() -> string
// cwd()
// Returns the current directory of the shell
// --- @returns string
func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
cwd, _ := os.Getwd()
@ -445,12 +444,12 @@ func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// timeout(cb, time) -> @Timer
// Runs the `cb` function after `time` in milliseconds.
// This creates a timer that starts immediately.
// timeout(cb, time)
// Runs the `cb` function after `time` in milliseconds
// Returns a `timer` object (see `doc timers`).
// --- @param cb function
// --- @param time number
// --- @returns Timer
// --- @returns table
func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
@ -471,12 +470,12 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
}
// interval(cb, time) -> @Timer
// interval(cb, time)
// Runs the `cb` function every `time` milliseconds.
// This creates a timer that starts immediately.
// Returns a `timer` object (see `doc timers`).
// --- @param cb function
// --- @param time number
// --- @return Timer
// --- @return table
func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
@ -537,11 +536,9 @@ func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// which(name) -> string
// Checks if `name` is a valid command.
// Will return the path of the binary, or a basename if it's a commander.
// which(name)
// Checks if `name` is a valid command
// --- @param name string
// --- @returns string
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
@ -551,10 +548,7 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
// itll return either the original command or what was passed
// if name isnt empty its not an issue
alias := aliases.Resolve(name)
cmd := strings.Split(alias, " ")[0]
cmd := aliases.Resolve(name)
// check for commander
if commands[cmd] != nil {

View File

@ -7,7 +7,6 @@ import (
"go/doc"
"go/parser"
"go/token"
"regexp"
"strings"
"os"
"sync"
@ -32,7 +31,6 @@ type emmyPiece struct {
}
type module struct {
Types []docPiece
Docs []docPiece
Fields []docPiece
Properties []docPiece
@ -40,7 +38,6 @@ type module struct {
Description string
ParentModule string
HasInterfaces bool
HasTypes bool
}
type docPiece struct {
@ -52,7 +49,6 @@ type docPiece struct {
GoFuncName string
IsInterface bool
IsMember bool
IsType bool
Fields []docPiece
Properties []docPiece
}
@ -65,7 +61,6 @@ type tag struct {
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",
@ -124,71 +119,6 @@ func docPieceTag(tagName string, tags map[string][]tag) []docPiece {
return dps
}
func setupDocType(mod string, typ *doc.Type) *docPiece {
docs := strings.TrimSpace(typ.Doc)
inInterface := strings.HasPrefix(docs, "#interface")
if !inInterface {
return nil
}
tags, doc := getTagsAndDocs(docs)
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
}
var parentMod string
if inInterface {
parentMod = mod
}
dps := &docPiece{
Doc: typeDoc,
FuncName: typeName,
Interfacing: interfaces,
IsInterface: inInterface,
IsMember: isMember,
IsType: true,
ParentModule: parentMod,
Fields: fields,
Properties: properties,
}
typeTable[strings.ToLower(typeName)] = []string{parentMod, interfaces}
return dps
}
func setupDoc(mod string, fun *doc.Func) *docPiece {
docs := strings.TrimSpace(fun.Doc)
inInterface := strings.HasPrefix(docs, "#interface")
@ -290,7 +220,6 @@ func main() {
for l, f := range pkgs {
p := doc.New(f, "./", doc.AllDecls)
pieces := []docPiece{}
typePieces := []docPiece{}
mod := l
if mod == "main" {
mod = "hilbish"
@ -308,14 +237,6 @@ func main() {
}
}
for _, t := range p.Types {
typePiece := setupDocType(mod, t)
if typePiece != nil {
typePieces = append(typePieces, *typePiece)
if typePiece.IsInterface {
hasInterfaces = true
}
}
for _, m := range t.Methods {
piece := setupDoc(mod, m)
if piece == nil {
@ -333,7 +254,6 @@ func main() {
shortDesc := descParts[0]
desc := descParts[1:]
filteredPieces := []docPiece{}
filteredTypePieces := []docPiece{}
for _, piece := range pieces {
if !piece.IsInterface {
filteredPieces = append(filteredPieces, piece)
@ -356,28 +276,10 @@ func main() {
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"),
@ -414,14 +316,7 @@ func main() {
f, _ := os.Create(docPath)
f.WriteString(fmt.Sprintf(header, modOrIface, modname, modu.ShortDescription))
typeTag, _ := regexp.Compile(`@\w+`)
modDescription := typeTag.ReplaceAllStringFunc(strings.Replace(modu.Description, "<", `\<`, -1), func(typ string) string {
typName := typ[1:]
typLookup := typeTable[strings.ToLower(typName)]
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("## Introduction\n%s\n\n", modDescription))
f.WriteString(fmt.Sprintf("## Introduction\n%s\n\n", modu.Description))
if len(modu.Fields) != 0 {
f.WriteString("## Interface fields\n")
for _, dps := range modu.Fields {
@ -440,68 +335,17 @@ func main() {
}
f.WriteString("\n")
}
if len(modu.Docs) != 0 {
f.WriteString("## Functions\n")
for _, dps := range modu.Docs {
if dps.IsMember {
continue
}
htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(dps.FuncSig, "<", `\<`, -1), func(typ string) string {
typName := typ[1:]
typLookup := typeTable[strings.ToLower(typName)]
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")
}
}
if len(modu.Types) != 0 {
f.WriteString("## Types\n")
for _, dps := range modu.Types {
f.WriteString(fmt.Sprintf("## %s\n", dps.FuncName))
for _, doc := range dps.Doc {
if !strings.HasPrefix(doc, "---") {
f.WriteString(doc + "\n")
}
}
if len(dps.Properties) != 0 {
f.WriteString("### Properties\n")
for _, dps := range dps.Properties {
f.WriteString(fmt.Sprintf("- `%s`: ", dps.FuncName))
f.WriteString(strings.Join(dps.Doc, " "))
f.WriteString("\n")
}
}
f.WriteString("\n")
f.WriteString("### Methods\n")
for _, dps := range modu.Docs {
if !dps.IsMember {
continue
}
htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(dps.FuncSig, "<", `\<`, -1), func(typ string) string {
// todo: get type from global table to link to
// other pages (hilbish page can link to hilbish.jobs#Job)
typName := typ[1:]
linkedTyp := strings.ToLower(typName) // TODO: link
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")
for _, dps := range modu.Docs {
f.WriteString(fmt.Sprintf("### %s\n", dps.FuncSig))
for _, doc := range dps.Doc {
if !strings.HasPrefix(doc, "---") {
f.WriteString(doc + "\n")
}
}
f.WriteString("\n")
}
}(mod, docPath, v)

View File

@ -8,41 +8,7 @@ menu:
---
## Introduction
Commander is a library for writing custom commands in Lua.
In order to make it easier to write commands for Hilbish,
not require separate scripts and to be able to use in a config,
the Commander library exists. This is like a very simple wrapper
that works with Hilbish for writing commands. Example:
```lua
local commander = require 'commander'
commander.register('hello', function(args, sinks)
sinks.out:writeln 'Hello world!'
end)
```
In this example, a command with the name of `hello` is created
that will print `Hello world!` to output. One question you may
have is: What is the `sinks` parameter?
A sink is a writable/readable pipe, or you can imagine a Lua
file. It's used in this case to write to the proper output,
incase a user either pipes to another command or redirects somewhere else.
So, the `sinks` parameter is a table containing 3 sinks:
`in`, `out`, and `err`.
- `in` is the standard input. You can read from this sink
to get user input. (**This is currently unimplemented.**)
- `out` is standard output. This is usually where text meant for
output should go.
- `err` is standard error. This sink is for writing errors, as the
name would suggest.
A sink has 2 methods:
- `write(str)` will write to the sink.
- `writeln(str)` will write to the sink with a newline at the end.
## Functions
### deregister(name)

View File

@ -35,7 +35,7 @@ replacing <cmd> with the name of the command (for example `command.git`).
`cb` must be a function that returns a table of "completion groups."
Check `doc completions` for more information.
### cwd() -> string
### cwd()
Returns the current directory of the shell
### exec(cmd)
@ -60,9 +60,9 @@ override this function with your custom handler.
### inputMode(mode)
Sets the input mode for Hilbish's line reader. Accepts either emacs or vim
### interval(cb, time) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;">Timer</a>
### interval(cb, time)
Runs the `cb` function every `time` milliseconds.
This creates a timer that starts immediately.
Returns a `timer` object (see `doc timers`).
### multiprompt(str)
Changes the continued line prompt to `str`
@ -95,11 +95,10 @@ Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua)
sh, and lua. It also accepts a function, to which if it is passed one
will call it to execute user input instead.
### timeout(cb, time) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;">Timer</a>
Runs the `cb` function after `time` in milliseconds.
This creates a timer that starts immediately.
### timeout(cb, time)
Runs the `cb` function after `time` in milliseconds
Returns a `timer` object (see `doc timers`).
### which(name) -> string
Checks if `name` is a valid command.
Will return the path of the binary, or a basename if it's a commander.
### which(name)
Checks if `name` is a valid command

View File

@ -17,8 +17,9 @@ This is an alias (ha) for the `hilbish.alias` function.
### delete(name)
Removes an alias.
### list() -> table\<string, string>
### list() -> aliases (table)
Get a table of all aliases, with string keys as the alias and the value as the command.
@returns table<string, string>
### resolve(alias) -> command (string)
Tries to resolve an alias to its command.

View File

@ -14,26 +14,7 @@ Manage interactive jobs in Hilbish via Lua.
Jobs are the name of background tasks/commands. A job can be started via
interactive usage or with the functions defined below for use in external runners.
## Functions
### add(cmdstr, args, execPath)
Adds a new job to the job table. Note that this does not immediately run it.
### all() -> table\<<a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;">Job</a>>
Returns a table of all job objects.
### disown(id)
Disowns a job. This deletes it from the job table.
### get(id) -> <a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;">Job</a>
Get a job object via its ID.
### last() -> <a href="/Hilbish/docs/api/hilbish/hilbish.jobs/#job" style="text-decoration: none;">Job</a>
Returns the last added job from the table.
## Types
## Job
The Job type describes a Hilbish job.
### Properties
## Object properties
- `cmd`: The user entered command string for the job.
- `running`: Whether the job is running or not.
- `id`: The ID of the job in the job table
@ -42,17 +23,32 @@ The Job type describes a Hilbish 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()
## Functions
### background()
Puts a job in the background. This acts the same as initially running a job.
#### foreground()
### 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()
### start()
Starts running the job.
#### stop()
### stop()
Stops the job from running.
### add(cmdstr, args, execPath)
Adds a new job to the job table. Note that this does not immediately run it.
### all() -> jobs (table<Job/Table>)
Returns a table of all job objects.
### disown(id)
Disowns a job. This deletes it from the job table.
### get(id) -> job (Job/Table)
Get a job object via its ID.
### last() -> job (Job/Table)
Returns the last added job from the table.

View File

@ -8,52 +8,30 @@ menu:
---
## Introduction
If you ever want to run a piece of code on a timed interval, or want to wait
a few seconds, you don't have to rely on timing tricks, as Hilbish has a
timer API to set intervals and timeouts.
These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc
accessible with `doc hilbish`). But if you want slightly more control over
them, there is the `hilbish.timers` interface. It allows you to get
a timer via ID and control them.
All functions documented with the `Timer` type refer to a Timer object.
An example of usage:
```
local t = hilbish.timers.create(hilbish.timers.TIMEOUT, 5000, function()
print 'hello!'
end)
t:start()
print(t.running) // true
```
The timers interface si one to easily set timeouts and intervals
to run functions after a certain time or repeatedly without using
odd tricks.
## Interface fields
- `INTERVAL`: Constant for an interval timer type
- `TIMEOUT`: Constant for a timeout timer type
## Functions
### create(type, time, callback) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;">Timer</a>
Creates a timer that runs based on the specified `time` in milliseconds.
The `type` can either be `hilbish.timers.INTERVAL` or `hilbish.timers.TIMEOUT`
### get(id) -> <a href="/Hilbish/docs/api/hilbish/hilbish.timers/#timer" style="text-decoration: none;">Timer</a>
Retrieves a timer via its ID.
## Types
## Timer
The Job type describes a Hilbish timer.
### Properties
## Object properties
- `type`: What type of timer it is
- `running`: If the timer is running
- `duration`: The duration in milliseconds that the timer will run
### Methods
#### start()
## Functions
### start()
Starts a timer.
#### stop()
### stop()
Stops a timer.
### create(type, time, callback)
Creates a timer that runs based on the specified `time` in milliseconds.
The `type` can either be `hilbish.timers.INTERVAL` or `hilbish.timers.TIMEOUT`
### get(id) -> timer (Timer/Table)
Retrieves a timer via its ID.

1
docs/api/index.md Normal file
View File

@ -0,0 +1 @@
hello!

View File

@ -3,5 +3,5 @@
+ `command.not-found` -> cmdStr > Thrown when a command is not found.
+ `command.not-executable` -> cmdStr > Thrown when Hilbish attempts to run a file
that is not executable.
+ `command.no-perm` -> cmdStr > Thrown when Hilbish attempts to execute a file but
has no permission.

View File

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

View File

@ -63,7 +63,6 @@ function hilbish.appendPath(dir) end
function hilbish.complete(scope, cb) end
--- Returns the current directory of the shell
--- @returns string
function hilbish.cwd() end
--- Replaces running hilbish with `cmd`
@ -95,10 +94,10 @@ function hilbish.hinter(line, pos) end
function hilbish.inputMode(mode) end
--- Runs the `cb` function every `time` milliseconds.
--- This creates a timer that starts immediately.
--- Returns a `timer` object (see `doc timers`).
--- @param cb function
--- @param time number
--- @return Timer
--- @return table
function hilbish.interval(cb, time) end
--- Changes the continued line prompt to `str`
@ -142,17 +141,15 @@ function hilbish.run(cmd, returnOut) end
--- @param mode string|function
function hilbish.runnerMode(mode) end
--- Runs the `cb` function after `time` in milliseconds.
--- This creates a timer that starts immediately.
--- Runs the `cb` function after `time` in milliseconds
--- Returns a `timer` object (see `doc timers`).
--- @param cb function
--- @param time number
--- @returns Timer
--- @returns table
function hilbish.timeout(cb, time) end
--- Checks if `name` is a valid command.
--- Will return the path of the binary, or a basename if it's a commander.
--- Checks if `name` is a valid command
--- @param name string
--- @returns string
function hilbish.which(name) end
--- Puts a job in the background. This acts the same as initially running a job.
@ -183,7 +180,7 @@ function hilbish.runner.lua(cmd) end
function hilbish.jobs:start() end
--- Stops the job from running.
function hilbish.jobs:stop() end
function hilbish.jobs.stop() end
--- Runs a command in Hilbish's shell script interpreter.
--- This is the equivalent of using `source`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,40 +1,5 @@
// library for custom commands
/*
Commander is a library for writing custom commands in Lua.
In order to make it easier to write commands for Hilbish,
not require separate scripts and to be able to use in a config,
the Commander library exists. This is like a very simple wrapper
that works with Hilbish for writing commands. Example:
```lua
local commander = require 'commander'
commander.register('hello', function(args, sinks)
sinks.out:writeln 'Hello world!'
end)
```
In this example, a command with the name of `hello` is created
that will print `Hello world!` to output. One question you may
have is: What is the `sinks` parameter?
A sink is a writable/readable pipe, or you can imagine a Lua
file. It's used in this case to write to the proper output,
incase a user either pipes to another command or redirects somewhere else.
So, the `sinks` parameter is a table containing 3 sinks:
`in`, `out`, and `err`.
- `in` is the standard input. You can read from this sink
to get user input. (**This is currently unimplemented.**)
- `out` is standard output. This is usually where text meant for
output should go.
- `err` is standard error. This sink is for writing errors, as the
name would suggest.
A sink has 2 methods:
- `write(str)` will write to the sink.
- `writeln(str)` will write to the sink with a newline at the end.
*/
// Commander is a library for writing custom commands in Lua.
package commander
import (

23
job.go
View File

@ -18,15 +18,6 @@ import (
var jobs *jobHandler
var jobMetaKey = rt.StringValue("hshjob")
// #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
@ -144,7 +135,6 @@ func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// #interface jobs
// #member
// stop()
// Stops the job from running.
func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -303,6 +293,13 @@ func (j *jobHandler) stopAll() {
}
// #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.
// background job management
/*
Manage interactive jobs in Hilbish via Lua.
@ -387,7 +384,7 @@ func jobUserData(j *job) *rt.UserData {
}
// #interface jobs
// get(id) -> @Job
// get(id) -> job (Job/Table)
// Get a job object via its ID.
// --- @param id number
// --- @returns Job
@ -447,7 +444,7 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// #interface jobs
// all() -> table<@Job>
// all() -> jobs (table<Job/Table>)
// Returns a table of all job objects.
// --- @returns table<Job>
func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -484,7 +481,7 @@ func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// #interface jobs
// last() -> @Job
// last() -> job (Job/Table)
// Returns the last added job from the table.
// --- @returns Job
func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {

View File

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

View File

@ -5,7 +5,7 @@ commander.register('cat', function(args, sinks)
local exit = 0
if #args == 0 then
sinks.out:writeln [[
print [[
usage: cat [file]...]]
end
@ -13,11 +13,11 @@ usage: cat [file]...]]
local f = io.open(fName)
if f == nil then
exit = 1
sinks.out:writeln(string.format('cat: %s: no such file or directory', fName))
print(string.format('cat: %s: no such file or directory', fName))
goto continue
end
sinks.out:writeln(f:read '*a')
sinks.out:write(f:read '*a')
::continue::
end
io.flush()

View File

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

View File

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

View File

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

View File

@ -4,27 +4,12 @@ local lunacolors = require 'lunacolors'
commander.register('doc', function(args, sinks)
local moddocPath = hilbish.dataDir .. '/docs/'
local stat = fs.stat '.git/refs/heads/extended-job-api'
if stat then
-- hilbish git
moddocPath = './docs/'
end
local apidocHeader = [[
# %s
{grayBg} {white}{italic}%s {reset}
]]
local modules = table.map(fs.readdir(moddocPath), function(f)
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', '')))
end)
local doc = [[
Welcome to Hilbish's documentation viewer! Here you can find
documentation for builtin functions and other things related
to Hilbish.
Usage: doc <section> [subdoc]
Available sections: ]] .. table.concat(modules, ', ')
if #args > 0 then
local mod = args[1]
@ -39,21 +24,18 @@ Available sections: ]] .. table.concat(modules, ', ')
subdocName = '_index'
end
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
if not f then
f = io.open(moddocPath .. subdocName:match '%w+' .. '/' .. subdocName .. '.md', 'rb')
end
if not f then
moddocPath = moddocPath .. subdocName .. '/'
subdocName = args[3] or '_index'
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
end
if not f then
sinks.out:writeln('No documentation found for ' .. mod .. '.')
return 1
sinks.out:write('No documentation found for ' .. mod .. '.')
return
end
end
funcdocs = f:read '*a':gsub('-([%d]+)', '%1')
local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= '_index.md' or f ~= 'index.md' end)
local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= '_index.md' end)
local subdocs = table.map(moddocs, function(fname)
return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.md', '')))
end)
@ -81,20 +63,34 @@ Available sections: ]] .. table.concat(modules, ', ')
if mod == 'api' then
funcdocs = string.format(apidocHeader, vals.title, vals.description or 'no description.') .. funcdocs
end
doc = funcdocs:sub(1, #funcdocs - 1)
local backtickOccurence = 0
local formattedFuncs = lunacolors.format(funcdocs:sub(1, #funcdocs - 1):gsub('`', function()
backtickOccurence = backtickOccurence + 1
if backtickOccurence % 2 == 0 then
return '{reset}'
else
return '{underline}{green}'
end
end):gsub('#+.-\n', function(t)
return '{bold}{magenta}' .. t .. '{reset}'
end))
sinks.out:write(formattedFuncs)
f:close()
end
local backtickOccurence = 0
sinks.out:writeln(lunacolors.format(doc:gsub('`', function()
backtickOccurence = backtickOccurence + 1
if backtickOccurence % 2 == 0 then
return '{reset}'
else
return '{underline}{green}'
end
end):gsub('#+.-\n', function(t)
local signature = t:gsub('<.->(.-)</.->', '{underline}%1'):gsub('\\', '<')
return '{bold}{yellow}' .. signature .. '{reset}'
end)))
return
end
local modules = table.map(fs.readdir(moddocPath), function(f)
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', '')))
end)
sinks.out:write [[
Welcome to Hilbish's doc tool! Here you can find documentation for builtin
functions and other things.
Usage: doc <section> [subdoc]
A section is a module or a literal section and a subdoc is a subsection for it.
Available sections: ]]
sinks.out:write(table.concat(modules, ', ') .. '\n')
end)

View File

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

View File

@ -67,7 +67,7 @@ do
end
bait.catch('error', function(event, handler, err)
print(string.format('Encountered an error in %s handler\n%s', event, err:sub(8)))
bait.release(event, handler)
end)
bait.catch('command.not-found', function(cmd)

View File

@ -75,12 +75,6 @@ function hilbish.runner.setCurrent(name)
hilbish.runner.setMode(r.run)
end
--- Returns the current runner by name.
--- @returns string
function hilbish.runner.getCurrent()
return currentRunner
end
hilbish.runner.add('hybrid', function(input)
local cmdStr = hilbish.aliases.resolve(input)

20
sink.go
View File

@ -25,7 +25,6 @@ func setupSinkType(rtm *rt.Runtime) {
sinkMethods := rt.NewTable()
sinkFuncs := map[string]util.LuaExport{
"write": {luaSinkWrite, 2, false},
"writeln": {luaSinkWriteln, 2, false},
}
util.SetExports(l, sinkMethods, sinkFuncs)
@ -59,25 +58,6 @@ func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func luaSinkWriteln(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
}
s, err := sinkArg(c, 0)
if err != nil {
return nil, err
}
data, err := c.StringArg(1)
if err != nil {
return nil, err
}
s.writer.Write([]byte(data + "\n"))
return c.Next(), nil
}
func newSinkInput(r io.Reader) *sink {
s := &sink{
reader: r,

View File

@ -15,11 +15,6 @@ const (
timerTimeout
)
// #interface timers
// #property type What type of timer it is
// #property running If the timer is running
// #property duration The duration in milliseconds that the timer will run
// The Job type describes a Hilbish timer.
type timer struct{
id int
typ timerType

View File

@ -62,7 +62,7 @@ func (th *timersModule) get(id int) *timer {
}
// #interface timers
// create(type, time, callback) -> @Timer
// create(type, time, callback)
// Creates a timer that runs based on the specified `time` in milliseconds.
// The `type` can either be `hilbish.timers.INTERVAL` or `hilbish.timers.TIMEOUT`
// --- @param type number
@ -91,7 +91,7 @@ func (th *timersModule) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// #interface timers
// get(id) -> @Timer
// get(id) -> timer (Timer/Table)
// Retrieves a timer via its ID.
// --- @param id number
// --- @returns Timer
@ -115,30 +115,13 @@ func (th *timersModule) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// #interface timers
// #field INTERVAL Constant for an interval timer type
// #field TIMEOUT Constant for a timeout timer type
// #property type What type of timer it is
// #property running If the timer is running
// #property duration The duration in milliseconds that the timer will run
// timeout and interval API
/*
If you ever want to run a piece of code on a timed interval, or want to wait
a few seconds, you don't have to rely on timing tricks, as Hilbish has a
timer API to set intervals and timeouts.
These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc
accessible with `doc hilbish`). But if you want slightly more control over
them, there is the `hilbish.timers` interface. It allows you to get
a timer via ID and control them.
## Timer Object
All functions documented with the `Timer` type refer to a Timer object.
An example of usage:
```
local t = hilbish.timers.create(hilbish.timers.TIMEOUT, 5000, function()
print 'hello!'
end)
t:start()
print(t.running) // true
```
*/
// The timers interface si one to easily set timeouts and intervals
// to run functions after a certain time or repeatedly without using
// odd tricks.
func (th *timersModule) loader(rtm *rt.Runtime) *rt.Table {
timerMethods := rt.NewTable()
timerFuncs := map[string]util.LuaExport{

View File

@ -11,7 +11,7 @@ var (
// Version info
var (
ver = "v2.1.0"
ver = "v2.0.0"
releaseName = "Hibiscus"
gitCommit string
gitBranch string

0
website/.hugo_build.lock Normal file
View File

View File

@ -20,16 +20,6 @@ enableGitInfo = true
name = 'Docs'
pageref = '/docs'
weight = 3
[[menu.nav]]
identifier = 'blog'
name = 'Blog'
pageref = '/blog'
weight = 4
[markup.goldmark.renderer]
unsafe = true
[author]
[author.sammyette]
name = 'sammyette'
picture = 'https://avatars1.githubusercontent.com/u/38820196?s=460&u=b9f4efb2375bae6cb30656d790c6e0a2939327c0&v=4'

View File

@ -1,114 +0,0 @@
---
title: "Hilbish v2.0 Release"
date: 2022-12-29T01:55:21+00:00
---
Hilbish v2.0 has been released!
Well actually, it was released a week ago, but I only wrote this
Hilbish blog *after* that.
This is a **big** release, coming 9 months after the previous v1.2.0 and
featuring over 40+ bug fixes and tons of new features and enhancements, so
let's see what is in this release.
# Documentation
When querying about the problems people have with Hilbish, one of the
issues was its poor documentation. Hilbish had plain text, autogenerated
documentation which only covered the module functions (bait, hilbish,
commander, etc.) and did not include the interfaces (`hilbish.timers`,
`hilbish.jobs` and all that).
I have tried to improve this by working on documenting all the
interfaces (except for some functions of `hilbish.runner`, that's hard to do)
and made the documentation markdown for use on this website. This means
that users can look at documentation here or with the `doc` command.
Hopefully this addresses documentation complaints, and if not, please open an issue.
# Main Bug Fixes
As this is a piece of software with no unit testing that is maintained by me alone,
there is gonna be either some bug or something that I overlooked when
making a change. I make a lot of mistakes. There's also the other fact that
sometimes there's just bugs for any other reasosn. Good thing I fixed
more than 40 of those bugs in this release!
## Readline Bug Fixes
The pure Go readline library is good in some ways and bad in others.
A good portion of the bug fixes are for the readline library, and also
related to text input with east asian characters and the like (Korean, Japanese,
etc.)
A few of the fixes (and additions) include:
- Fixing various crashes, including when there is a "stray" newline at the end of text
- Grid completion menu causing spam and duplicate text when there are items longer than
the terminal and/or contain Japanese or other characters.
- Cursor positioning with CJK characters
- Adding new keybinds and fixing others
## Other fixes
There are a lot more fixes, even more than the ones listed here, but these are the main ones:
- Don't put alias expanded command in history (I've fixed this 5 times now....)
- Handle stdin being nonblocking
- Completion related fixes, like showing the full name, completing files with spaces
# Breaking changes
This release is a major version bump not only because there are tons of fixes, but because
there are breaking changes. This means that there are some changes done which would
cause errors with an old user config (breaking).
## Lua 5.4
The most important is the use of a new Lua VM library. Previously, Hilbish
used gopher-lua, which implements Lua 5.1. This has been changed to
[golua](https://github.com/arnodel/golua/), which implements Lua 5.4.
Moving from 5.1 to 5.4 does have breaking changes even if it doesn't seem like it,
and since these are different Lua implementations, there may be some differences there too.
## Userdata
Previously, objects such as jobs or timers were represented by tables.
This has been changed to userdata to make more sense.
## Other changes
Runner functions are now required to return a table.
It can (at the moment) have 4 variables:
- `input` (user input)
- `exitCode` (exit code)
- `error` (error message)
- `continue` (whether to prompt for more input)
User input has been added to the return to account for runners wanting to
prompt for continued input, and to add it properly to history. `continue`
got added so that it would be easier for runners to get continued input
without having to actually handle it at all.
The MacOS config paths now match Linux, since it makes more sense for
a program like Hilbish.
The Hilbish greeting is now an *opt*, and is printed by default.
# Feature Additions
Besides fixes and changes, this release also includes a good portion of
new features! Users can now add handlers for syntax highlighting and
inline hinting.
Some new hooks have been added, like `hilbish.cancel` and `hilbish.init`.
You can look at all the hooks via the `doc hooks` command
Job management functions have also been added. You can now put jobs in the
foreground/background and disown them via the expected commands and also
via the Lua API.
The `hilbish.timers` API interface was also added in this release!
# Closing Off
Hilbish has gone from something small and simple for myself to a slightly
advanced shell with a decent amount of features, and a few users. It
still hasn't reached levels of other alt shells in regards to literally
everything, but the goal is to get there!
If you want to check the FULL changelog, you can [do so here.](https://github.com/Rosettea/Hilbish/releases/tag/v2.0.0)
This v2.0 release marks an advancement in Hilbish (and also how long
one of my projects hasn't died) and I hope it can advance even further.
Thanks for reading, and I'll be back for the v2.1 release notes, or maybe
something else in between.

View File

@ -1,6 +0,0 @@
---
title: "Welcome to the Hilbish blog"
---
Hello! Welcome to the Hilbish blog. This will mainly contain release
announcements and some other things relating to Hilbish (development).

View File

@ -1,11 +1,6 @@
<h{{ (add .Level 1) }} id="{{ .Anchor | safeURL }}" class="heading">
<h{{ (add .Level 1) }} id="{{ .Anchor | safeURL }}">
{{ .Text | safeHTML }}
<a href="#{{ .Anchor | safeURL }}" class='heading-link'>
<i class="fas fa-paperclip"></i>
</a>
</h{{ (add .Level 1) }}>
{{ if eq .Text ""}}
<hr>
<hr>
{{ end }}

View File

@ -1 +1,4 @@
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if eq (substr .Destination 0 4) "http" }} target="_blank" rel="noopener"{{ end }}>{{ .Text | safeHTML }}</a>
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if eq (substr .Destination 0 4) "http" }} target="_blank" rel="noopener"{{ end }}>
{{ .Text | safeHTML }}
</a>

View File

@ -1,21 +0,0 @@
{{ define "main" }}
<main>
<div class="row row-cols-1 row-cols-md-1 g-4">
{{ range where .Site.RegularPages "Section" "in" "blog" }}
<div class="col d-flex justify-content-center">
<div class="card" style="width: 56rem;">
<div class="card-body">
<a href="{{ .Permalink }}"><h5 class="card-title">{{ .Title }}</h5></a>
<h6 class='card-subtitle text-muted mb-2'>
{{- if isset .Params "date" -}}
<time>{{ .Date.Format "January 2, 2006" }}</time>
{{- end -}}
</h6>
<p class="card-text">{{if .Description}}{{ .Description }}{{ else }}{{ .Summary }}{{ end }}</p>
</div>
</div>
</div>
{{- end }}
</div>
</main>
{{ end }}

View File

@ -2,16 +2,7 @@
<main>
<div class="container mt-2">
<h1>{{ .Title }}</h1>
<img src='{{ .Site.Author.sammyette.picture }}' width=48 style='border-radius: 100%'>
<em class='text-muted'>
by <strong>{{ .Site.Author.sammyette.name }}</strong>
{{- if isset .Params "date" -}}
<time> // {{ .Date.Format "January 2, 2006" }}</time>
{{- end -}}
</em>
<div class='my-4'>
{{.Content}}
</div>
{{.Content}}
</div>
</main>
{{ end }}

View File

@ -1,7 +1,7 @@
<head>
{{ $title := print .Title " — " .Site.Title }}
{{ if .IsHome }}{{ $title = .Site.Title }}{{ end }}
<title>{{ $title }}</title>
{{ if .IsHome }}{{ $title = .Site.Title }}{{ end }}
<title>{{ $title }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"/>
@ -23,16 +23,4 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
.heading > .heading-link {
opacity: 0
}
.heading:hover > .heading-link {
visibility: visible;
opacity: 1;
transition: all .1s ease-in;
}
</style>
</head>