Compare commits

..

8 commits
0.1.0 ... main

Author SHA1 Message Date
Endeavorance b29c09a339 Add frontmatter number 2024-07-04 19:02:02 -04:00
Endeavorance fcbaa1bd52 Add frontmatter helpers 2024-07-04 17:39:01 -04:00
Endeavorance 29ea947b41 Add example to readme 2024-05-13 08:17:15 -04:00
Endeavorance da04862aad Update README.md 2024-05-12 22:13:00 +00:00
Endeavorance 78a621e788 Prefer scopes 2024-05-12 15:57:11 -04:00
Endeavorance 71ed28b3f2 Revert slug change 2024-05-12 09:15:44 -04:00
Endeavorance ec5d7296dc Bump version 2024-05-12 08:56:51 -04:00
Endeavorance b4d6e01f6d Fix bug with scope 2024-05-12 08:54:40 -04:00
8 changed files with 108 additions and 44 deletions

View file

@ -1 +0,0 @@
**_test

View file

@ -2,18 +2,40 @@
Load and manipulate Obsidian vault data Load and manipulate Obsidian vault data
## Early Release
This project is in early development. It is not yet fully featured.
### Roadmap
Features that have yet to be implemented but are planned:
- Create / Delete documents
- Custom write location
- Canvas support
- `.obsidian` folder support
- Improved file slug/id handling
- Plugin API
## Example Usage
```javascript
import { Vault } from "@foundry/hammerstone";
const vault = new Vault("./my-vault");
vault.process((document) => {
document.setFrontmatter({ "Last Processed": Date.now() });
});
vault.write();
```
## API ## API
### `new Vault(vaultRootPath [, options])` ### `new Vault(vaultRootPath)`
Create a new `Vault` object. Searches for markdown files in the given directory, loads, and parses frontmatter for each document. Create a new `Vault` object. Searches for markdown files in the given directory, loads, and parses frontmatter for each document.
#### Options
| Option | Description | Default |
| ---------------- | ---------------------------------------------------------- | ------- |
| `ignorePatterns` | An optional array of globs to ignore when discovering docs | `[]` |
### `vault.process(fn)` ### `vault.process(fn)`
Process all documents in the vault. Each document is passed to the provided function. Process all documents in the vault. Each document is passed to the provided function.

BIN
bun.lockb Executable file

Binary file not shown.

View file

