Compare commits
No commits in common. "main" and "0.1.0" have entirely different histories.
1
.npmignore
Normal file
1
.npmignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
**_test
|
36
README.md
36
README.md
|
@ -2,40 +2,18 @@
|
||||||
|
|
||||||
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)`
|
### `new Vault(vaultRootPath [, options])`
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@foundry/hammerstone",
|
"name": "@endeavorance/hammerstone",
|
||||||
"version": "0.3.2",
|
"version": "0.1.0",
|
||||||
"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",
|
||||||
|
|
|
@ -80,13 +80,13 @@ Testing!
|
||||||
|
|
||||||
test("checking for tags", () => {
|
test("checking for tags", () => {
|
||||||
const doc = getTestDocument();
|
const doc = getTestDocument();
|
||||||
const taggedDoc = doc.vault.index["i-have-tags"]!;
|
const taggedDoc = doc.vault.index["directory-subdirectory-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["i-have-tags"]!;
|
const taggedDoc = doc.vault.index["directory-subdirectory-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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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", () => {
|
||||||
|
@ -24,9 +25,20 @@ 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["i-have-tags"];
|
const taggedFile = vault.index["directory-subdirectory-i-have-tags"];
|
||||||
|
|
||||||
if (taggedFile === undefined) {
|
if (taggedFile === undefined) {
|
||||||
assert.fail("Expected file with tags");
|
assert.fail("Expected file with tags");
|
||||||
|
|
|
@ -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.filename);
|
this.slug = slug(`${this.taxonomy.join("-")}-${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,74 +177,6 @@ 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;
|
||||||
|
|
23
src/vault.ts
23
src/vault.ts
|
@ -8,8 +8,13 @@ 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(vault: Vault): MarkdownDocument[] {
|
function loadVaultDocuments(
|
||||||
const discoveredMarkdownDocuments = globSync(`${vault.vaultPath}/**/*.md`);
|
vault: Vault,
|
||||||
|
ignorePatterns: string[] = [],
|
||||||
|
): MarkdownDocument[] {
|
||||||
|
const discoveredMarkdownDocuments = globSync(`${vault.vaultPath}/**/*.md`, {
|
||||||
|
ignore: ignorePatterns,
|
||||||
|
});
|
||||||
|
|
||||||
const markdownDocuments: MarkdownDocument[] = [];
|
const markdownDocuments: MarkdownDocument[] = [];
|
||||||
|
|
||||||
|
@ -86,6 +91,10 @@ 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[] = [];
|
||||||
|
@ -102,10 +111,14 @@ 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;
|
||||||
|
|
||||||
constructor(vaultRootPath: string) {
|
/** File patterns to ignore when discovering vault files */
|
||||||
this.vaultPath = path.resolve(vaultRootPath);
|
private ignorePatterns: string[] = [];
|
||||||
|
|
||||||
const allFiles = loadVaultDocuments(this);
|
constructor(vaultRootPath: string, options?: VaultOptions) {
|
||||||
|
this.vaultPath = path.resolve(vaultRootPath);
|
||||||
|
this.ignorePatterns = options?.ignorePatterns ?? [];
|
||||||
|
|
||||||
|
const allFiles = loadVaultDocuments(this, this.ignorePatterns);
|
||||||
const allSlugs = allFiles.map((doc) => doc.slug);
|
const allSlugs = allFiles.map((doc) => doc.slug);
|
||||||
|
|
||||||
this.documents = allFiles;
|
this.documents = allFiles;
|
||||||
|
|
Loading…
Reference in a new issue