Class: OKF::Bundle
- Inherits:
-
Object
- Object
- OKF::Bundle
- Defined in:
- lib/okf/bundle.rb,
lib/okf/bundle/graph.rb,
lib/okf/bundle/folder.rb,
lib/okf/bundle/linter.rb,
lib/okf/bundle/reader.rb,
lib/okf/bundle/search.rb,
lib/okf/bundle/writer.rb,
lib/okf/bundle/validator.rb,
lib/okf/bundle/linter/report.rb,
lib/okf/bundle/validator/result.rb
Overview
A knowledge bundle held in memory (spec §2), Concept-first: parsed concepts plus the raw text of reserved index/log files and any files whose frontmatter failed to parse. Pure — it performs no disk access.
Build one straight from data (the Rails path — no markdown round-trip):
OKF::Bundle.new(concepts: [OKF::Concept.new(...)])
or let OKF::Bundle::Reader parse a directory into one. OKF::Bundle::Validator,
OKF::Bundle::Linter, and OKF::Bundle::Graph consume it; the convenience methods below forward
to them, so bundle.validate / bundle.lint / bundle.graph work on any
in-memory bundle.
root is the bundle path kept purely as data — it seeds bundle-relative link
resolution (§5.1) and report messages, never I/O. A bundle built in memory
without a real directory gets VIRTUAL_ROOT so relative-link math still works.
Defined Under Namespace
Classes: Entry, Folder, Graph, Linter, Reader, Search, Validator, Writer
Constant Summary collapse
- VIRTUAL_ROOT =
Stand-in absolute root for bundles built in memory (no directory). Only ever the base for pure path arithmetic in Links.resolve; the paths it yields are bundle-relative regardless of its value.
"/okf"
Instance Attribute Summary collapse
-
#concepts ⇒ Object
readonly
Returns the value of attribute concepts.
-
#reserved ⇒ Object
readonly
Returns the value of attribute reserved.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
-
#unparseable ⇒ Object
readonly
Returns the value of attribute unparseable.
Instance Method Summary collapse
-
#catalog ⇒ Object
Rich per-concept metadata the catalog / files / stats consumers want but the lean graph omits — the descriptive frontmatter fields plus in/out link degree taken from the graph edges.
-
#concept_by_id(id) ⇒ Object
The Concept with this id, or nil.
-
#directory_index ⇒ Object
The progressive-disclosure map (spec §6): one entry per directory that holds concepts or carries an index.md, sorted with the root (".") first.
- #graph(minimal: false, body: true) ⇒ Object
- #index_files ⇒ Object
-
#initialize(concepts: [], reserved: [], unparseable: [], root: nil) ⇒ Bundle
constructor
A new instance of Bundle.
- #lint(**options) ⇒ Object
- #log_files ⇒ Object
-
#paths ⇒ Object
Bundle-relative paths of every markdown file — concepts, reserved, and unparseable — sorted.
-
#paths_by_id ⇒ Object
{ id => bundle-relative path }.
-
#reserved_content(path) ⇒ Object
Raw content of a reserved file (index.md/log.md) by bundle-relative path, or "" when absent.
-
#validate ⇒ Object
── analysis (pure; forwards to the core analyzers) ──.
Constructor Details
#initialize(concepts: [], reserved: [], unparseable: [], root: nil) ⇒ Bundle
Returns a new instance of Bundle.
40 41 42 43 44 45 |
# File 'lib/okf/bundle.rb', line 40 def initialize(concepts: [], reserved: [], unparseable: [], root: nil) @concepts = concepts @reserved = reserved @unparseable = unparseable @root = root || VIRTUAL_ROOT end |
Instance Attribute Details
#concepts ⇒ Object (readonly)
Returns the value of attribute concepts.
38 39 40 |
# File 'lib/okf/bundle.rb', line 38 def concepts @concepts end |
#reserved ⇒ Object (readonly)
Returns the value of attribute reserved.
38 39 40 |
# File 'lib/okf/bundle.rb', line 38 def reserved @reserved end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
38 39 40 |
# File 'lib/okf/bundle.rb', line 38 def root @root end |
#unparseable ⇒ Object (readonly)
Returns the value of attribute unparseable.
38 39 40 |
# File 'lib/okf/bundle.rb', line 38 def unparseable @unparseable end |
Instance Method Details
#catalog ⇒ Object
Rich per-concept metadata the catalog / files / stats consumers want but the lean graph omits — the descriptive frontmatter fields plus in/out link degree taken from the graph edges. Pure: derived from the concepts and their links, sorted by id. Shared by the CLI views and the server's /catalog endpoint.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/okf/bundle.rb', line 102 def catalog out_degree = Hash.new(0) in_degree = Hash.new(0) graph(minimal: true).edges.each do |edge| out_degree[edge[:source]] += 1 in_degree[edge[:target]] += 1 end concepts.map do |concept| id = concept.id { id: id, title: (concept.title || id).to_s, type: concept.type.to_s, description: concept.description.to_s, tags: Array(concept.).map(&:to_s), timestamp: concept.&.to_s, status: concept.frontmatter["status"]&.to_s, backlog_ref: concept.frontmatter["backlog_ref"]&.to_s, dir: File.dirname("#{id}.md"), area: id.include?("/") ? id.split("/").first : "(root)", links_out: out_degree[id], links_in: in_degree[id] } end.sort_by { |entry| entry[:id] } end |
#concept_by_id(id) ⇒ Object
The Concept with this id, or nil. Last wins on a (rare) duplicate id.
75 76 77 |
# File 'lib/okf/bundle.rb', line 75 def concept_by_id(id) concepts_by_id[id] end |
#directory_index ⇒ Object
The progressive-disclosure map (spec §6): one entry per directory that holds
concepts or carries an index.md, sorted with the root (".") first. Each entry
gives the authored index body (frontmatter stripped) when an index.md is
present, a type/tag rollup over the concepts that live directly in the
directory, its immediate child directories, and the concept listing an
index.md there would enumerate. A directory with concepts but no index.md has
present: false and still carries the listing, so a consumer can synthesize
the map on the fly (§6 permits exactly that). Grouped by the concept's file
path — index files are physical directory listings, so a custom frontmatter
id must not move a concept out of the directory it lives in. Pure: derived
from the concepts and the reserved index text, no disk. Shared by the
okf index view and the server's Index panel (/index).
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/okf/bundle.rb', line 141 def directory_index by_dir = concepts.group_by { |concept| File.dirname(concept.path) } dirs = directory_set(by_dir.keys) dirs.map do |dir| here = (by_dir[dir] || []).sort_by(&:id) index_path = dir == "." ? "index.md" : "#{dir}/index.md" present = index_files.include?(index_path) { dir: dir, index_path: index_path, present: present, synthesized: !present, body: present ? strip_frontmatter(reserved_content(index_path)) : nil, count: here.size, types: tally(here.map { |concept| concept.type.to_s }), tags: tally(here.flat_map { |concept| Array(concept.).map(&:to_s) }), subdirs: dirs.select { |other| other != dir && File.dirname(other) == dir }, listing: here.map do |concept| { id: concept.id, title: (concept.title || concept.id).to_s, description: concept.description.to_s, type: concept.type.to_s, tags: Array(concept.).map(&:to_s) } end } end end |
#graph(minimal: false, body: true) ⇒ Object
94 95 96 |
# File 'lib/okf/bundle.rb', line 94 def graph(minimal: false, body: true) Graph.build(self, minimal: minimal, body: body) end |
#index_files ⇒ Object
53 54 55 |
# File 'lib/okf/bundle.rb', line 53 def index_files reserved_paths("index.md") end |
#lint(**options) ⇒ Object
90 91 92 |
# File 'lib/okf/bundle.rb', line 90 def lint(**) Linter.call(self, **) end |
#log_files ⇒ Object
57 58 59 |
# File 'lib/okf/bundle.rb', line 57 def log_files reserved_paths("log.md") end |
#paths ⇒ Object
Bundle-relative paths of every markdown file — concepts, reserved, and unparseable — sorted.
49 50 51 |
# File 'lib/okf/bundle.rb', line 49 def paths (@concepts.map(&:path) + @reserved.map(&:path) + @unparseable.map(&:path)).sort end |
#paths_by_id ⇒ Object
{ id => bundle-relative path }.
80 81 82 |
# File 'lib/okf/bundle.rb', line 80 def paths_by_id @paths_by_id ||= @concepts.map { |concept| [ concept.id, concept.path ] }.to_h end |
#reserved_content(path) ⇒ Object
Raw content of a reserved file (index.md/log.md) by bundle-relative path, or "" when absent. Reserved structure is validated as text, and index links are extracted from it, so its raw form is retained.
64 65 66 67 |
# File 'lib/okf/bundle.rb', line 64 def reserved_content(path) entry = @reserved.find { |candidate| candidate.path == path } entry ? entry.content.to_s : "" end |