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
## 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
### `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.
#### Options
| Option | Description | Default |
| ---------------- | ---------------------------------------------------------- | ------- |
| `ignorePatterns` | An optional array of globs to ignore when discovering docs | `[]` |
### `vault.process(fn)`
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",
"version": "0.1.0",
"name": "@foundry/hammerstone",
"version": "0.3.2",
"description": "Load and manipulate Obsidian vault data",
"type": "module",
"exports": "./bin/hammerstone.js",

View file

@ -80,13 +80,13 @@ Testing!
test("checking for tags", () => {
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(doc.hasTag("tags"), false);
});
test("checking for taxonomy", () => {
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(doc.hasTaxonomy(["Directory", "Subdirectory"]), false);
});

View file

@ -4,7 +4,6 @@ import Vault from "../vault.js";
import path from "node:path";
const DOCS_IN_TEST_VAULT = 5;
const EXCLUDED_DOCS = 1;
describe("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", () => {
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) {
assert.fail("Expected file with tags");

View file

@ -61,7 +61,7 @@ export default class MarkdownDocument {
this.dirname = path.relative(vaultPath, fileDirname);
this.taxonomy = this.dirname.split(path.sep);
this.filename = path.basename(filePath, ".md");
this.slug = slug(`${this.taxonomy.join("-")}-${this.filename}`);
this.slug = slug(this.filename);
this._content = rawFileContent;
this.contentHistory = [rawFileContent];
this._frontmatter = frontmatter;
@ -137,7 +137,7 @@ export default class MarkdownDocument {
* @returns `true` If this document shares the given taxonomy
*/
hasTaxonomy(dirs: string[]) {
if (dirs.length < this.taxonomy.length) {
if (dirs.length > this.taxonomy.length) {
return false;
}
@ -177,6 +177,74 @@ export default class MarkdownDocument {
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) */
get markdown() {
return this._markdown;

View file

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