Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ build
*.pdf
node_modules
mariadb_data
pr-drafts/

# Ignore direnv files
.direnv/*

# Ignore local documentation files
.local-docs/
harper-core/german_dictionary.dict
docs/
3 changes: 3 additions & 0 deletions .harper-dictionary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

SvelteKit
harper
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Harper Docs Map for Agents

This repository’s documentation site is powered by Vite + SvelteKit + SveltePress.
this repository’s documentation site is powered by Vite + SvelteKit + SveltePress.

Use `packages/web/vite.config.ts` as the source of truth for documentation scope:
- Sidebar and important doc routes are defined in `packages/web/vite.config.ts`.
Expand Down Expand Up @@ -45,7 +45,7 @@ Agents should remind humans of this when possible.
- `packages/web/src/routes/docs/weir/+page.md`: Weir rule language reference with syntax, expression types, and examples. Very important if you're asked to write a Weir rule.
- `packages/web/src/routes/docs/rules/+page.svelte`: Live/generated rule catalog (rule names, defaults, and descriptions).
- `packages/web/src/routes/docs/integrations/obsidian/+page.md`: Obsidian plugin overview, privacy/value comparison, installation, and support links.
- `packages/web/src/routes/docs/integrations/chrome-extension/+page.md`: End-user Chrome extension overview and install link.
- `packages/web/src/routes/docs/integrations/chrome-extension/+page.md`: End-user Chrome Extension overview and install link.
- `packages/web/src/routes/docs/integrations/firefox-extension/+page.md`: End-user Firefox extension overview and install link.
- `packages/web/src/routes/docs/integrations/wordpress/+page.md`: Current WordPress guidance, including migration recommendation to Chrome extension and legacy plugin status.
- `packages/web/src/routes/docs/integrations/language-server/+page.md`: `harper-ls` install methods, dictionaries, code actions, ignore comments, and full configuration reference.
Expand Down Expand Up @@ -82,7 +82,7 @@ Agents should remind humans of this when possible.
- `packages/web/src/routes/docs/about/+page.ts`: Route behavior helper (`ssr = false`) for the About page.
- `packages/web/src/routes/docs/harperjs/CDN/example/+server.ts`: Serves the HTML example used by the `harper.js` CDN documentation page.

## External Sidebar Targets (No Local Source File)
## External Sidebar Targets (no Local Source File)

- `https://docs.rs/harper-core/latest/harper_core/`
- `/docs/harperjs/ref/index.html` (generated API reference target)
Expand Down
8 changes: 0 additions & 8 deletions COMPARISON.md

This file was deleted.

2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion harper-cli/src/input/single_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ impl SingleInputTrait for FileInput {
Some("lhs") => Box::new(LiterateHaskellParser::new_markdown(
MarkdownOptions::default(),
)),
Some("org") => Box::new(OrgMode),
Some("org") => Box::new(OrgMode::default()),
Some("tex" | "latex" | "sty" | "cls" | "dtx") => Box::new(harper_tex::TeX::default()),
Some("typ") => Box::new(harper_typst::Typst),
Some("py") | Some("pyi") => Box::new(PythonParser::default()),
Expand Down
17 changes: 14 additions & 3 deletions harper-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#![doc = include_str!("../README.md")]

use harper_core::spell::{Dictionary, FstDictionary, MutableDictionary, WordId};
use harper_core::spell::{
Dictionary, FstDictionary, MutableDictionary, WordId, curated_german_dictionary,
};
use hashbrown::HashMap;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{self, BufReader};
use std::path::PathBuf;
// use std::sync::Arc;
use std::sync::Arc;
use std::{fs, process};

use anyhow::anyhow;
Expand Down Expand Up @@ -255,9 +257,15 @@ fn main() -> anyhow::Result<()> {
let dialect = parse_dialect(&dialect_str)
.map_err(|e| anyhow!("Invalid dialect '{}': {}", dialect_str, e))?;

let dict: Arc<dyn Dictionary> = if dialect.is_german() {
curated_german_dictionary()
} else {
curated_dictionary.clone()
};

lint(
markdown_options,
curated_dictionary,
dict,
inputs,
LintOptions {
count,
Expand Down Expand Up @@ -1008,6 +1016,9 @@ fn parse_dialect(dialect: &str) -> anyhow::Result<Dialect> {
"au" | "aus" | "australia" | "australian" | "en-au" | "en_au" => Ok(Dialect::Australian),
"in" | "india" | "indian" | "bharat" | "en-in" | "en_in" => Ok(Dialect::Indian),
"ca" | "canada" | "canadian" | "en-ca" | "en_ca" => Ok(Dialect::Canadian),
"de" | "german" | "deutsch" | "de-de" | "de_de" => Ok(Dialect::German),
"at" | "austria" | "austrian" | "de-at" | "de_at" => Ok(Dialect::GermanAustrian),
"ch" | "switzerland" | "swiss" | "de-ch" | "de_ch" => Ok(Dialect::GermanSwiss),
_ => Err(anyhow!("Unknown dialect: {}", dialect)),
}
}
Expand Down
1 change: 1 addition & 0 deletions harper-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ bitflags = { version = "2.11.0", features = ["serde"] }
trie-rs = "0.4.2"
zip = { version = "8.6.0", default-features = false, features = ["deflate"] }
regex = "1.12.3"
flate2 = "1.1"

[dev-dependencies]
criterion = { version = "0.8.2", default-features = false }
Expand Down
110 changes: 81 additions & 29 deletions harper-core/build.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,96 @@
use std::{env, fs, path::PathBuf};

fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let weir_rule_dir = manifest_dir.join("./src/linting/weir_rules");
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let dest = out_dir.join("weir_rules_generated_list.rs");
//! Build script for Harper core library.
//!
//! This script generates test boilerplate for Weir rules (pattern-based linters).
//! Weir rules are defined in `.weir` files with inline tests, and this script
//! automatically generates Rust test functions at compile time.
//!
//! ## German Language Support
//!
//! For German language support, we maintain a separate directory of Weir rules
//! at `src/linting/weir_rules/de/`. Each rule defines common German error patterns
//! (e.g., "bei beim" → "beim", "viele Dank" → "vielen Dank").
//!
//! The build script:
//! 1. Scans both English and German Weir rule directories
//! 2. Generates two separate test lists: `weir_rules_generated_list.rs` and
//! `german_weir_rules_generated_list.rs`
//! 3. Sets environment variables so the code can locate the rules at runtime
//!
//! ## Adding New Weir Rules
//!
//! To add a new German Weir rule:
//! 1. Create a new `.weir` file in `src/linting/weir_rules/de/`
//! 2. Define the pattern, message, and inline tests
//! 3. Rebuild - the build script will automatically include it in the generated list

let mut files: Vec<PathBuf> = fs::read_dir(&weir_rule_dir)
.unwrap()
.filter_map(Result::ok)
.filter(|e| e.file_type().unwrap().is_file())
.map(|e| e.path().to_path_buf())
.collect();
use std::{env, fs, path::PathBuf};

files.sort();
/// Collect all `.weir` files from a directory, sorted alphabetically.
/// Returns an empty vector if the directory doesn't exist (for optional language support).
fn collect_weir_files(dir: &PathBuf) -> Vec<PathBuf> {
match fs::read_dir(dir) {
Ok(entries) => {
let mut files: Vec<PathBuf> = entries
.filter_map(Result::ok)
.filter(|e| e.file_type().unwrap().is_file())
.map(|e| e.path())
.filter(|p| p.extension().map(|e| e == "weir").unwrap_or(false))
.collect();
files.sort();
files
}
Err(_) => vec![],
}
}

/// Generate Rust code that creates test functions for each Weir rule.
/// The output is a file containing: `generate_boilerplate!{[Rule1, Rule2, ...]}`
/// which is then expanded by the macro in `src/linting/weir_rules/mod.rs`.
fn write_boilerplate(files: &[PathBuf], dest: &PathBuf) {
let mut code = String::new();

code.push_str("generate_boilerplate!{[");

for file in files {
if file
.file_name()
.unwrap()
.to_string_lossy()
.ends_with(".weir")
{
code.push_str(&format!(
"{},\n",
file.file_stem().unwrap().to_str().unwrap()
));
}
code.push_str(&format!(
"{},\n",
file.file_stem().unwrap().to_str().unwrap()
));
}

code.push_str("]}");
fs::write(dest, code).unwrap();
}

fn main() {
// Locate source directories for Weir rules
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let weir_rule_dir = manifest_dir.join("./src/linting/weir_rules");
let german_weir_rule_dir = manifest_dir.join("./src/linting/weir_rules/de");
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

// Generate test lists for both English and German Weir rules
let dest = out_dir.join("weir_rules_generated_list.rs");
let de_dest = out_dir.join("german_weir_rules_generated_list.rs");

let files = collect_weir_files(&weir_rule_dir);
write_boilerplate(&files, &dest);

fs::write(&dest, code).unwrap();
let de_files = collect_weir_files(&german_weir_rule_dir);
write_boilerplate(&de_files, &de_dest);

// Tell Cargo to rerun this script if any Weir files change
println!("cargo:rerun-if-changed={}", weir_rule_dir.display());
println!("cargo:rerun-if-changed={}", german_weir_rule_dir.display());
println!("cargo:rerun-if-changed=build.rs");

// Export environment variables for use in the codebase
// These allow the code to locate Weir rule files and generated test lists at runtime
println!("cargo:rustc-env=WEIR_RULE_DIR={}", weir_rule_dir.display());
println!(
"cargo:rustc-env=GERMAN_WEIR_RULE_DIR={}",
german_weir_rule_dir.display()
);
println!("cargo:rustc-env=WEIR_RULE_LIST={}", dest.display());
println!(
"cargo:rustc-env=GERMAN_WEIR_RULE_LIST={}",
de_dest.display()
);
}
Binary file added harper-core/german_dictionary.dict.gz
Binary file not shown.
Loading
Loading