hyprlang 0.2.1

A scripting language interpreter and parser for Hyprlang and Hyprland configuration files.
Documentation

hyprlang-rs

A Rust reimplementation of Hyprlang, the configuration language used by Hyprland.

Hyprlang is a powerful configuration language featuring variables, nested categories, expressions, custom handlers, and more. This library provides a complete parser and configuration manager with a clean, idiomatic Rust API.

This project is not endorsed by or affiliated with the Hyprland project/HyprWM Organization.

Rust Build Crates.io Docs.rs License Crates.io Total Downloads Crates.io Size GitHub Sponsors

Features

  • 🎯 Complete Hyprlang Implementation - Full compatibility with the original C++ version
  • 🚀 Fast PEG Parser - Built with pest for efficient parsing
  • 🔧 Type-Safe API - Strongly-typed configuration values (Int, Float, String, Vec2, Color)
  • 📦 Variable System - Support for user-defined and environment variables with cycle detection
  • 🧮 Expression Evaluation - Arithmetic expressions with {{expr}} syntax
  • 🎨 Color Support - Multiple color formats: rgba(), rgb(), and hex colors
  • 📐 Vec2 Coordinates - Built-in support for 2D coordinate pairs
  • 🔌 Handler System - Extensible keyword handlers for custom syntax
  • 🏷️ Special Categories - Keyed, static, and anonymous category types
  • 📄 Source Directives - Include external configuration files
  • 💬 Conditional Directives - # hyprlang if/endif/noerror support with negation
  • 🎨 Expression Escaping - Escape expressions with \{{}} or {\{}} for literal braces
  • 🔄 Mutation & Serialization - Modify config values and save back to files (optional)
  • 🎯 Windowrule v3 / Layerrule v2 - Full support for new special category syntax with 85+ registered properties
  • Fully Tested - 171 tests covering all features

Installation

Add this to your Cargo.toml:

[dependencies]
hyprlang = "0.2.1"

Optional Features

hyprland Feature

Enable the hyprland feature to get a high-level Hyprland struct with pre-configured handlers and typed access to Hyprland configuration options:

[dependencies]
hyprlang = { version = "0.2.1", features = ["hyprland"] }

This feature provides:

  • Automatic registration of all Hyprland handlers (bind, monitor, env, etc.)
  • Typed accessor methods for common Hyprland config values
  • Convenient methods to access all binds, windowrules, animations, etc.

mutation Feature

Enable the mutation feature to modify configuration values and serialize configs back to files:

[dependencies]
hyprlang = { version = "0.2.1", features = ["mutation"] }

This feature provides:

  • Mutation API - Modify config values, variables, handlers, and special categories
  • Serialization - Save configurations back to files with clean formatting
  • Two mutation styles - Direct setters and mutable references
  • Round-trip support - Parse → modify → save → parse

Quick Start

use hyprlang::Config;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = Config::new();

    // Parse a configuration string
    config.parse(r#"
        general {
            gaps_in = 5
            gaps_out = 20
            border_size = 2
        }
    "#)?;

    // Access values
    let gaps_in = config.get_int("general:gaps_in")?;
    let gaps_out = config.get_int("general:gaps_out")?;

    println!("gaps_in: {}, gaps_out: {}", gaps_in, gaps_out);

    Ok(())
}

Hyprland API (Optional Feature)

The hyprland feature provides a high-level, type-safe API specifically designed for working with Hyprland configurations. Instead of manually registering handlers and using string-based key access, you get a convenient Hyprland struct with pre-configured handlers and typed accessor methods.

Why Use the Hyprland Feature?

Without the Hyprland feature (using low-level Config API):

use hyprlang::Config;

let mut config = Config::new();

// Manually register all handlers
config.register_handler_fn("bind", |_| Ok(()));
config.register_handler_fn("monitor", |_| Ok(()));
config.register_handler_fn("windowrule", |_| Ok(()));
// ... register 20+ more handlers

config.register_category_handler_fn("animations", "animation", |_| Ok(()));
config.register_category_handler_fn("animations", "bezier", |_| Ok(()));

// Access values with string keys and manual type conversion
let border_size = config.get_int("general:border_size")?;
let gaps_in = config.get_string("general:gaps_in")?; // Could be int or string
let binds = config.get_handler_calls("bind").unwrap_or(&vec![]);

With the Hyprland feature (using high-level Hyprland API):

use hyprlang::Hyprland;

let mut hypr = Hyprland::new(); // All handlers pre-registered!

// Typed accessor methods
let border_size = hypr.general_border_size()?;         // Returns i64
let gaps_in = hypr.general_gaps_in()?;                 // Returns String (CSS-style)
let active_border = hypr.general_active_border_color()?; // Returns Color

// Convenient array access
let binds = hypr.all_binds();  // Returns Vec<&String>

Complete Example

use hyprlang::Hyprland;
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create Hyprland config (handlers auto-registered)
    let mut hypr = Hyprland::new();

    // Parse your Hyprland config
    hypr.parse_file(Path::new("~/.config/hypr/hyprland.conf"))?;

    // === Access General Settings ===
    println!("Border: {}", hypr.general_border_size()?);
    println!("Layout: {}", hypr.general_layout()?);
    println!("Gaps: {} / {}", hypr.general_gaps_in()?, hypr.general_gaps_out()?);

    let active = hypr.general_active_border_color()?;
    println!("Active border: rgba({}, {}, {}, {})",
        active.r, active.g, active.b, active.a);

    // === Access Decoration Settings ===
    println!("\nRounding: {}", hypr.decoration_rounding()?);
    println!("Active opacity: {}", hypr.decoration_active_opacity()?);
    println!("Blur enabled: {}", hypr.decoration_blur_enabled()?);
    println!("Blur size: {}", hypr.decoration_blur_size()?);

    // === Access Animation Settings ===
    if hypr.animations_enabled()? {
        println!("\nAnimations:");
        for anim in hypr.all_animations() {
            println!("  - {}", anim);
        }

        println!("\nBezier curves:");
        for bezier in hypr.all_beziers() {
            println!("  - {}", bezier);
        }
    }

    // === Access Input Settings ===
    println!("\nKeyboard layout: {}", hypr.input_kb_layout()?);
    println!("Mouse sensitivity: {}", hypr.input_sensitivity()?);
    println!("Natural scroll: {}", hypr.input_touchpad_natural_scroll()?);

    // === Access Keybindings ===
    println!("\nKeybindings ({}):", hypr.all_binds().len());
    for (i, bind) in hypr.all_binds().iter().enumerate() {
        println!("  [{}] {}", i + 1, bind);
    }

    // === Access Window Rules ===
    println!("\nWindow rules ({}):", hypr.all_windowrules().len());
    for rule in hypr.all_windowrules() {
        println!("  - {}", rule);
    }

    // === Access Variables ===
    println!("\nVariables:");
    for (name, value) in hypr.variables() {
        println!("  ${} = {}", name, value);
    }

    // === Access Monitors ===
    for monitor in hypr.all_monitors() {
        println!("Monitor: {}", monitor);
    }

    // === Access Environment Variables ===
    for env in hypr.all_env() {
        println!("Env: {}", env);
    }

    // === Access Autostart ===
    for exec in hypr.all_exec_once() {
        println!("Exec-once: {}", exec);
    }

    Ok(())
}

Available Methods

General Settings

hypr.general_border_size() -> Result<i64>
hypr.general_gaps_in() -> Result<String>           // CSS-style: "5" or "5 10 15 20"
hypr.general_gaps_out() -> Result<String>          // CSS-style: "20" or "5 10 15 20"
hypr.general_layout() -> Result<&str>              // "dwindle" or "master"
hypr.general_allow_tearing() -> Result<bool>
hypr.general_active_border_color() -> Result<Color>
hypr.general_inactive_border_color() -> Result<Color>

Decoration Settings

hypr.decoration_rounding() -> Result<i64>
hypr.decoration_active_opacity() -> Result<f64>
hypr.decoration_inactive_opacity() -> Result<f64>
hypr.decoration_blur_enabled() -> Result<bool>
hypr.decoration_blur_size() -> Result<i64>
hypr.decoration_blur_passes() -> Result<i64>

Animation Settings

hypr.animations_enabled() -> Result<bool>
hypr.all_animations() -> Vec<&String>       // All animation definitions
hypr.all_beziers() -> Vec<&String>          // All bezier curve definitions

Input Settings

hypr.input_kb_layout() -> Result<&str>
hypr.input_follow_mouse() -> Result<i64>
hypr.input_sensitivity() -> Result<f64>
hypr.input_touchpad_natural_scroll() -> Result<bool>

Layout Settings

hypr.dwindle_pseudotile() -> Result<bool>
hypr.dwindle_preserve_split() -> Result<bool>
hypr.master_new_status() -> Result<&str>

Misc Settings

hypr.misc_disable_hyprland_logo() -> Result<bool>
hypr.misc_force_default_wallpaper() -> Result<i64>

Handler Calls (Arrays)

hypr.all_binds() -> Vec<&String>            // All bind definitions
hypr.all_bindm() -> Vec<&String>            // All mouse bindings
hypr.all_bindel() -> Vec<&String>           // All bindel definitions
hypr.all_bindl() -> Vec<&String>            // All bindl definitions
hypr.all_windowrules() -> Vec<&String>      // All windowrule v1 definitions (deprecated)
hypr.all_windowrulesv2() -> Vec<&String>    // All windowrule v2 definitions (deprecated)
hypr.all_layerrules() -> Vec<&String>       // All layerrule v1 definitions (deprecated)
hypr.all_workspaces() -> Vec<&String>       // All workspace definitions
hypr.all_monitors() -> Vec<&String>         // All monitor definitions
hypr.all_env() -> Vec<&String>              // All env definitions
hypr.all_exec() -> Vec<&String>             // All exec definitions
hypr.all_exec_once() -> Vec<&String>        // All exec-once definitions

Windowrule v3 & Layerrule v2 (Special Categories)

// New v3 syntax for windowrules
hypr.windowrule_names() -> Vec<String>                    // All windowrule names
hypr.get_windowrule(name: &str) -> Result<RuleInstance>  // Get specific rule

// New v2 syntax for layerrules
hypr.layerrule_names() -> Vec<String>                     // All layerrule names
hypr.get_layerrule(name: &str) -> Result<RuleInstance>   // Get specific rule

// RuleInstance helper provides typed access to properties:
rule.get(key: &str) -> Result<&ConfigValue>       // Get any property
rule.get_string(key: &str) -> Result<String>      // Get as string
rule.get_int(key: &str) -> Result<i64>            // Get as integer
rule.get_float(key: &str) -> Result<f64>          // Get as float
rule.get_color(key: &str) -> Result<Color>        // Get as color

Variables

hypr.variables() -> &HashMap<String, String>  // All variables
hypr.get_variable(name: &str) -> Option<&String>  // Get specific variable

Direct Config Access

If you need access to the underlying low-level Config API:

let config: &Config = hypr.config();        // Immutable access
let config: &mut Config = hypr.config_mut(); // Mutable access

// Use all Config methods
let custom_value = config.get("custom:key")?;
config.register_handler_fn("custom", |ctx| { /* ... */ Ok(()) });

What Handlers Are Pre-Registered?

The Hyprland struct automatically registers these handlers:

Root-level handlers:

  • monitor - Monitor configuration
  • env - Environment variables
  • bind, bindm, bindel, bindl, bindr, binde, bindn - Keybindings
  • windowrule, windowrulev2 - Window rules
  • layerrule - Layer rules
  • workspace - Workspace configuration
  • exec, exec-once - Commands
  • source - File inclusion
  • blurls - Blur layer surface
  • plugin - Plugin loading

Category-specific handlers:

  • animations:animation - Animation definitions
  • animations:bezier - Bezier curve definitions

Special categories:

  • device[name] - Per-device input configuration (keyed category)
  • monitor[name] - Per-monitor configuration (keyed category)
  • windowrule[name] - Window rules v3 syntax (keyed category with 80+ properties)
  • layerrule[name] - Layer rules v2 syntax (keyed category with 12 properties)

When to Use Each API

Use the Hyprland API when:

  • ✅ You're working specifically with Hyprland configurations
  • ✅ You want typed, convenient access to common config values
  • ✅ You want all Hyprland handlers pre-registered automatically
  • ✅ You're building tools for Hyprland users (config editors, validators, etc.)

Use the low-level Config API when:

  • ✅ You're implementing a different config language
  • ✅ You need full control over handler registration
  • ✅ You're working with a custom config format
  • ✅ You want minimal dependencies (no Hyprland-specific code)

Usage Examples

Basic Values

use hyprlang::Config;

let mut config = Config::new();
config.parse(r#"
    # Integers and floats
    count = 42
    opacity = 0.95

    # Strings
    terminal = kitty
    shell = "zsh"

    # Booleans
    enabled = true
    disabled = false
"#)?;

assert_eq!(config.get_int("count")?, 42);
assert_eq!(config.get_float("opacity")?, 0.95);
assert_eq!(config.get_string("terminal")?, "kitty");

Variables

use hyprlang::Config;

let mut config = Config::new();
config.parse(r#"
    $terminal = kitty
    $mod = SUPER

    # Variables are expanded when used
    my_term = $terminal
    modifier = $mod
"#)?;

// Access variables directly
let vars = config.variables();
assert_eq!(vars.get("terminal"), Some(&"kitty".to_string()));

// Or access expanded values
assert_eq!(config.get_string("my_term")?, "kitty");

Colors

use hyprlang::Config;

let mut config = Config::new();
config.parse(r#"
    color1 = rgba(33ccffee)
    color2 = rgb(255, 128, 64)
    color3 = 0xff8040ff
"#)?;

let color = config.get_color("color1")?;
println!("R: {}, G: {}, B: {}, A: {}", color.r, color.g, color.b, color.a);

Vec2 (2D Coordinates)

use hyprlang::Config;

let mut config = Config::new();
config.parse(r#"
    position1 = 100, 200
    position2 = (50, 75)
"#)?;

let pos = config.get_vec2("position1")?;
assert_eq!(pos.x, 100.0);
assert_eq!(pos.y, 200.0);

Expressions

use hyprlang::Config;

let mut config = Config::new();
config.parse(r#"
    $base = 10

    # Arithmetic expressions with {{}}
    double = {{$base * 2}}
    sum = {{5 + 3}}
    complex = {{($base + 5) * 2}}
"#)?;

assert_eq!(config.get_int("double")?, 20);
assert_eq!(config.get_int("sum")?, 8);
assert_eq!(config.get_int("complex")?, 30);

Nested Categories

use hyprlang::Config;

let mut config = Config::new();
config.parse(r#"
    general {
        border_size = 2

        gaps {
            inner = 5
            outer = 10
        }
    }
"#)?;

// Access with colon-separated paths
assert_eq!(config.get_int("general:border_size")?, 2);
assert_eq!(config.get_int("general:gaps:inner")?, 5);
assert_eq!(config.get_int("general:gaps:outer")?, 10);

Custom Handlers

use hyprlang::Config;

let mut config = Config::new();

// Register a handler for custom keywords
config.register_handler_fn("bind", |ctx| {
    println!("Bind: {}", ctx.value);
    Ok(())
});

config.parse(r#"
    bind = SUPER, Q, exec, kitty
    bind = SUPER, C, killactive
"#)?;

// Access handler calls as arrays
let binds = config.get_handler_calls("bind").unwrap();
assert_eq!(binds.len(), 2);

Category-Specific Handlers

use hyprlang::Config;

let mut config = Config::new();

// Register handlers that only work in specific categories
config.register_category_handler_fn("animations", "animation", |ctx| {
    println!("Animation: {}", ctx.value);
    Ok(())
});

config.parse(r#"
    animations {
        animation = windows, 1, 4, default
        animation = fade, 1, 3, quick
    }
"#)?;

// Handler calls are namespaced by category
let anims = config.get_handler_calls("animations:animation").unwrap();
assert_eq!(anims.len(), 2);

Special Categories

use hyprlang::{Config, SpecialCategoryDescriptor};

let mut config = Config::new();

// Register a special category
config.register_special_category(
    SpecialCategoryDescriptor::keyed("device", "name")
);

config.parse(r#"
    device[mouse] {
        sensitivity = 0.5
        accel_profile = flat
    }

    device[keyboard] {
        repeat_rate = 50
        repeat_delay = 300
    }
"#)?;

// Access keyed category instances
let mouse = config.get_special_category("device", "mouse")?;
println!("Mouse sensitivity: {:?}", mouse.get("sensitivity"));

Windowrule v3 / Layerrule v2 (Hyprland Feature)

The new windowrule v3 and layerrule v2 syntax uses special category blocks:

use hyprlang::Hyprland;

let mut hypr = Hyprland::new();

hypr.parse(r#"
    # New v3 syntax - windowrule as special category
    windowrule[float-terminals] {
        # Match properties
        match:class = ^(kitty|alacritty)$
        match:floating = false

        # Effect properties
        float = true
        size = 800 600
        center = true
        opacity = 0.95
        rounding = 10
        border_color = rgba(33ccffee)
    }

    # Layerrule v2 syntax
    layerrule[blur-waybar] {
        match:namespace = waybar
        blur = true
        ignorealpha = 0.5
    }
"#)?;

// Access windowrules
let names = hypr.windowrule_names();  // vec!["float-terminals"]
let rule = hypr.get_windowrule("float-terminals")?;

// Get properties with type safety
let class_match = rule.get_string("match:class")?;  // "^(kitty|alacritty)$"
let is_float = rule.get_int("float")?;               // 1 (true)
let opacity = rule.get_float("opacity")?;             // 0.95
let color = rule.get_color("border_color")?;          // Color { r: 51, g: 204, b: 255, a: 238 }

// Old v2 handler syntax still works for backward compatibility
hypr.parse(r#"
    windowrulev2 = float, class:^(kitty)$
"#)?;

let v2_rules = hypr.all_windowrulesv2();

Supported Properties

Windowrule v3 - Match Properties (19):

  • match:class, match:title, match:initial_class, match:initial_title
  • match:floating, match:xwayland, match:fullscreen, match:pinned
  • match:focus, match:group, match:modal, match:tag
  • match:fullscreenstate_internal, match:fullscreenstate_client
  • match:on_workspace, match:content, match:xdg_tag
  • match:namespace, match:exec_token

Windowrule v3 - Effect Properties (60+ with aliases):

  • Static: float, tile, fullscreen, maximize, move, size, center, pseudo, monitor, workspace, pin, group, etc.
  • Dynamic: rounding, opacity, border_color, border_size, max_size, min_size, animation, no_blur, no_shadow, xray, etc.

Layerrule v2 - Match Properties (6):

  • match:namespace, match:address, match:class, match:title, match:monitor, match:layer

Layerrule v2 - Effect Properties (6):

  • blur, ignorealpha, ignorezero, animation, noanim, xray

### Source Directive

```hyprlang
 # .colors.conf
 
 $borderSize = 3
use hyprlang::{Config, ConfigOptions};
use std::path::PathBuf;

let mut options = ConfigOptions::default();
options.base_dir = Some(PathBuf::from("/path/to/config"));

let mut config = Config::with_options(options);

// This will include another config file
config.parse(r#"
    source = ./colors.conf

    general {
        border_size = $borderSize
    }
"#)?;

Mutation & Serialization (Optional Feature)

Enable the mutation feature to modify configurations and save them:

use hyprlang::{Config, ConfigValue};

let mut config = Config::new();
config.parse(r#"
    $GAPS = 10
    border_size = 3
    opacity = 0.9
"#)?;

// ===== Mutate Values =====
config.set_int("border_size", 5);
config.set_float("opacity", 1.0);
config.set("new_key", ConfigValue::String("value".to_string()));

// Remove values
let old = config.remove("opacity")?;

// ===== Mutate Variables =====
// Method 1: Direct mutation
config.set_variable("GAPS".to_string(), "15".to_string());

// Method 2: Mutable reference
if let Some(mut gaps) = config.get_variable_mut("GAPS") {
    gaps.set("20")?;  // Now GAPS = 20
}

// ===== Mutate Handlers =====
config.register_handler_fn("bind", |_| Ok(()));
config.add_handler_call("bind", "SUPER, Q, exec, terminal".to_string())?;
config.remove_handler_call("bind", 0)?;  // Remove first bind

// ===== Serialize & Save =====
let output = config.serialize();  // Get string representation
config.save_as("config_modified.conf")?;  // Save to file

// Verify round-trip
let mut config2 = Config::new();
config2.parse_file(Path::new("config_modified.conf"))?;
assert_eq!(config2.get_int("border_size")?, 5);

Run the comprehensive example:

cargo run --example mutation_example --features mutation

Parse from File

use hyprlang::Config;
use std::path::Path;

let mut config = Config::new();
config.parse_file(Path::new("config.conf"))?;

// Access all keys
for key in config.keys() {
    println!("Key: {}", key);
}

Hyprland Feature (Optional)

When the hyprland feature is enabled, you can use the high-level Hyprland struct:

use hyprlang::Hyprland;
use std::path::Path;

// Create a new Hyprland config (automatically registers all handlers)
let mut hypr = Hyprland::new();

// Parse your Hyprland config
hypr.parse_file(Path::new("~/.config/hypr/hyprland.conf"))?;

// Access config with typed methods
let border_size = hypr.general_border_size()?;
let gaps_in = hypr.general_gaps_in()?;
let active_border = hypr.general_active_border_color()?;

// Get all binds as an array
let binds = hypr.all_binds();
for bind in binds {
    println!("Bind: {}", bind);
}

// Get all animations
let animations = hypr.all_animations();
println!("Found {} animations", animations.len());

// Get all window rules
let rules = hypr.all_windowrules();
for rule in rules {
    println!("Rule: {}", rule);
}

// Access variables
let terminal = hypr.get_variable("terminal");

The Hyprland struct provides convenient typed access to:

  • General settings: border_size, gaps, colors, layout, etc.
  • Decoration: rounding, opacity, blur settings
  • Animations: enabled status, all animations, all beziers
  • Input: keyboard layout, mouse settings, touchpad
  • Layout: dwindle and master layout settings
  • Handlers: all binds, windowrules, monitors, env vars, exec-once, etc.
  • Variables: all user-defined variables

Configuration Options

use hyprlang::{Config, ConfigOptions};
use std::path::PathBuf;

let mut options = ConfigOptions::default();

// Collect all errors instead of stopping at the first one
options.throw_all_errors = false;

// Allow parsing after initial parse
options.allow_dynamic_parsing = true;

// Base directory for resolving source directives
options.base_dir = Some(PathBuf::from("/path/to/config"));

let config = Config::with_options(options);

API Overview

Main Types

  • Config - Main configuration manager
  • ConfigValue - Enum representing all value types
    • Int(i64) - Integer value
    • Float(f64) - Float value
    • String(String) - String value
    • Vec2(Vec2) - 2D coordinate
    • Color(Color) - RGBA color
    • Custom { type_name, value } - Custom value type
  • Color - RGBA color (r, g, b, a)
  • Vec2 - 2D coordinate (x, y)

Key Methods

// Parsing
config.parse(content: &str) -> Result<()>
config.parse_file(path: &Path) -> Result<()>

// Getting values
config.get(key: &str) -> Result<&ConfigValue>
config.get_int(key: &str) -> Result<i64>
config.get_float(key: &str) -> Result<f64>
config.get_string(key: &str) -> Result<&str>
config.get_vec2(key: &str) -> Result<Vec2>
config.get_color(key: &str) -> Result<Color>

// Setting values
config.set(key: impl Into<String>, value: ConfigValue)
config.set_variable(name: String, value: String)

// Mutation (requires `mutation` feature)
config.set_int(key, value: i64)
config.set_float(key, value: f64)
config.set_string(key, value: impl Into<String>)
config.remove(key: &str) -> Result<ConfigValue>
config.get_variable_mut(name: &str) -> Option<MutableVariable>
config.remove_variable(name: &str) -> Option<String>
config.add_handler_call(handler, value: String) -> Result<()>
config.remove_handler_call(handler: &str, index: usize) -> Result<String>
config.get_special_category_mut(category, key) -> Result<MutableCategoryInstance>

// Serialization (requires `mutation` feature)
config.serialize() -> String
config.save() -> Result<()>
config.save_as(path: impl AsRef<Path>) -> Result<()>

// Querying
config.keys() -> Vec<&str>
config.variables() -> &HashMap<String, String>
config.has(key: &str) -> bool

// Handlers
config.register_handler_fn(keyword, handler_fn)
config.register_category_handler_fn(category, keyword, handler_fn)
config.get_handler_calls(handler: &str) -> Option<&Vec<String>>
config.all_handler_calls() -> &HashMap<String, Vec<String>>

// Special categories
config.register_special_category(descriptor)
config.get_special_category(category: &str, key: &str) -> Result<HashMap<String, &ConfigValue>>

Examples

The repository includes several examples demonstrating different features:

examples/pretty_print.rs

A comprehensive example showing a complete configuration with all features:

cargo run --example pretty_print

examples/parse_hyprland.rs

Parse and pretty-print a real Hyprland configuration file:

cargo run --example parse_hyprland

This example demonstrates:

  • Parsing complex real-world configs
  • Variable handling
  • Nested categories
  • Handler calls (binds, windowrules, etc.)
  • Beautiful formatted output

examples/hyprland_api.rs

Demonstrate the high-level Hyprland API (requires hyprland feature):

cargo run --example hyprland_api --features hyprland

This example demonstrates:

  • Using the Hyprland struct for typed config access
  • Accessing general, decoration, animation, and input settings
  • Getting all binds, windowrules, and other handler calls
  • Working with variables
  • Comprehensive display of all Hyprland configuration options

examples/mutation_example.rs

Comprehensive example demonstrating mutation and serialization (requires mutation feature):

cargo run --example mutation_example --features mutation

This example demonstrates:

  • Mutating configuration values (both direct setters and mutable references)
  • Mutating variables using both API styles
  • Adding and removing handler calls
  • Serializing configurations to strings
  • Saving configurations to files
  • Round-trip verification (parse → mutate → save → parse)

Testing

Run the full test suite:

cargo test

# Run with all features enabled
cargo test --all-features

The project includes 171 tests with 100% pass rate:

  • 52 unit tests covering core functionality
  • 11 conditional directive tests
  • 11 expression escaping tests
  • 15 windowrule v3 / layerrule v2 tests
  • 12 Hyprland config tests
  • 10 mutation & round-trip serialization tests
  • 19 parsing edge case tests
  • 41 documentation tests

All tests from the original Hyprlang C++ implementation have been ported and pass successfully, plus additional tests for new features like expression escaping, negated conditionals, windowrule v3 syntax, and comprehensive edge case coverage.

Grammar

The parser is implemented using pest with a PEG grammar. The grammar file is located at src/hyprlang.pest.

Key syntax features:

  • Comments: # for single-line, ## for documentation
  • Variables: $VAR = value, $env:PATH (environment variables)
  • Expressions: {{expr}} with arithmetic operators (+, -, *, /)
  • Expression escaping: \{{}} or {\{}} for literal braces
  • Categories: category { ... } (nested supported)
  • Special categories: category[key] { ... } (keyed, static, anonymous)
  • Assignments: key = value
  • Handlers: keyword = value (with optional flags: keyword[flag])
  • Source directive: source = path
  • Conditional directives: # hyprlang if VAR, # hyprlang if !VAR, # hyprlang endif
  • Error suppression: # hyprlang noerror true/false

License

This project is a reimplementation of Hyprlang in Rust, based on the original C++ implementation by the Hyprland team.

Contributing

Contributions are welcome! Please ensure all tests pass before submitting a PR:

cargo test
cargo clippy
cargo fmt --check

Acknowledgments

  • Hyprland - The original project
  • Hyprlang - The C++ reference implementation
  • pest - The parser generator used in this project