Skip to content

Commit 6e5beeb

Browse files
orvalDuncan Rance
authored andcommitted
version 0.0.12
0 parents  commit 6e5beeb

40 files changed

+10710
-0
lines changed

.eslintrc.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module.exports = {
2+
env: {
3+
browser: true,
4+
commonjs: true,
5+
es2021: true
6+
},
7+
extends: [
8+
'standard',
9+
'plugin:react/recommended'
10+
],
11+
overrides: [
12+
],
13+
parserOptions: {
14+
ecmaVersion: 'latest'
15+
},
16+
rules: {
17+
},
18+
settings: {
19+
react: {
20+
version: 'detect'
21+
}
22+
}
23+
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Duncan Rance
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# rakosh
2+
3+
rakosh is a place to store nuggets of information. Like a knowledge base, yet built with small "nuggets" rather than pages.
4+
5+
raskosh is offline-first -- for now -- this is a prototype. Data are stored in an [ArangoDB](https://github.com/arangodb/arangodb) database. In future there may be a server and a frontend. For now, there is a tool for depositing data into ArangoDB, and one for extracting it as a [Gatsby](https://www.gatsbyjs.com) site.
6+
7+
One aim of this prototype is to develop an [ontology](https://en.wikipedia.org/wiki/Ontology_%28computer_science%29) for these "nuggets" of information and the relationships between them.
8+
9+
## Usage
10+
11+
In this first iteration, the use of ArangoDB is a sledgehammer to crack a site-generation nut. In future, it will be nice to see additional extraction targets and an online mode with an API; and even a frontend. For now, ArangoDB is acting as a schema validation tool.
12+
13+
ArangoDB can be installed in [various ways](https://www.arangodb.com/download-major/). Since the ArangoDB instance need not be long lived, using a Docker container is probably the simplest. To run a container, use this command:
14+
15+
docker run -e ARANGO_NO_AUTH=1 -p 8529:8529 --rm \
16+
arangodb/arangodb arangod --server.endpoint tcp://0.0.0.0:8529
17+
18+
Include a `--volume` to persist the data if required.
19+
20+
Once the server is up, content laid out in a specific directory structure can be deposited like so:
21+
22+
rakosh deposit ../my/content/dir
23+
24+
## Terminology
25+
26+
| Term | ArangoDB Objects | Description |
27+
|-|-|-|
28+
| mine | Database | the content store |
29+
| nugget | Document, Vertex | a small piece of useful information |
30+
| lode | Vertex | a top-level content category |
31+
| seam | Vertices | a set of nuggets that exist at the same level in the mine |
32+
| passage | Vertex | non-content verticies used to link content together |
33+
| vein | Vertices, Edges | a route through the mine that defines a nugget's location |
34+
| primary | Graph | internal - defines the relationship between all other objects |
35+
| edges | Edges | internal - name of the collection use for all edges |
36+
37+
## TODO
38+
39+
* refine/dedupe minemap
40+
* support for `passage: .`
41+
* github actions
42+
* write some more docs
43+
* define passages using symbolic links to directories in the filesystem deposit
44+
* text search
45+
* minemap search
46+
* minemap collapse/expand
47+
* dark mode
48+
* adjacent graph vertices view
49+
* keyboard navigation
50+
* handlebars for site generation mods

cli/deposit.js

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
'use strict'
2+
const { statSync, readdirSync } = require('node:fs')
3+
const { basename, join, resolve, extname } = require('node:path')
4+
const { Database } = require('arangojs')
5+
const { aql } = require('arangojs/aql')
6+
const { Nugget } = require('../lib/nugget')
7+
const log = require('loglevel')
8+
9+
log.setLevel('WARN')
10+
11+
const PRIMARY = 'primary'
12+
const PASSAGE = 'passage'
13+
const EDGES = 'edges'
14+
const SEAM = 'seam'
15+
const NUGGET = 'nugget'
16+
17+
const uuidRe = /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/
18+
19+
const passageLookup = {}
20+
21+
exports.command = 'deposit <directory> [options]'
22+
23+
exports.describe = 'Deposit content from the filesystem into a mine'
24+
25+
exports.builder = (yargs) => {
26+
return yargs
27+
.positional('directory', {
28+
describe: 'Directory containing a rakosh mine layout',
29+
string: true,
30+
normalize: true,
31+
coerce: d => {
32+
try {
33+
if (!statSync(d).isDirectory()) throw new Error()
34+
} catch {
35+
throw new Error(`${d} is not a directory`)
36+
}
37+
return d
38+
}
39+
})
40+
.option('replace', {
41+
alias: 'r',
42+
describe: 'Replace the existing mine if it exists',
43+
type: 'boolean',
44+
default: false
45+
})
46+
}
47+
48+
exports.handler = async function (argv) {
49+
try {
50+
if (argv.verbose) log.setLevel('INFO')
51+
52+
log.info(`creating mine deposit from directory ${argv.directory}`)
53+
54+
const dbName = basename(argv.directory)
55+
log.info(`mine is ${dbName}`)
56+
57+
const systemDb = new Database()
58+
const databaseNames = await systemDb.listDatabases()
59+
60+
if (databaseNames.includes(dbName)) {
61+
if (argv.replace) {
62+
log.info(`removing mine ${dbName}`)
63+
systemDb.dropDatabase(dbName)
64+
} else {
65+
log.error(`ERROR: mine ${dbName} already exists`)
66+
process.exit(1)
67+
}
68+
}
69+
70+
log.info(`creating mine ${dbName}`)
71+
const db = await systemDb.createDatabase(dbName)
72+
73+
await createGraph(db, argv.directory)
74+
} catch (err) {
75+
log.error(`ERROR: ${err}`)
76+
process.exit(1)
77+
}
78+
}
79+
80+
async function createGraph (db, directory) {
81+
log.info('creating primary graph')
82+
const graph = db.graph(PRIMARY)
83+
await graph.create([
84+
{
85+
collection: EDGES,
86+
from: [PASSAGE, SEAM, NUGGET],
87+
to: [PASSAGE, SEAM, NUGGET]
88+
}
89+
])
90+
91+
const adit = await graph.vertexCollection(PASSAGE).save({ _key: 'adit' })
92+
await deposit(graph, adit, directory)
93+
await createLinks(graph, directory)
94+
await createSeams(db, graph)
95+
}
96+
97+
async function deposit (graph, parentVertex, path) {
98+
const dirContents = readdirSync(path, { withFileTypes: true })
99+
const mdFiles = dirContents.filter(e => e.isFile() && extname(e.name) === '.md')
100+
const dirs = dirContents.filter(e => e.isDirectory())
101+
const passageNuggets = {}
102+
103+
// process markdown files
104+
for (const mdFile of mdFiles) {
105+
const base = basename(mdFile.name, '.md')
106+
107+
// only look at files named <UUID>.md
108+
if (uuidRe.test(base)) {
109+
const nugget = new Nugget(resolve(join(path, mdFile.name)))
110+
let collection = NUGGET
111+
112+
// a seam is a special flavour of nugget and goes in the SEAM collection
113+
if ('type' in nugget && nugget.type === 'seam') {
114+
log.info(`creating seam ${base}`)
115+
collection = SEAM
116+
}
117+
118+
if ('passage' in nugget) {
119+
log.info(`saving passage ${base}`)
120+
passageNuggets[nugget.passage] = nugget
121+
} else {
122+
log.info(`creating nugget ${base}`)
123+
const vertex = await graph.vertexCollection(collection).save(nugget.document)
124+
await graph.edgeCollection(EDGES).save({ _from: parentVertex._id, _to: vertex._id })
125+
}
126+
}
127+
}
128+
129+
for (const dir of dirs) {
130+
const doc = (dir.name in passageNuggets)
131+
? passageNuggets[dir.name].document
132+
: { label: dir.name }
133+
134+
// create a package vertex and recurse down the directory tree
135+
log.info(`creating passage ${dir.name} ${doc.label}`)
136+
const passageVertex = await graph.vertexCollection(PASSAGE).save(doc)
137+
const dirPath = join(path, dir.name)
138+
139+
passageLookup[resolve(dirPath)] = passageVertex._id
140+
await graph.edgeCollection(EDGES).save({ _from: parentVertex._id, _to: passageVertex._id })
141+
142+
await deposit(graph, passageVertex, dirPath)
143+
}
144+
}
145+
146+
async function createLinks (graph, directory) {
147+
const dirContents = readdirSync(directory, { withFileTypes: true })
148+
for (const dirent of dirContents) {
149+
if (dirent.isDirectory()) {
150+
createLinks(graph, resolve(join(directory, dirent.name)))
151+
} else if (dirent.isSymbolicLink() && extname(dirent.name) === '.md') {
152+
const base = basename(dirent.name, '.md')
153+
154+
if (!passageLookup[directory]) {
155+
log.error(`ERROR: cannot find passage vertex for ${base} with path ${directory}`)
156+
continue
157+
}
158+
159+
log.info(`creating link from ${passageLookup[directory]} to ${base}`)
160+
try {
161+
await graph.edgeCollection(EDGES).save({
162+
_from: passageLookup[directory],
163+
_to: `nugget/${base}`
164+
})
165+
} catch (err) {
166+
log.error(`ERROR: code ${err.code} linking ${passageLookup[directory]} to ${dirent.name}`)
167+
}
168+
}
169+
}
170+
}
171+
172+
async function createSeams (db, graph) {
173+
try {
174+
const collection = db.collection(SEAM)
175+
const cursor = await db.query(aql`
176+
FOR doc IN ${collection}
177+
RETURN doc
178+
`)
179+
180+
for await (const seam of cursor) {
181+
if (!('nuggets' in seam)) {
182+
log.warn(`WARNING: seam ${seam._id} has no nuggets`)
183+
continue
184+
}
185+
186+
let from = seam._id
187+
188+
for (const nugget of seam.nuggets) {
189+
const nuggetId = `nugget/${nugget}`
190+
191+
log.info(`creating link from ${from} to ${nuggetId}`)
192+
try {
193+
await graph.edgeCollection(EDGES).save({
194+
_from: from,
195+
_to: nuggetId
196+
})
197+
} catch (err) {
198+
log.error(`ERROR: code ${err.code} linking ${from} to ${nuggetId}`)
199+
}
200+
201+
from = nuggetId
202+
}
203+
}
204+
} catch (err) {
205+
log.error(`ERROR: exception during seam creation: ${err}`)
206+
}
207+
}

0 commit comments

Comments
 (0)