keg

package module
v0.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 22, 2022 License: Apache-2.0 Imports: 29 Imported by: 3

README

🌳 KEG Commands

GoDoc License

This keg Bonzai branch contains all KEG related commands, most of which are exported so they can be composed individually if preferred.

Install

You can just download from the releases page.

curl -L https://github.com/rwxrob/keg/releases/latest/download/keg-linux-amd64 -o ~/.local/bin/keg
curl -L https://github.com/rwxrob/keg/releases/latest/download/keg-darwin-amd64 -o ~/.local/bin/keg
curl -L https://github.com/rwxrob/keg/releases/latest/download/keg-darwin-arm64 -o ~/.local/bin/keg
curl -L https://github.com/rwxrob/keg/releases/latest/download/keg-windows-amd64 -o ~/.local/bin/keg

Or with go:

go install github.com/rwxrob/keg/cmd/keg@latest

You might want to create a small script to encapsulate KEG_CURRENT rather than changing into the directory all the time. Note that aliases and functions do not reliably work from within vim, only executables (which is 80% of the reason to use keg in the first place).

#!/bin/bash
KEG_CURRENT=zet keg "$@"

Composed

package z

import (
	Z "github.com/rwxrob/bonzai/z"
	"github.com/rwxrob/keg"
)

var Cmd = &Z.Cmd{
	Name:     `z`,
	Commands: []*Z.Cmd{help.Cmd, keg.Cmd},
}

Tab Completion

To activate bash completion just use the complete -C option from your .bashrc or command line. There is no messy sourcing required. All the completion is done by the program itself.

complete -C keg keg

If you don't have bash or tab completion check use the shortcut commands instead.

Embedded Documentation

All documentation (like manual pages) has been embedded into the source code of the application. See the source or run the program with help to access it.

Command Line Usage

keg help

Configuration

map - map of all local keg ids pointing to their directories (like PATH)

Variables

current - current keg from map

Build and Release Instructions