@ -1,6 +1,6 @@
{ {
"name": "@endeavorance/hammerstone", "name": "@foundry/hammerstone",
"version": "0.1.0", "version": "0.3.2",
"description": "Load and manipulate Obsidian vault data", "description": "Load and manipulate Obsidian vault data",
"type": "module", "type": "module",
"exports": "./bin/hammerstone.js", "exports": "./bin/hammerstone.js",

View file

@ -80,13 +80,13 @@ Testing!
test("checking for tags", () => { test("checking for tags", () => {
const doc = getTestDocument(); const doc = getTestDocument();
const taggedDoc = doc.vault.index["directory-subdirectory-i-have-tags"]!; const taggedDoc = doc.vault.index["i-have-tags"]!;
assert.equal(taggedDoc.hasTag("tags"), true); assert.equal(taggedDoc.hasTag("tags"), true);
assert.equal(doc.hasTag("tags"), false); assert.equal(doc.hasTag("tags"), false);
}); });
test("checking for taxonomy", () => { test("checking for taxonomy", () => {
const doc = getTestDocument(); const doc = getTestDocument();
const taggedDoc = doc.vault.index["directory-subdirectory-i-have-tags"]!; const taggedDoc = doc.vault.index["i-have-tags"]!;
assert.equal(taggedDoc.hasTaxonomy(["Directory", "Subdirectory"]), true); assert.equal(taggedDoc.hasTaxonomy(["Directory", "Subdirectory"]), true);
assert.equal(doc.hasTaxonomy(["Directory", "Subdirectory"]), false); assert.equal(doc.hasTaxonomy(["Directory", "Subdirectory"]), false);
}); });

View file

@ -4,7 +4,6 @@ import Vault from "../vault.js";
import path from "node:path"; import path from "node:path";
const DOCS_IN_TEST_VAULT = 5; const DOCS_IN_TEST_VAULT = 5;
const EXCLUDED_DOCS = 1;
describe("Vault", () => { describe("Vault", () => {
test("load a vault", () => { test("load a vault", () => {
@ -25,20 +24,9 @@ describe("Vault", () => {
); );
}); });
test("ignored paths are not loaded into the vault", () => {
const vault = new Vault("./bin/_test/test-vault", {
ignorePatterns: ["**/Ignoreme/**"],
});
assert.equal(
vault.size,
DOCS_IN_TEST_VAULT - EXCLUDED_DOCS,
"Unexpected number of documents in vault",
);
});
test("document tags are properly parsed", () => { test("document tags are properly parsed", () => {
const vault = new Vault("./bin/_test/test-vault"); const vault = new Vault("./bin/_test/test-vault");
const taggedFile = vault.index["directory-subdirectory-i-have-tags"]; const taggedFile = vault.index["i-have-tags"];
if (taggedFile === undefined) { if (taggedFile === undefined) {
assert.fail("Expected file with tags"); assert.fail("Expected file with tags");

View file

@ -61,7 +61,7 @@ export default class MarkdownDocument {
this.dirname = path.relative(vaultPath, fileDirname); this.dirname = path.relative(vaultPath, fileDirname);
this.taxonomy = this.dirname.split(path.sep); this.taxonomy = this.dirname.split(path.sep);
this.filename = path.basename(filePath, ".md"); this.filename = path.basename(filePath, ".md");
this.slug = slug(`${this.taxonomy.join("-")}-${this.filename}`); this.slug = slug(this.filename);
this._content = rawFileContent; this._content = rawFileContent;
this.contentHistory = [rawFileContent]; this.contentHistory = [rawFileContent];
this._frontmatter = frontmatter; this._frontmatter = frontmatter;
@ -137,7 +137,7 @@ export default class MarkdownDocument {
* @returns `true` If this document shares the given taxonomy * @returns `true` If this document shares the given taxonomy
*/ */
hasTaxonomy(dirs: string[]) { hasTaxonomy(dirs: string[]) {
if (dirs.length < this.taxonomy.length) { if (dirs.length > this.taxonomy.length) {
return false; return false;
} }
@ -177,6 +177,74 @@ export default class MarkdownDocument {
return this; return this;
} }
/**
* Retrieves the value of a frontmatter key as a string.
* If the key is not found, returns the provided default value.
* Throws an error if the value is not a string.
*
* @param key - The key of the frontmatter value to retrieve.
* @param defaultValue - The default value to return if the key is not found.
* @returns The value of the frontmatter key as a string.
* @throws Error if the frontmatter key is not a string.
*/
getFrontmatterString(key: string, defaultValue: string): string {
const val = this._frontmatter[key];
if (val === undefined) {
return defaultValue;
}
return `${val}`;
}
/**
* Retrieves an array value from the frontmatter object based on the provided key.
* If the value is not found, it returns the provided defaultValue.
* If the value is found but is not an array, it throws an error.
*
* @param key - The key to retrieve the array value from the frontmatter object.
* @param defaultValue - The default value to return if the key is not found in the frontmatter object.
* @returns The array value associated with the provided key, or the defaultValue if the key is not found.
* @throws Error if the value associated with the key is found but is not an array.
*/
getFrontmatterArray(key: string, defaultValue: string[]): string[] {
const val = this._frontmatter[key];
if (val === undefined) {
return defaultValue;
}
if (!Array.isArray(val)) {
throw new Error(`Frontmatter key is not an array: ${key}`);
}
return val;
}
/**
* Retrieves a number value from the frontmatter using the specified key.
* If the key is not found in the frontmatter, the defaultValue is returned.
* If the value associated with the key is not a number, an error is thrown.
*
* @param key - The key to retrieve the number value from the frontmatter.
* @param defaultValue - The default value to return if the key is not found in the frontmatter.
* @returns The number value associated with the key in the frontmatter, or the defaultValue if the key is not found.
* @throws Error if the value associated with the key is not a number.
*/
getFrontmatterNumber(key: string, defaultValue: number): number {
const val = this._frontmatter[key];
if (val === undefined) {
return defaultValue;
}
if (typeof val !== "number") {
throw new Error(`Frontmatter key is not a number: ${key}`);
}
return val;
}
/** The markdown portion of the file (without frontmatter) */ /** The markdown portion of the file (without frontmatter) */
get markdown() { get markdown() {
return this._markdown; return this._markdown;

View file

@ -8,13 +8,8 @@ import MarkdownDocument from "./document.js";
* @param ignorePatterns A glob pattern to ignore files for * @param ignorePatterns A glob pattern to ignore files for
* @returns A promise which resolves as an array of laoded Documents * @returns A promise which resolves as an array of laoded Documents
*/ */
function loadVaultDocuments( function loadVaultDocuments(vault: Vault): MarkdownDocument[] {
vault: Vault, const discoveredMarkdownDocuments = globSync(`${vault.vaultPath}/**/*.md`);
ignorePatterns: string[] = [],
): MarkdownDocument[] {
const discoveredMarkdownDocuments = globSync(`${vault.vaultPath}/**/*.md`, {
ignore: ignorePatterns,
});
const markdownDocuments: MarkdownDocument[] = []; const markdownDocuments: MarkdownDocument[] = [];
@ -91,10 +86,6 @@ class VaultView {
} }
} }
interface VaultOptions {
ignorePatterns?: string[];
}
export default class Vault { export default class Vault {
/** An array of all discovered Markdown documents */ /** An array of all discovered Markdown documents */
documents: MarkdownDocument[] = []; documents: MarkdownDocument[] = [];
@ -111,14 +102,10 @@ export default class Vault {
/** The number of documents in this vault */ /** The number of documents in this vault */
readonly size: number = 0; readonly size: number = 0;
/** File patterns to ignore when discovering vault files */ constructor(vaultRootPath: string) {
private ignorePatterns: string[] = [];
constructor(vaultRootPath: string, options?: VaultOptions) {
this.vaultPath = path.resolve(vaultRootPath); this.vaultPath = path.resolve(vaultRootPath);
this.ignorePatterns = options?.ignorePatterns ?? [];
const allFiles = loadVaultDocuments(this, this.ignorePatterns); const allFiles = loadVaultDocuments(this);
const allSlugs = allFiles.map((doc) => doc.slug); const allSlugs = allFiles.map((doc) => doc.slug);
this.documents = allFiles; this.documents = allFiles;