orderednodes

package module
v0.0.0-...-0972119 Latest Latest
Warning

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

Go to latest
Published: May 19, 2026 License: MIT Imports: 8 Imported by: 4

README

orderednodes

Nodes with ordered children, to use for XML mixed content and with golang os.DirFS

Documentation

Overview

Package orderednodes is a way to create a hierarchical tree of nodes, where the nodes are ordered and keep their order, and without needing or using Go generics.

Node order is of course important for markup in general and XML mixed content in particular, while unimportant when XML is used purely for data records.

interface Norder is implemented not for type Nord but rather for `*Nord´. NThis is so that nodes are writable.

Index

Constants

View Source
const (
	Sep     = os.PathSeparator
	ListSep = os.PathListSeparator
)

Variables

View Source
var SkipAll error = fs.SkipAll

SkipAll as a return value (from [NordProcFunc]) says skip all remaining files and directories. It is not returned as an error by any function.

View Source
var SkipDir error = fs.SkipDir

SkipDir as a return value (from [NordProcFunc]) says to skip the directory named in the call. It is not returned as an error by any function.

Functions

func InspectTree

func InspectTree(p Norder, f InspectorFunc) error

func InspectTree used to be func WalkNorders .

func InspectTreeWithPreAndPost

func InspectTreeWithPreAndPost(p Norder,
	f0 InspectorFunc, f1 InspectorFunc) error

func WalkNorderTree

func WalkNorderTree(root Norder, fn fs.WalkDirFunc) error

WalkNorderTree walks the tree of [ON.Norder]s, calling `NorderProcFunc` for each Norder in the tree, including the input Norder.

?? All errors that arise visiting files and directories are filtered by fn: ?? see the fs.WalkDirFunc documentation for details.

The paths associated with the `Norder`s are, at each level downward, pretty much assumed to be in lexical order. .

Types

type FilePropsNord

type FilePropsNord struct {
	Nord
	FU.FSObject
}

FilePropsNord is an Ordered Propertied Path node: NOT ONLY the child nodes have a specific specified order BUT ALSO each node has a filepath plus the file item itself including its contents (in TypedRaw). This means Pthat every Parent node is a directory.

It also means we can use the redundancy to do a lot of error checking. Also we can use fields of seqId's to store parent and kid seqId's, adding yet another layer of error checking and simplified access.

type InspectorFunc

type InspectorFunc func(pNode Norder) error

type Nord

type Nord struct {
	// contains filtered or unexported fields
}

Nord is shorthand for "ordered node" (or "ordinal node") - a Node whose child nodes have an externally-defined order, so 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 lexical order and walks directories in lexical order.)

The ordering lets us define funcs like FirstKid, NextKid, PrevKid, LastKid. They are defined in interface Norder. A Nord for file/dir also contains its own relative path (relative to its inbatch) and absolute path.

Usage

Nord is written so that it can be embedded within a larger struct. We do this rather than define a Nord as a struct that takes a generic argument that defines a node's associated data. It does mean that many methods on Nord need to be methods on pointers rather than methods on values. But the interface Norder will be defined on pointers rather than values, so the resulting syntax will be visually acceptable.

Alternatives Everywhere

There are three use cases identified for Nords:

  • UC.1: files & directories (here ordering is less important)
  • UC.2: XML/HTML/DOM markup (this has complexity handling same-named siblings, such as multiple <p> tags)
  • UC.3: [Lw]DITA map files and other ToC's (these should be an ideal use case)

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 Nords! 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 Nords would fail unless the tags are made unique with subscript indices (such as "[1]", "[2]").

NOTE: Using Nords 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 Nord, 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 Nord, such as [fileutils.FSObject].

If we build up a tree of Nords 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 Nord will always appear AFTER the Nord for its directory has appeared, which makes it "easy" to build a tree.

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 Nord 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 Nord that has unordered kids. .

func NewNord

func NewNord(aRelPath string) *Nord

NewNord expects a relative path (!!), and does not either (a) set/unset the bool [isDir] or (b) load file content, because these are expensive operations that can and should be done elsewhere, and also (c) they do not apply if this is being used for XML DOM.

func NewRootNord

func NewRootNord(rootPath string, smryFunc StringFunc) *Nord

NewRootNord verifies it got a directory, and then sets the bools [isRoot] and [isDir]. Note that the passed-in field [rootPath] is set elsewhere, and must be set in the global NordEng before any child Nord is created using NewNord.

func (*Nord) AbsFP

func (p *Nord) AbsFP() string

AbsFP is duh.

func (*Nord) AddKid

func (p *Nord) AddKid(aKid Norder) Norder

AddKid adds the supplied node as the last kid, and returns it (i.e. the new last kid), now linked into the tree.

func (*Nord) AddKids

func (p *Nord) AddKids(rKids []Norder) Norder

AddKids adds the supplied nodes as kids, after any pre-existing kids, and returns the parent.

func (*Nord) Echo

func (p *Nord) Echo() string

Echo implements Markupper.

func (*Nord) FirstKid

func (p *Nord) FirstKid() Norder

FirstKid provides read-only access for other packages. Can return nil.

func (*Nord) HasKids

func (p *Nord) HasKids() bool

HasKids is duh.

func (*Nord) IsDir

func (p *Nord) IsDir() bool

IsDir does NOT work, because we are not setting bool isDir yet. It is set (or not set) in embedding structs.

func (*Nord) IsRoot

func (p *Nord) IsRoot() bool

IsRoot is duh.

func (*Nord) KidsAsSlice

func (p *Nord) KidsAsSlice() []Norder

func (*Nord) LastKid

func (p *Nord) LastKid() Norder

LastKid provides read-only access for other packages. Can return nil.

func (*Nord) Level

func (p *Nord) Level() int

Level is duh.

func (Nord) LinePrefixString

func (p Nord) LinePrefixString() string

LinePrefixString provides indentation and should start a line of display/debug.

It does not end the string with (white)space. .

func (*Nord) LineSummaryFunc

func (p *Nord) LineSummaryFunc() StringFunc

func (*Nord) LineSummaryString

func (p *Nord) LineSummaryString() string

func (*Nord) NextKid

func (p *Nord) NextKid() Norder

NextKid provides read-only access for other packages. Can return nil.

func (*Nord) Parent

func (p *Nord) Parent() Norder

Parent returns the parent, duh.

func (*Nord) PrevKid

func (p *Nord) PrevKid() Norder

PrevKid provides read-only access for other packages. Can return nil.

func (*Nord) PrintCssTree

func (p *Nord) PrintCssTree(w io.Writer) error

func (*Nord) PrintTree

func (p *Nord) PrintTree(w io.Writer) error

func (*Nord) RelFP

func (p *Nord) RelFP() string

RelFP is duh.

func (*Nord) ReplaceWith

func (pOld *Nord) ReplaceWith(pNew Norder) Norder

AddKid adds the supplied node as the last kid, and returns it (i.e. the new last kid), now linked into the tree.

func (*Nord) Root

func (p *Nord) Root() RootNorder

Root walks the tree upward until [IsRoot] is true, so it does not use any global variables.

func (*Nord) SetFirstKid

func (p *Nord) SetFirstKid(p2 Norder)

SetFirstKid has no side effects.

func (*Nord) SetLastKid

func (p *Nord) SetLastKid(p2 Norder)

SetLastKid has no side effects.

func (*Nord) SetNextKid

func (p *Nord) SetNextKid(p2 Norder)

SetNextKid has no side effects.

func (*Nord) SetParent

func (p *Nord) SetParent(p2 Norder)

SetParent has no side effects.

func (*Nord) SetPrevKid

func (p *Nord) SetPrevKid(p2 Norder)

SetPrevKid has no side effects.

type NordEngine

type NordEngine struct {
	// contains filtered or unexported fields
}

NordEngine tracks the state of a Nord tree being assembled, for example when a directory is specified for recursive analysis.

var NordEng *NordEngine = new(NordEngine)

NordEng is a package global, which is dodgy and not re-entrant. The solution probably involves currying.

type Norder

type Norder interface {
	// SeqID() int
	// SetSeqID(int)
	// Level is zero-based (i.e. root nord's is 0)
	Level() int
	// RelFP is rel.filepath for a file/dir, and for a DOM
	// node, it is meaningless, unless it is a [RootNorder],
	// for which it is the rel.path to the DOCUMENT
	RelFP() string
	// AbsFP 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 [RootNorder],
	// for which it is the abs.path to the DOCUMENT
	AbsFP() string
	IsRoot() bool
	// Root should always return the root, at arena index 0
	Root() RootNorder
	IsDir() bool
	// IsDirlike() bool // FIXME: add this!
	Parent() Norder
	HasKids() bool
	FirstKid() Norder
	LastKid() Norder
	PrevKid() Norder
	NextKid() Norder
	KidsAsSlice() []Norder
	// AddKid returns the kid, who knows his
	// own arena index (using [slices.Index])
	AddKid(Norder) Norder
	// AddKids returns the method target
	// - the parent of all the kids
	AddKids([]Norder) Norder
	ReplaceWith(Norder) Norder
	SetParent(Norder)
	SetPrevKid(Norder)
	SetNextKid(Norder)
	SetFirstKid(Norder)
	SetLastKid(Norder)
	LinePrefixString() string
	LineSummaryString() string
	LineSummaryFunc() StringFunc
	PrintTree(io.Writer) error
	// contains filtered or unexported methods
}

Norder is satisfied by *Nord NOT by Nord.

func ReplaceTree

func ReplaceTree(oldRoot Norder, f ReplacerFunc) (newRoot Norder, err error)

type ReplacerFunc

type ReplacerFunc func(Norder) Norder

type RootFilePropsNord

type RootFilePropsNord FilePropsNord

Available to ensure that assignments to/from root node are explicit.

type RootNord

type RootNord Nord

RootNord is defined, so that assignments to/from a root node have to be explicit.

type RootNorder

type RootNorder interface {
	Norder
}

RootNorder is kind of TBD, but it is (in principle) satisfied by *RootNord and/but NOT by RootNord.

type StringFunc

type StringFunc func(Norder) string

StringFunc is used by interface Norder, so its method signature actually (MAYBE!) looks like: func (*Nord) FuncName() string

Jump to

Keyboard shortcuts

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