Building workflow uses the good Go helper tool (often composited into bonzai personal command trees (z go):

cd cmd/keg
good build
gh release create
gh release upload TAGVER build/*

Documentation

Index

Examples

Constants

View Source
const IsoDateExpStr = `\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\dZ`
View Source
const IsoDateFmt = `2006-01-02 15:04:05Z`

Variables

View Source
var ChangesDefault = 5
View Source
var Cmd = &Z.Cmd{
	Name:        `keg`,
	Aliases:     []string{`kn`},
	Version:     `v0.9.2`,
	UseVars:     true,
	Copyright:   `Copyright 2022 Robert S Muhlestein`,
	License:     `Apache-2.0`,
	Site:        `rwxrob.tv`,
	Source:      `git@github.com:rwxrob/keg.git`,
	Issues:      `https://github.com/rwxrob/keg/issues`,
	ConfVars:    true,
	Summary:     help.S(_keg),
	Description: help.D(_keg),

	Commands: []*Z.Cmd{
		editCmd, help.Cmd, conf.Cmd, vars.Cmd,
		indexCmd, createCmd, currentCmd, directoryCmd, deleteCmd,
		lastCmd, changesCmd, titlesCmd, initCmd, randomCmd,
		importCmd, grepCmd, viewCmd, columnsCmd, linkCmd, tagCmd,
	},

	Shortcuts: Z.ArgMap{
		`set`:    {`var`, `set`},
		`unset`:  {`var`, `unset`},
		`get`:    {`var`, `get`},
		`sample`: {`create`, `sample`},
	},
}
View Source
var DefColumns = 100
View Source
var LatestDexEntryExp = regexp.MustCompile(
	`^\* (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\dZ) \[(.*)\]\(\.\./(\d+)\)$`,
)
View Source
var NodePaths = _fs.IntDirs

NodePaths returns a list of node directory paths contained in the keg root directory path passed. Paths returns are fully qualified and cleaned. Only directories with valid integer node IDs will be recognized. Empty slice is returned if kegroot doesn't point to directory containing node directories with integer names.

The lowest and highest integer names are returned as well to help determine what to name a new directory.

File and directories that do not have an integer name will be ignored.

View Source
var SampleNodeReadme string

Functions

func DexRemove added in v0.8.1

func DexRemove(kegpath string, entry *DexEntry) error

DexRemove removes an entry without changing the current sort order of dex/changes.md and calls WriteDex without a ScanDex.

func DexUpdate added in v0.4.0

func DexUpdate(kegpath string, entry *DexEntry) error

DexUpdate first checks the keg at kegpath for an existing dex/changes.md file and if found loads it, if not, MakeDex is called to create it. Then DexUpdate examines the Dex for the DexEntry passed and if found updates it with the new information, otherwise, it will add the new entry without any further validation and call WriteDex create the dex files and update keg file.

func Edit added in v0.4.0

func Edit(kegpath string, id int) error

Edit calls file.Edit on the given node README.md file within the given kegpath.

func GrepTags added in v0.9.0

func GrepTags(kegdir, tags string) (string, error)

GrepTags returns all the lines from dex/tags with any of the tags listed (separated by comma).

Example
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {
	str, err := keg.GrepTags(`testdata/samplekeg`, `foo`)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(str)
}
Output:

foo 2 6 3
Example (Multiple)
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {
	str, err := keg.GrepTags(`testdata/samplekeg`, `foo,bar`)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(str)
}
Output:

foo 2 6 3
bar 8

func HaveDex added in v0.4.0

func HaveDex(kegpath string) bool

HaveDex returns true if keg at kegpath has a dex/changes.md file.

Example
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {
	fmt.Println(keg.HaveDex(`testdata/samplekeg`))
	fmt.Println(keg.HaveDex(`testdata/nothing`))
}
Output:

true
false

func Import added in v0.6.0

func Import(kegpath string, targets ...string) error

Import imports the targets into the kegpath creating new, unique identifiers for each. If the target ends with an integer it is assumed to be a node directory. If not, it is assumed to contain node directories with integer identifiers. Currently, there is no resolution of any links contained within any node README.md file.

func ImportNode

func ImportNode(kegpath, target string) error

ImportNode imports a single specific directory into the kegpath by getting the next integer identifier and moving the target into the kegpath with an os.Rename (which has limitations based on the host operating system's handling of cross-file system boundaries).

func MakeDex

func MakeDex(kegdir string) error

MakeDex calls ScanDex and writes (or overwrites) the output to the reserved dex node file within the kegdir passed. File-level locking is attempted using the go-internal/lockedfile (used by Go itself). Both a friendly markdown file reverse sorted by time of last update (changes.md) and a tab-delimited file sorted numerically by node ID (nodes.tsv) are created. Any empty content node directory is automatically removed. Empty is defined to be one that only contains 0-length files, recursively.

func Publish added in v0.2.0

func Publish(kegpath string) error

Publish publishes the keg at kegpath location to its distribution targets listed in the keg file under "publish." Currently, this only involves looking for a .git directory and if found doing a git pull/add/commit/push. Git commit messages are always based on the latest node title without any verb.

func Tag added in v0.9.0

func Tag(kegdir, id, tags string) error

Tag will add the id specified to the dex/tags file, one entry for each line containing one of the comma-separated tags. The rwxrob/fs/file.Overwrite function is used preventing concurrent writes to the file, but it is possible that the file could change by another process before the Overwrite takes place. Therefore, other measures to preserve transactional integrity should be taken where needed. If the dex/tags file does not exist will create it.

func Tags added in v0.9.0

func Tags(kegdir string) string

Tags returns a space separated string with all the tags currently in use (even if no nodes yet assigned).

func UpdateUpdated

func UpdateUpdated(kegpath string) error

UpdateUpdated sets the updated YAML field in the keg info file.

func Updated

func Updated(kegpath string) (*time.Time, error)

Updated parses the most recent change time in the dex/node.md file (the first line) and returns the time stamp it contains as a time.Time. If a time stamp could not be determined returns time.

func UpdatedString

func UpdatedString(kegpath string) string

UpdatedString returns Updated time as a string or an empty string if there is a error.

Example
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {
	fmt.Println(keg.UpdatedString(`testdata/samplekeg`))
}
Output:

2022-11-26 19:33:24Z

func WriteDex added in v0.4.0

func WriteDex(kegpath string, dex *Dex) error

WriteDex writes the dex/changes.md and dex/nodes.tsv files to the keg at kegpath and calls UpdateUpdated to keep keg info file in sync.

func WriteSample added in v0.4.1

func WriteSample(kegpath string, entry *DexEntry) error

WriteSample writes the embedded SampleNodeReadme to the entry indicated in the keg specified by kegpath.

Types

type Dex

type Dex []*DexEntry

Dex is a collection of DexEntry structs. This allows mapping methods for its serialization to different output formats.

Example (Json)
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/rwxrob/keg"
)

func main() {
	date := time.Date(2022, 12, 10, 6, 10, 4, 0, time.UTC)
	d := keg.DexEntry{U: date, N: 2, T: `Some title`}
	byt, err := json.Marshal(d)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(byt))
}
Output:

{"U":"2022-12-10T06:10:04Z","T":"Some title","N":2,"HBeg":0,"HEnd":0}
Example (String)
package main

import (
	"fmt"
	"time"

	"github.com/rwxrob/keg"
)

func main() {
	date := time.Date(2022, 12, 10, 6, 10, 4, 0, time.UTC)
	d := keg.DexEntry{U: date, N: 2, T: `Some title`}
	fmt.Println(d)
	fmt.Println(d.MD())
}
Output:

* 2022-12-10 06:10:04Z [Some title](../2)
* 2022-12-10 06:10:04Z [Some title](../2)
Example (Tsv)
package main

import (
	"fmt"
	"time"

	"github.com/rwxrob/keg"
)

func main() {
	date := time.Date(2022, 12, 10, 6, 10, 4, 0, time.UTC)
	d := keg.DexEntry{U: date, N: 2, T: `Some title`}
	fmt.Println(d.TSV())
}
Output:

2	2022-12-10 06:10:04Z	Some title

func ParseDex

func ParseDex(in any) (*Dex, error)

ParseDex parses any input valid for to.String into a Dex pointer. FIXME: replace regular expression with pegn.Scanner instead

func ReadDex

func ReadDex(kegdir string) (*Dex, error)

ReadDex reads an existing dex/changes.md dex and returns it.

func ScanDex

func ScanDex(kegdir string) (*Dex, error)

ScanDex takes the target path to a keg root directory returns a Dex object.

func (*Dex) Add added in v0.4.0

func (d *Dex) Add(entry *DexEntry)

Add appends the entry to the Dex.

func (Dex) AsIncludes

func (e Dex) AsIncludes() string

AsIncludes renders the entire Dex as a KEGML include list (markdown bulleted list) and cab be useful from within editing sessions to include from the current keg without leaving the terminal editor.

func (Dex) ByChanges added in v0.5.0

func (d Dex) ByChanges() Dex

ByChanges sorts the Dex from most recently changed to oldest. A pointer to self is returned for convenience.

func (Dex) ByID

func (d Dex) ByID() Dex

ByID sorts the Dex from lowest to highest node ID integer. A pointer to self is returned for convenience.

func (*Dex) ChooseWithTitleText added in v0.3.1

func (d *Dex) ChooseWithTitleText(key string) *DexEntry

ChooseWithTitleText returns a single *DexEntry for the keyword passed. If there are more than one then user is prompted to choose from list sent to the terminal.

func (*Dex) ChooseWithTitleTextExp added in v0.7.0

func (d *Dex) ChooseWithTitleTextExp(re *regexp.Regexp) *DexEntry

ChooseWithTitleTextExp returns a single *DexEntry for the regular expression matches passed. If there are more than one then user is prompted to choose from list sent to the terminal.

func (*Dex) Delete added in v0.8.1

func (d *Dex) Delete(entry *DexEntry)

Delete removes the specified entry shrinking the slice without changing the underlying size of the supporting array while maintaining references to each DexEntry. Note that the persisted content node directory may still exist. This method only affects the Dex itself. Stops at first match.

Example
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {
	one := &keg.DexEntry{N: 1, T: `One`}
	two := &keg.DexEntry{N: 2, T: `Two`}
	three := &keg.DexEntry{N: 3, T: `Three`}
	dex := &keg.Dex{one, two, three}
	dex.Delete(two)
	fmt.Println(dex.MD())
}
Output:

* 0001-01-01 00:00:00Z [One](../1)
* 0001-01-01 00:00:00Z [Three](../3)

func (Dex) Last added in v0.5.0

func (d Dex) Last() *DexEntry

Last returns the DexEntry with the highest integer value identifier.

func (Dex) LastChanged added in v0.5.0

func (d Dex) LastChanged() *DexEntry

LastChanged returns the highest integer value identifier.

func (Dex) LastChangedIdString added in v0.5.0

func (d Dex) LastChangedIdString() string

LastChangedIdString returns Last as string.

func (Dex) LastChangedIdWidth added in v0.5.0

func (d Dex) LastChangedIdWidth() int

LastChangedIdWidth returns width of Last integer identifier.

func (Dex) LastIdString added in v0.5.0

func (d Dex) LastIdString() string

LastIdString returns Last as string.

func (Dex) LastIdWidth added in v0.5.0

func (d Dex) LastIdWidth() int

LastIdWidth returns width of Last integer identifier.

func (Dex) Lookup added in v0.4.0

func (d Dex) Lookup(id int) *DexEntry

Lookup does a linear search through the Dex for one with the passed id and if found returns, otherwise returns nil.

func (Dex) MD

func (e Dex) MD() string

MD renders the entire Dex as a Markdown list suitable for the standard dex/changes.md file.

func (*Dex) MarshalJSON

func (d *Dex) MarshalJSON() ([]byte, error)

MarshalJSON produces JSON text that contains one DexEntry per line that has not been HTML escaped (unlike the default).

func (Dex) Pretty added in v0.2.3

func (d Dex) Pretty() string

Pretty returns a string with pretty color string with no time stamps rendered in more readable way.

func (Dex) PrettyLines added in v0.2.3

func (d Dex) PrettyLines() []string

PrettyLines returns Pretty but each line separate and without line return.

func (Dex) Random added in v0.4.0

func (d Dex) Random() *DexEntry

Random returns a random entry.

func (Dex) String

func (e Dex) String() string

String fulfills the fmt.Stringer interface as JSON. Any error returns a "null" string.

func (Dex) TSV

func (e Dex) TSV() string

TSV renders the entire Dex as a loadable tab-separated values file.

func (*Dex) WithTitleText

func (e *Dex) WithTitleText(keyword string) Dex

WithTitleText returns a new Dex from self with all nodes that do not contain the text substring in the title filtered out.

func (*Dex) WithTitleTextExp added in v0.7.0

func (e *Dex) WithTitleTextExp(re *regexp.Regexp) Dex

WithTitleTextExp returns a new Dex from self with all nodes that do not contain the regular expression matches in the title filtered out.

type DexEntry

type DexEntry struct {
	U    time.Time // updated
	T    string    // title
	N    int       // node id (also see ID)
	HBeg int       // start of highlighted
	HEnd int       // end of highlighted
}

DexEntry represents a single line in an index (usually the changes.md or nodes.tsv file). All three fields are always required.

func Last

func Last(kegpath string) *DexEntry

Last returns the last created content node. If cannot determine returns nil

Example
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {
	fmt.Println(keg.Last(`testdata/samplekeg`).N)
	fmt.Println(keg.Last(`testdata/noexist`))
}
Output:

12
<nil>

func LastChanged added in v0.5.0

func LastChanged(kegpath string) *DexEntry

LastChanged parses and returns a DexEntry of the most recently updated node from first line of the dex/changes.md file. If cannot determine returns nil.

func MakeNode added in v0.4.0

func MakeNode(kegpath string) (*DexEntry, error)

MakeNode examines the keg at kegpath for highest integer identifier and provides a new one returning a *DexEntry for it.

func Next added in v0.6.0

func Next(kegpath string) *DexEntry

Next returns a new DexEntry with its integer identify set to the next integer after Last and returns nil if cannot determine which is next. The updated time stamp is set to the current time even though the DexEntry may not have yet been written to disk and its time would be different from the actual time written. This is to save the overhead of grabbing it again once written.

Example
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {
	fmt.Println(keg.Next(`testdata/samplekeg`).N)
	fmt.Println(keg.Next(`testdata/noexist`))
}
Output:

13
<nil>

func (*DexEntry) AsInclude

func (e *DexEntry) AsInclude() string

Asinclude returns a KEGML include link list item without the time suitable for creating include blocks in node files.

func (*DexEntry) ID added in v0.2.0

func (e *DexEntry) ID() string

ID returns the node identifier as a string instead of an integer. Returns an empty string if unable to parse the integer.

func (*DexEntry) MD

func (e *DexEntry) MD() string

MD returns the entry as a single Markdown list item for inclusion in the dex/nodex.md file:

  1. Second last changed in UTC in ISO8601 (RFC3339)
  2. Current title (always first line of README.md)
  3. Unique node integer identifier

Note that the second of last change is based on *any* file within the node directory changing, not just the README.md or meta files.

func (*DexEntry) MarshalJSON

func (e *DexEntry) MarshalJSON() ([]byte, error)

MarshalJSON produces JSON text that contains one DexEntry per line that has not been HTML escaped (unlike the default) and that uses a consistent DateTime format. Note that the (broken) encoding/json encoder is not used at all.

func (*DexEntry) Pretty added in v0.5.0

func (e *DexEntry) Pretty() string

Pretty returns a string with pretty colors.

Example
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {

	e := keg.DexEntry{T: "Some"}
	fmt.Printf("%q\n", e.Pretty())
	e.HBeg = 2
	e.HEnd = 3
	fmt.Printf("%q\n", e.Pretty())

}
Output:

"\x1b[30m0001-01-01 00:00Z \x1b[32m0 \x1b[37mSome\x1b[0m\n"
"\x1b[30m0001-01-01 00:00Z \x1b[32m0 \x1b[37mSo\x1b[31mm\x1b[37me\x1b[0m\n"

func (DexEntry) String

func (e DexEntry) String() string

String implements fmt.Stringer interface as MD.

func (*DexEntry) TSV

func (e *DexEntry) TSV() string

func (*DexEntry) Update added in v0.4.0

func (e *DexEntry) Update(kegpath string) error

Update gets the entry for the target keg at kegpath by looking up the latest change to any file within it and parsing the title.

type Local

type Local struct {
	Name string
	Path string
}

Local contains a name to full path mapping for kegs stored locally.

type TagsMap added in v0.9.0

type TagsMap map[string][]string

func ReadTags added in v0.9.0

func ReadTags(kegdir string) (TagsMap, error)

ReadTags reads an existing dex/tags files within the target keg directory. The tags file must have one tag group per line with the tag being the first field. Fields are separated by a single space for the most performant parsing possible.

Example
package main

import (
	"fmt"

	"github.com/rwxrob/keg"
)

func main() {
	tags, err := keg.ReadTags(`testdata/samplekeg`)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(tags[`foo`][1])
}
Output:

6

func (TagsMap) MarshalText added in v0.9.0

func (tl TagsMap) MarshalText() ([]byte, error)

func (TagsMap) String added in v0.9.0

func (tl TagsMap) String() string

func (TagsMap) UnmarshalText added in v0.9.0

func (tl TagsMap) UnmarshalText(buf []byte) error

UnmarshalText parses the tag lines items from the bytes buffer and sets the key pair for that tag to the values overwriting any that were already set.

func (TagsMap) Write added in v0.9.0

func (tl TagsMap) Write(path string) error

Write writes the marshaled text of a TagsMap to the file at path.

Directories

Path Synopsis
cmd
keg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL