Documentation
¶
Overview ¶
Package nork is Nodes with ORdered Kids - a way to create a hierarchical tree of nodes, where the nodes are ordered and keep their order. It does not need or use Go generics.
Node order is of course important for markup in general and XML mixedcontent in particular, while unimportant when XML is used purely for data records.
interface Norker is implemented not for type Nork but rather for `*Nork´. This is so that nodes are shared-writable.
nork is a Node with Ordered Kids. It stores basic bidirectional parent/kid relationships, plus other useful hierarchy-related info like levels and paths.
interface Norker is implemented not for struct Nork but rather for the pointer, i.e. `*Nork´. This makes nodes writable and also sharable.
Index ¶
- func DebugG(p *Nork) string
- func EchoG(p *Nork) string
- func Example()
- func InfosG(p *Nork) string
- func NewFSOTreeNorkFactory(aRootPath string) (*FSOTreeNorkFactory, *FSONork, error)
- type Cntyer
- type FSONork
- type FSOTreeNorkFactory
- type InspectorFunc
- type Nork
- func (p *Nork) AddKid(aKid *Nork) *Nork
- func (p *Nork) AddKids(rKids []*Nork) *Nork
- func (p *Nork) CreatPath() string
- func (p *Nork) Debug() string
- func (p *Nork) Echo() string
- func (p *Nork) FirstKid() *Nork
- func (p *Nork) HasKids() bool
- func (p *Nork) Infos() string
- func (p *Nork) KidsAsSlice() []*Nork
- func (p *Nork) LastKid() *Nork
- func (p *Nork) Level() int
- func (p *Nork) LinePrefixString() string
- func (p *Nork) NextPeer() *Nork
- func (p *Nork) Parent() *Nork
- func (p *Nork) PrevPeer() *Nork
- func (p *Nork) PrintTree(w io.Writer) error
- func (p *Nork) SetLevel(i int)
- func (p *Nork) SetNextPeer(p2 *Nork)
- func (p *Nork) SetParent(p2 *Nork)
- func (p *Nork) SetPrevPeer(p2 *Nork)
- func (p *Nork) StringserTree(f StringerFunc, w io.Writer) error
- type Norker
- type StringFunc
- type StringerFunc
- type Stringser
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewFSOTreeNorkFactory ¶
func NewFSOTreeNorkFactory(aRootPath string) ( *FSOTreeNorkFactory, *FSONork, error)
NewFSOTreeNorkFactory works with a directory in a filesystem:
- it verifies that it got a directory
- it also creates and returns the root Nork
- an error return is a *os.PathError
If the target is a single file (instead of a directory), just use func NewFSOLoneNork, which is also used to create the initial root Nork in this func.
TBD: Global index counter or ask SQLite ? TBD: Maybe also pass in a "NewNork" function. .
Types ¶
type Cntyer ¶
type Cntyer interface {
// =========================
// FILEPATHS (Rel. & Abs.)
// =========================
// RelFP is rel.filepath for a file/dir.
RelFP() string
SetRelFP(string)
// AbsFP is abs.filepath for a file/dir.
AbsFP() string
SetAbsFP(string)
// Kids stuff that only makes sense for Kids in a linked list.
SetFirstKid(*Nork)
SetLastKid(*Nork)
SetPrevPeer(*Nork)
SetNextPeer(*Nork)
}
Cntyer is satisfied by [*Cnty] NOT by Cnty.
type FSONork ¶
type FSONork struct {
Nork
// FSO includes paths and flags like IsDir and DoesNotExist.
// (For non-FS use case, IsDir() might be sorta "CanKids".)
// (IsDirlike should be considered TBD for anything but FS.)
FSO *FU.FSObject
// contains filtered or unexported fields
}
FSONork embeds an FSO and a Nork (a "QuadraNode").
The UC (Use Case) is:
- UC.FSI: File System Item: represent a dir-or-file-or-softlink: Here ordering is less important. Paths are valid FS paths. Prnt:dir, Kids:contents(!dir-v-file), Usrs:incoming-symlinks
Using Norks for files & dirs exhibits strong typing. Dirs are dirs and files are files and never the twain shall meet. This means that (a) dirs cannot contain own-content, and (b) files can never be non-leaf nodes. (Note tho that symlinks have aspects of both.) However this dir/file/etc typing is too complex to handle here in a Nork, because a leaf node can be either a file or a dir, and a field like "canKid bool" is a bit OTT, so the file/dir distinction is handled instead by an outer struct type that embeds Nork, such as FSONork, embedding [fileutils.FSObject].
If we build up a tree of Norks when processing an os.DirFS, the strict ordering provided by DirFS is not strictly needed, BUT it can anyways be used (and relied upon) because the three flavors of WalkDir are deterministic (using lexical order). WalkDir does tho promise that a given Nork will always appear AFTER the Nork for its directory has appeared, which makes it "easy" to build a tree. .
func NewFSOLoneNork ¶
NewFSOLoneNork prefers a relative path. It does not load file content, which is an expensive operation done elsewhere. But it loads the [Filepaths], which does checks specific to filesystem objects. Then the content can be loaded lazily at any time.
Do NOT use this for a path that is not a filesystem path. .
func (*FSONork) AbsFP ¶
AbsFP is an absolute filepath. Discussion: It is the same as rel.path, but expanded to be rooted in - i.e. traced back to the root of - a filesystem.
func (*FSONork) RelFP ¶
RelPath is a relative filepath. Discussion: it is the relative path of this Nork, relative to its tree's root Nork, which is the "local root" shared with other Norks in the same interconnected tree. (That is to say, a local root is the highest/topmost node of a directory tree imported in a single batch.) The last element of the relFP is this Nork's own name/label, analagous to FP.Base(Path).
type FSOTreeNorkFactory ¶
type FSOTreeNorkFactory struct {
// NexSeqID should be unique across all Norks,
// so it should probably be filled in by SQLite.
NexSeqID int
// RootNork includes the [Filepaths].
RootFSONork *FSONork
// BatchID indexes a DB table.
BatchID int
}
FSOTreeNorkFactory stores and tracks the state of an FSO (File System Object) Nork tree being assembled via calls to its method [NewFSOTreeNork].
It cannot (and should not) become invalid after creation, so Errer is not embedded & used.
A Factory is a good idea because then we can add arbitrary additional functionality (and complexity) and also spin up Nork factories that are customised for any of many purposes. .
func (*FSOTreeNorkFactory) NewFSOTreeNork ¶
func (pFac *FSOTreeNorkFactory) NewFSOTreeNork(aRelPath string) *FSONork
NewFSOTreeNork prefers a relative path. It does not load file content, which is an expensive operation done elsewhere. But it loads the [Filepaths], which does checks specific to filesystem objects. Then the content can be loaded lazily at any time.
Do NOT use this for a path that is not a filesystem path. .
func (*FSOTreeNorkFactory) RootPath ¶
func (p *FSOTreeNorkFactory) RootPath() string
type InspectorFunc ¶
type Nork ¶
type Nork struct {
// Name is the only field that we know works across
// all Use Cases, and any rules are specific to UC.
Name string
// Uuid7 is so that we can make & follow links btwn separate trees & lists.
Uuid7 string
// UC is Use Case is QuadraMode UC selector
UC string
// ==========
//
// And also
//
// ==========
// Errer is here because a Nork that is already linked into
// a tree might acquire an error due to an external change.
FU.Errer
// -----------
// GUI Stuff
// -----------
HasFocus, IsSelected, IsExpanded, IsVisible bool
// contains filtered or unexported fields
}
Nork is a "QuadraNode", handling four directions of connections for four distinct node user cases (UCs). Nork is kind of a maximalist implementation, and is subject to redefinition and getting slimmed down.
Paths need not be in the filesystem, and so paths are IAW package path rather than package path/filepath.
Child nodes ("Kid"s) have an externally-defined order, implying that child nodes are not directly [Comparable] amongst themselves. This specific specified order or child nodes is essential for representation of content.
(Child ordering can help in other contexts too, such as filesystem operations, but it turns out that the Go stdlib generally returns directory items in an order - lexical order - and walks directories in lexical order.)
The ordering lets us define funcs like FirstKid, NextPeer, PrevPeer, LastKid. They are defined in interface Norker.
Structs that embed Nork can use funcs to redefine the names and usage of fields in Nork. For example, a Nork embedded in a [Cnty] (Content Entity) used for a file-or-dir can provide access to non-public field `relPath` via `func RelFP()` (where FP is filepath).
Implementation of Interface ¶
Methods on Nork need to be methods on pointers rather than methods on values. But the interface Norker will be defined on pointers rather than values, so the resulting syntax will be visually acceptable.
When a Nork is embedded in another struct, the embedding struct can rename the functions defined here. For example, [Nork.relPath] can be renamed as ´relFP`when we are dealing with a filesystem.
Directions in GUI:
- Left is ParentDir and Incoming Links
- Right is Kids and Outgoing Links
- Up is Incomiong Facets and maybe other metadata
- Down is Versions
Fields and use cases:
- The field Prnt is for a "Parent" singleton of some type (whenever applicable).
- The fields Tags and Vers are used pretty much the same in all UC's.
- UC.FSO: File System Object: represent a dir-or-file-or-softlink: Here ordering is less important. Paths are valid FS paths. Prnt:dir, Kids:contents(!dir-v-file), Usrs:incoming-symlinks
- UC.CMS: CMS Usage: Table of Contents line item (transclusion in CMS) This should be an ideal use case. Prnt:?TBD, Kids:sub-ToC's/outrefs, Usrs:inrefs+transcluders
- UC.XML: XML text: tag/element in markup (XML, or other w AST) This has complexity handling same-named siblings, such as multiple <p> tags. Paths are tricky (mult. same tags). Prnt:parent-elm(or file @root), Kids:kid-elms, Usrs:?entity-stuf
- UC.GST: GolangAST: Go code AST node (https://pkg.go.dev/go/ast#Node) Pnt:node(or root's file), Kids:AST, Usrs:callers/refs
Note that UUID might take on outsized importance, because it might be used to reference all manner of other DB tables and data structures and external (to the system) targets. It may become necessary to add prefixes to UUIDs to specify (e.g.) the DB tables of targets.
Also there are two distinct memory management nodes for allocating and linking nodes:
- The "traditional method" of allocating nodes individually, and linking them using pointers. Using this method, both deletions and insertions are relatively simple.
- Such nodes can also be loaded into a map, for random access to nodes based on path.
- The "new-fangled way" called an "arena", where we put all our nodes in a big slice, and link them using indices. This method is much kinder on memory management, but might becomes clumsy when we need dynamic node management.
- Deletions are easy if we just zero out the slice entry; we cannot then do compaction because it would require updating all indices past the first point of compaction.
- Insertions are costly. However note that in this implementation, for a node's kids, we use a linked list rather than a slice, so this makes it easier to append a new node at the end of the arena-slice and then update indices, wherever they may be elsewhere in the slice.
- In any case, if an arena-slice has to grow (because of a call to append), it might be moved elsewhere in memory, which would invalidate all ptrs to other Norks! If this happens, we should trust only the "traditional method".
Also there are multiple ways to represent node trees in our SQLite DBMS, and multiple ways to walk a node tree, so there is a unavoidable complexity wherever we look.
NOTE: DOM markup exhibits name duplication: In UC.1 we never have two same-named files in the same directory, but in UC.2 we might have (for example) multiple sibling <p> tags. So when representing markup, a map from paths to Norks would fail unless the tags are made unique with subscript indices (such as "[1]", "[2]").
Link fields are lower-cased so that other packages cannot damage links.
NOTE: This implementation stores pointers to child nodes in a doubly linked list, not a slice. Therefore a Nork does not have a complete set of pointers to all of its kids. Therefore (a) it is not simple to get a kid count, because it requires a list traversal, and (b) it is not feasible to monify this code to define a simpler, more efficient variant of Nork that has unordered kids. .
func NewNork ¶
NewNork does NOT assume an FS-type environment, and is fairly simple:
- It expects a relative path, not absolute; "." and "./" are OK but not ""; an absolute path does NOT return an error
- The path need not be in a filesystem, but paths are still expected to conform to package path (rather than package path/filepath): "The path package should only be used for paths separated by forward slashes, such as the paths in URLs. This package does not deal with Windows paths with drive letters or backslashes; to manipulate operating system paths, use the path/filepath package."
- It does not attempt to resolve the path to an absolute path: this capability is not part of the `path` package
- Its only concept is tree, not filesystem, so it mainly just records its own node name and acquires a UUID7
- It implements interface [Errer], so errors can be acquired dynamically
- There is no distinction between a "tree" Nork and a "lone" Nork
.
func (*Nork) AddKid ¶
AddKid adds the supplied node as the last kid, and returns it (i.e. the new last kid), now linked into the tree. NOTE that just about ALL old (linked list) code is ripped out.
func (*Nork) AddKids ¶
AddKids adds the supplied nodes as kids, after any pre-existing kids, and returns the parent. The supplied nodes may NOT be already linked to each other as a linked list.
func (*Nork) KidsAsSlice ¶
func (*Nork) LinePrefixString ¶
LinePrefixString provides indentation and should start a line of display/debug.
It does not end the string with (white)space. .
func (*Nork) StringserTree ¶
func (p *Nork) StringserTree(f StringerFunc, w io.Writer) error
type Norker ¶
type Norker interface {
Stringser
// =====================
// PATHS (Rel. & Abs.)
// =====================
// RelPath is rel.filepath for a file/dir, and for a DOM
// node, it is meaningless, unless it is a [RootNorker],
// for which it is the rel.path to the containing document.
RelPath() string
SetRelPath(string)
// AbsPath is abs.filepath for a file/dir, and for a DOM
// node, the (abs.)path of the node w.r.t. the document
// root, except for a [RootNorker], for which it is
// the abs.path to the containing document.
AbsPath() string
SetAbsPath(string)
// ======================
// PATH CHARACTERISTICS
// ======================
// Level is zero-based (i.e. root Nork's is 0)
Level() int
// SetLevel(int) // should be self-calculated by node
// Root should always return the root
// (at arena index 0, if one is used)
Root() *Nork // RootNork
IsRoot() bool
IsDir() bool
IsDirlike() bool
// ==================
// NODE LINKS i.e.
// INTERCONNECTIONS
// ==================
Parent() *Nork
SetParent(*Nork)
HasKids() bool
KidsAsSlice() []*Nork
FirstKid() *Nork
LastKid() *Nork
PrevPeer() *Nork // cos "PrevKid" = iterator on the parent
NextPeer() *Nork
// AddKid returns the just-added kid, who
// is added at the end of the list of Kids,
// and who (TODO FWIW) knows his own arena
// index (using [slices.Index])
AddKid(*Nork) *Nork
// AddKids returns the method target
// - the parent of all the kids
AddKids([]*Nork) *Nork
ReplaceBy(*Nork) *Nork // returns Whom
// ============
// MISCELLANY
// ============
LinePrefixString() string
PrintTree(io.Writer) error
}
Norker is satisfied by *Nork NOT by Nork.
type StringFunc ¶
StringFunc is used by interface Norker, so a method signature actually (MAYBE!) looks like:
func (*Nork) FuncName() string