From 9617fcc6b7d0c1fc9d93b5f14a7afc41616355ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 23:26:08 +0000 Subject: [PATCH 1/2] Initial plan From d91f461b303af3116fab4936c94341320aac5e2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 23:32:03 +0000 Subject: [PATCH 2/2] Comprehensive rewrite of README.md with improved structure and accessibility Co-authored-by: vic <331+vic@users.noreply.github.com> --- README.md | 610 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 313 insertions(+), 297 deletions(-) diff --git a/README.md b/README.md index a5addf4..566e941 100644 --- a/README.md +++ b/README.md @@ -1,446 +1,462 @@ -# 🌲🌴 import-tree 🎄🌳 +# 🌲 import-tree 🌳 -> Helper functions for import of [Nixpkgs module system](https://nix.dev/tutorials/module-system/) modules under a directory recursively +[![Build Status](https://github.com/vic/import-tree/actions/workflows/test.yml/badge.svg)](https://github.com/vic/import-tree/actions/workflows/test.yml) +[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -- Flake callable; Easy to use, intuitive for the most common use case: `inputs.import-tree ./modules` -- Module class agnostic; can be used for NixOS, nix-darwin, home-manager, flake-parts, NixVim. -- Can be used outside flakes as a dependencies-free lib; Just import our `./default.nix`. -- Can be used to list other file types, not just `.nix`. See `.initFilter`, `.files` API. -- Extensible API. import-tree objects are customizable. See `.addAPI`. +> **Powerful and extensible helper for importing [Nixpkgs module system](https://nix.dev/tutorials/module-system/) modules from directory trees** -## Quick Usage (with flake-parts) +**import-tree** simplifies the management of Nix module collections by automatically discovering and importing `.nix` files from directory structures. Whether you're organizing NixOS configurations, home-manager setups, or flake-parts modules, import-tree provides an intuitive and extensible API to streamline your module imports. -This example shows how to load all nix files inside `./modules`, following the -[Dendritic Pattern](https://github.com/mightyiam/dendritic) +Perfect for implementing the [Dendritic Pattern](https://github.com/mightyiam/dendritic) where each file represents a discrete, composable module, making your Nix configurations more modular and maintainable. -```nix -{ - inputs.import-tree.url = "github:vic/import-tree"; - inputs.flake-parts.url = "github:hercules-ci/flake-parts"; - - outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules); -} -``` +## Features at a Glance -## Quick Usage (outside a modules evaluation) - -If you want to get a list of nix files programmatically outside of a modules evaluation, -you can use the import-tree API (read below for more). - -```nix -(import-tree.withLib pkgs.lib).leafs ./modules # => list of .nix files -``` +### 🚀 **Easy to Use** +- **One-liner imports**: `inputs.import-tree ./modules` for the most common use case +- **Flake-ready**: Works seamlessly with modern Nix flakes +- **Zero dependencies**: Can be used outside flakes by importing `./default.nix` -## Ignored files +### 🎯 **Universal Compatibility** +- **Module system agnostic**: Works with NixOS, nix-darwin, home-manager, flake-parts, NixVim +- **Path flexible**: Accepts files, directories, or nested lists of paths +- **Mixed usage**: Can import other file types beyond `.nix` files -By default, paths having a component that begins with an underscore (`/_`) are ignored. +### 🔧 **Highly Customizable** +- **Extensible API**: Add custom methods with `.addAPI` +- **Smart filtering**: Built-in filters with customizable patterns +- **Transformation support**: Map functions over discovered files +- **Ignore patterns**: Sensible defaults with full customization -This can be changed by using `.initFilter` API. +### 📦 **Library-Friendly** +- **Pre-configured trees**: Library authors can ship ready-to-use configurations +- **Community conventions**: Support for domain-specific naming and organization patterns +- **API composition**: Chain and combine multiple import-tree instances -
- +## Quick Start -## API usage +### For Flake Users -The following goes recursively through `./modules` and imports all `.nix` files. +The simplest way to use import-tree with flake-parts to import all modules from a directory: ```nix -# Usage as part of any nix module system. -{config, ...} { - imports = [ (import-tree ./modules) ]; +{ + inputs = { + import-tree.url = "github:vic/import-tree"; + flake-parts.url = "github:hercules-ci/flake-parts"; + }; + + outputs = inputs: + inputs.flake-parts.lib.mkFlake { inherit inputs; } + # This imports all .nix files from ./modules recursively + (inputs.import-tree ./modules); } ``` -For more advanced usage, `import-tree` can be configured via its extensible API. - - +### For Non-Flake Users -## Obtaining the API - -When used as a flake, the flake outputs attrset is the primary callable. -Otherwise, importing the `default.nix` that is at the root of this repository will evaluate into the same attrset. -This callable attrset is referred to as `import-tree` in this documentation. - -## `import-tree` - -Takes a single argument: path or deeply nested list of path. -Returns a module that imports the discovered files. -For example, given the following file tree: - -``` -default.nix -modules/ - a.nix - subdir/ - b.nix -``` - -The following +You can use import-tree in traditional Nix setups by importing the library directly: ```nix -{lib, config, ...} { +let + # Import the library + import-tree = import (fetchGit "https://github.com/vic/import-tree"); + + # Get list of module files (requires lib for non-module usage) + moduleFiles = (import-tree.withLib pkgs.lib).files ./modules; +in +{ + # Use in any module system imports = [ (import-tree ./modules) ]; } ``` -Is similar to +### Basic Usage in Module Systems + +Use import-tree anywhere you need to import modules: ```nix -{lib, config, ...} { +# NixOS configuration +{ config, pkgs, ... }: { imports = [ - { - imports = [ - ./modules/a.nix - ./modules/subdir/b.nix - ]; - } + # Import all .nix files from ./modules + (import-tree ./modules) + + # Can also import from multiple directories + (import-tree [ ./modules ./extra-modules ]) ]; } ``` -If given a deeply nested list of paths the list will be flattened and results concatenated. -The following is valid usage: +## Understanding the Default Behavior -```nix -{lib, config, ...} { - imports = [ (import-tree [./a [./b]]) ]; -} -``` +### File Discovery -Other import-tree objects can also be given as arguments (or in lists) as if they were paths. +By default, import-tree: +- **Recursively scans** directories for `.nix` files +- **Ignores paths** containing components that start with underscore (`/_`) +- **Returns a module** that imports all discovered files -As an special case, when the single argument given to an `import-tree` object is an -attribute-set containing an `options` attribute, the `import-tree` object -assumes it is being evaluated as a module. This way, a pre-configured `import-tree` can -also be used directly in a list of module imports. +### Default Ignore Pattern -This is useful for authors exposing pre-configured `import-tree`s that users can directly -add to their import list or continue configuring themselves using its API. +Files and directories starting with underscore are ignored by default: +- `_private.nix` ❌ (ignored) +- `modules/_internal/config.nix` ❌ (ignored) +- `modules/public.nix` ✅ (included) -```nix -let - # imagine this configured tree comes from some author's flake or library. - # library author can extend an import-tree with custom API methods - # according to the library's directory and file naming conventions. - configured-tree = import-tree.addAPI { - # the knowledge of where modules are located inside the library structure - # or which filters/regexes/transformations to apply are abstracted - # from the user by the author providing a meaningful API. - maximal = self: self.addPath ./modules; - minimal = self: self.maximal.filter (lib.hasInfix "minimal"); - }; -in { - # the library user can directly import or further configure an import-tree. - imports = [ configured-tree.minimal ]; -} -``` +This convention allows you to keep private/internal files alongside public modules without accidentally importing them. -## Configurable behavior +## Advanced Usage & API -`import-tree` objects with custom behavior can be obtained using a builder pattern. -For example: +### Filtering Files + +Control which files are imported using the `.filter` method: ```nix +# Only import files containing "feature" in their path +import-tree.filter (lib.hasInfix "feature") ./modules + +# Chain multiple filters (all must match) lib.pipe import-tree [ - (i: i.map lib.traceVal) # trace all paths. useful for debugging what is being imported. - (i: i.filter (lib.hasInfix ".mod.")) # filter nix files by some predicate - (i: i ./modules) # finally, call the configured import-tree with a path + (i: i.filter (lib.hasInfix "desktop")) + (i: i.filter (lib.hasSuffix "default.nix")) + (i: i ./modules) ] ``` -Here is a simpler but less readable equivalent: +### Using Regular Expressions + +Use `.match` for more complex pattern matching: ```nix -((import-tree.map lib.traceVal).filter (lib.hasInfix ".mod.")) ./modules +# Import files matching specific patterns +import-tree.match ".*/desktop@(gnome|kde)\.nix" ./modules ``` -### `import-tree.filter` and `import-tree.filterNot` - -`filter` takes a predicate function `path -> bool`. Only paths for which the filter returns `true` are selected: +### Transforming Files -> \[!NOTE\] -> Only files with suffix `.nix` are candidates. +Use `.map` to transform paths before importing: ```nix -# import-tree.filter : (path -> bool) -> import-tree - -import-tree.filter (lib.hasInfix ".mod.") ./some-dir +# Add debugging information to each import +import-tree.map (path: { + imports = [ path ]; + # Track which files were automatically imported + _importedPaths = [ (toString path) ]; +}) ./modules ``` -`filter` can be applied multiple times, in which case only the files matching _all_ filters will be selected: +### Working with File Lists + +Get the list of discovered files without importing them: ```nix -lib.pipe import-tree [ - (i: i.filter (lib.hasInfix ".mod.")) - (i: i.filter (lib.hasSuffix "default.nix")) - (i: i ./some-dir) -] +# Get list of .nix files (remember to use withLib for non-module contexts) +nixFiles = (import-tree.withLib lib).files ./modules; + +# Process non-Nix files +jsFiles = lib.pipe import-tree [ + (i: i.initFilter (lib.hasSuffix ".js")) # Look for .js files + (i: i.withLib lib) # Required for .files + (i: i.files ./src) # Get the file list +]; ``` -Or, in a simpler but less readable way: +### Pre-configuring Paths + +Use `.addPath` to create import-tree instances with predefined search paths: ```nix -(import-tree.filter (lib.hasInfix ".mod.")).filter (lib.hasSuffix "default.nix") ./some-dir -``` +# Create a configured tree +myTree = import-tree.addPath ./modules; -See also `import-tree.initFilter`. +# Use it with empty arguments or additional paths +config = { + imports = [ + myTree.result # Same as: myTree [] + (myTree ./extra-modules) # Includes both ./modules and ./extra-modules + ]; +}; +``` -### `import-tree.match` and `import-tree.matchNot` +### Custom API Extensions -`match` takes a regular expression. The regex should match the full path for the path to be selected. Matching is done with `builtins.match`. +Create domain-specific APIs using `.addAPI`: ```nix -# import-tree.match : regex -> import-tree - -import-tree.match ".*/[a-z]+@(foo|bar)\.nix" ./some-dir +# Define a custom API for feature management +featureTree = import-tree.addAPI { + # Method to get all available features + all = self: self.addPath ./features; + + # Method to enable specific features + enable = self: featureName: + self.all.filter (lib.hasInfix "+${featureName}"); + + # Method for minimal feature set + minimal = self: self.enable "minimal"; + + # Method for desktop features + desktop = self: self.enable "desktop"; +}; + +# Usage: +{ + imports = [ + featureTree.minimal # Only minimal features + featureTree.desktop # Only desktop features + ]; +} ``` -`match` can be applied multiple times, in which case only the paths matching _all_ regex patterns will be selected, and can be combined with any number of `filter`, in any order. +## Why Use import-tree? -### `import-tree.map` +### The Dendritic Pattern -`map` can be used to transform each path by providing a function. +import-tree was inspired by the [Dendritic Pattern](https://github.com/mightyiam/dendritic), a modular approach where each file represents a focused, composable module. This pattern offers several advantages: -e.g. to convert the path into a module explicitly: +- **Better organization**: Each file has a single responsibility +- **Easier maintenance**: Changes are isolated to specific files +- **Improved reusability**: Modules can be easily shared across configurations +- **Cleaner git history**: Changes are more granular and easier to track -```nix -# import-tree.map : (path -> any) -> import-tree +Learn more from: +- [@mightyiam's original post](https://discourse.nixos.org/t/pattern-each-file-is-a-flake-parts-module/61271) +- [@drupol's practical experience](https://not-a-number.io/2025/refactoring-my-infrastructure-as-code-configurations/) +- [@vic's implementation insights](https://discourse.nixos.org/t/how-do-you-structure-your-nixos-configs/65851/8) -import-tree.map (path: { - imports = [ path ]; - # assuming such an option is declared - automaticallyImportedPaths = [ path ]; -}) -``` +### Library Distribution & Community Configurations + +The extensible API enables library authors to create domain-specific import-tree configurations, similar to popular editor distributions (like Spacemacs or LazyVim). -`map` can be applied multiple times, composing the transformations: +#### Example: Editor Configuration Distribution ```nix -lib.pipe import-tree [ - (i: i.map (lib.removeSuffix ".nix")) - (i: i.map builtins.stringLength) -] ./some-dir +# editor-distro's flake output +{ inputs, lib, ... }: +let + # Pre-configured import-tree with editor-specific API + modules-tree = lib.pipe inputs.import-tree [ + (i: i.addPath ./modules) + (i: i.addAPI { + # Language-specific features + ruby = self: self.filter (lib.hasInfix "+ruby"); + python = self: self.filter (lib.hasInfix "+python"); + + # Editor preferences + vim = self: self.filter (lib.hasInfix "+vim"); + emacs = self: self.filter (lib.hasInfix "+emacs"); + + # Feature toggles + minimal = self: self.filterNot (lib.hasInfix "+bloat"); + ai-assist = self: self.filter (lib.hasInfix "+copilot"); + }) + ]; +in { + lib = { inherit modules-tree; }; +} ``` -The above example first removes the `.nix` suffix from all selected paths, then takes their lengths. - -Or, in a simpler but less readable way: +#### Using Distributed Configurations ```nix -((import-tree.map (lib.removeSuffix ".nix")).map builtins.stringLength) ./some-dir +# Consumer flake +{ inputs, ... }: +let + editor = inputs.editor-distro.lib.modules-tree; +in { + imports = [ + # Compose features declaratively + (editor.vim.ruby.minimal) + + # Mix and match as needed + (editor.python.ai-assist) + ]; +} ``` -`map` can be combined with any number of `filter` and `match` calls, in any order, but the (composed) transformation is applied _after_ the filters, and only to the paths that match all of them. +This approach enables community-driven configuration sharing while maintaining the flexibility to customize for specific needs. + +## API Reference -### `import-tree.addPath` +### Core Methods -`addPath` can be used to prepend paths to be filter as a setup for import-tree. -This function can be applied multiple times. +#### `import-tree ` +**Primary function**: Takes paths and returns a module that imports discovered files. ```nix -# import-tree.addPath : (path_or_list_of_paths) -> import-tree +# Single path +import-tree ./modules -# Both of these result in the same imported files. -# however, the first adds ./vendor as a *pre-configured* path. -# and the final user can supply ./modules or [] empty. -(import-tree.addPath ./vendor) ./modules -import-tree [./vendor ./modules] -``` +# Multiple paths +import-tree [ ./modules ./features ] -### `import-tree.addAPI` +# Nested paths (flattened automatically) +import-tree [ ./modules [ ./features ./plugins ] ] +``` -`addAPI` extends the current import-tree object with new methods. -The API is cumulative, meaning that this function can be called multiple times. +### Configuration Methods -`addAPI` takes an attribute set of functions taking a single argument: -`self` which is the current import-tree object. +#### `.filter ` +**Filter files** by a predicate function `path -> bool`. ```nix -# import-tree.addAPI : api-attr-set -> import-tree - -import-tree.addAPI { - maximal = self: self.addPath ./modules; - feature = self: featureName: self.maximal.filter (lib.hasInfix feature); - minimal = self: self.feature "minimal"; -} +import-tree.filter (lib.hasInfix "desktop") ./modules ``` -on the previous API, users can call `import-tree.feature "+vim"` or `import-tree.minimal`, etc. +#### `.filterNot ` +**Exclude files** matching the predicate. -### `import-tree.withLib` +```nix +import-tree.filterNot (lib.hasInfix "experimental") ./modules +``` -> \[!NOTE\] -> `withLib` is required prior to invocation of any of `.leafs` or `.pipeTo`. -> Because with the use of those functions the implementation does not have access to a `lib` that is provided as a module argument. +#### `.match ` +**Filter by regex** pattern (uses `builtins.match`). ```nix -# import-tree.withLib : lib -> import-tree - -import-tree.withLib pkgs.lib +import-tree.match ".*/feature-[a-z]+\.nix" ./modules ``` -### `import-tree.pipeTo` - -`pipeTo` takes a function that will receive the list of paths. -When configured with this, `import-tree` will not return a nix module but the result of the function being piped to. +#### `.map ` +**Transform each path** with a function `path -> any`. ```nix -# import-tree.pipeTo : ([paths] -> any) -> import-tree - -import-tree.pipeTo lib.id # equivalent to `.leafs` +import-tree.map (path: { imports = [ path ]; source = toString path; }) ``` -### `import-tree.leafs` - -`leafs` takes no arguments, it is equivalent to calling `import-tree.pipeTo lib.id`. That is, instead of producing a nix module, just return the list of results. +#### `.addPath ` +**Add paths** to be searched (accumulates with multiple calls). ```nix -# import-tree.leafs : import-tree - -import-tree.leafs +(import-tree.addPath ./base).addPath ./extras ``` -### `import-tree.new` - -Returns a fresh import-tree with empty state. If you previously had any path, lib, filter, etc, -you might need to set them on the new empty tree. +#### `.initFilter ` +**Replace the initial filter** (default: `.nix` files, ignore `/_` paths). -### `import-tree.initFilter` +```nix +# Look for .md files instead +import-tree.initFilter (lib.hasSuffix ".md") -*Replaces* the initial filter which defaults to: Include files with `.nix` suffix and not having `/_` infix. +# Custom ignore pattern +import-tree.initFilter (p: lib.hasSuffix ".nix" p && !lib.hasInfix "/private/") +``` -_NOTE_: initFilter is non-accumulating and is the *first* filter to run before those accumulated via `filter`/`match`. +### Extension Methods -You can use this to make import-tree scan for other file types or change the ignore convention. +#### `.addAPI ` +**Extend the API** with custom methods. ```nix -# import-tree.initFilter : (path -> bool) -> import-tree - -import-tree.initFilter (p: lib.hasSuffix ".nix" p && !lib.hasInfix "/ignored/") # nix files not inside /ignored/ -import-tree.initFilter (lib.hasSuffix ".md") # scan for .md files everywhere, nothing ignored. +import-tree.addAPI { + stable = self: self.filter (lib.hasInfix "stable"); + beta = self: self.filter (lib.hasInfix "beta"); +} ``` -### `import-tree.files` - -A shorthand for `import-tree.leafs.result`. Returns a list of matching files. +#### `.withLib ` +**Provide lib instance** (required for `.files`, `.leafs`, `.pipeTo` methods). -This can be used when you don't want to import the tree, but just get a list of files from it. +```nix +import-tree.withLib pkgs.lib +``` -Useful for listing files other than `.nix`, for example, for passing all `.js` files to a minifier: +### Output Methods -_TIP_: remember to use `withLib` when *not* using import-tree as a module import. +#### `.files` +**Get list of matching files** instead of importing them. ```nix -# import-tree.files : [ ] - -# paths to give to uglify-js -lib.pipe import-tree [ - (i: i.initFilter (lib.hasSuffix ".js")) # look for .js files. ignore nothing. - (i: i.addPath ./out) # under the typescript compiler outDir - (i: i.withLib lib) # set lib since we are not importing modules. - (i: i.files) -] -# => list of all .js files +(import-tree.withLib lib).files ./modules +# => [ /path/to/modules/a.nix /path/to/modules/b.nix ] ``` -### `import-tree.result` +#### `.result` +**Apply configured tree** with empty path list (useful for pre-configured trees). -Exactly the same as calling the import-tree object with an empty list `[ ]`. -This is useful for import-tree objects that already have paths configured via `.addPath`. +```nix +(import-tree.addPath ./modules).result +# Same as: (import-tree.addPath ./modules) [] +``` + +#### `.pipeTo ` +**Process file list** with a custom function. ```nix -# import-tree.result : +import-tree.pipeTo (files: lib.length files) ./modules +# => number of discovered files +``` -# these two are exactly the same: -(import-tree.addPath ./modules).result -(import-tree.addPath ./modules) [ ] +#### `.new` +**Create fresh instance** with empty configuration. + +```nix +import-tree.addPath ./modules).new.addPath ./other +# Only includes ./other (previous config cleared) ``` -
+## Testing -## Why +import-tree uses [checkmate](https://github.com/vic/checkmate) for comprehensive testing. -Importing a tree of nix modules has some advantages: +### Running Tests -### Dendritic Pattern: each file is a flake-parts module +```bash +# Run the full test suite +nix flake check path:checkmate --override-input target path:. -[That pattern](https://github.com/mightyiam/dendritic) was the original inspiration for this library. -See [@mightyiam's post](https://discourse.nixos.org/t/pattern-each-file-is-a-flake-parts-module/61271), -[@drupol's blog post](https://not-a-number.io/2025/refactoring-my-infrastructure-as-code-configurations/) and -[@vic's reply](https://discourse.nixos.org/t/how-do-you-structure-your-nixos-configs/65851/8) -to learn about the Dendritic pattern advantages. +# Format code (if you're contributing) +nix run github:vic/checkmate#fmt +``` -### Sharing pre-configured subtrees of modules +### Test Structure -
- +The test suite in [`checkmate.nix`](checkmate.nix) covers: -Since the import-tree API is _extensible_ and lets you add paths or -filters at configuration time, configuration-library authors can -provide custom import-tree instances with an API suited for their -particular idioms. +- **Core functionality**: File discovery, filtering, module generation +- **API methods**: All configuration and extension methods +- **Edge cases**: Invalid inputs, empty directories, mixed path types +- **Integration**: Usage within module systems -@vic is using this on [Dendrix](https://github.com/vic/dendrix) for [community conventions](https://github.com/vic/dendrix/blob/main/dev/modules/community/_pipeline.nix) on tagging files. +## Contributing - +We welcome contributions! Here's how to get started: -This would allow us to have community-driven *sets* of configurations, -much like those popular for editors: spacemacs/lazy-vim distributions. +### Development Setup -Imagine an editor distribution exposing the following flake output: +1. **Clone the repository**: + ```bash + git clone https://github.com/vic/import-tree.git + cd import-tree + ``` -```nix -# editor-distro's flakeModule -{inputs, lib, ...}: -let - flake.lib.modules-tree = lib.pipe inputs.import-tree [ - (i: i.addPath ./modules) - (i: i.addAPI { inherit on off exclusive; }) - (i: i.addAPI { ruby = self: self.on "ruby"; }) - (i: i.addAPI { python = self: self.on "python"; }) - (i: i.addAPI { old-school = self: self.off "copilot"; }) - (i: i.addAPI { vim-btw = self: self.exclusive "vim" "emacs"; }) - ]; +2. **Run tests** to ensure everything works: + ```bash + nix flake check path:checkmate --override-input target path:. + ``` - on = self: flag: self.filter (lib.hasInfix "+${flag}"); - off = self: flag: self.filterNot (lib.hasInfix "+${flag}"); - exclusive = self: onFlag: offFlag: lib.pipe self [ - (self: on self onFlag) - (self: off self offFlag) - ]; -in -{ - inherit flake; -} -``` +### Making Changes -Users of such distribution can do: +1. **Follow the existing code style** - use the formatter: + ```bash + nix run github:vic/checkmate#fmt + ``` -```nix -# consumer flakeModule -{inputs, lib, ...}: let - ed-tree = inputs.editor-distro.lib.modules-tree; -in { - imports = [ - (ed-tree.vim-btw.old-school.on "rust") - ]; -} -``` +2. **Add tests** for new functionality in `checkmate.nix` -
+3. **Update documentation** if you change the API -## Testing +4. **Test your changes** thoroughly before submitting -`import-tree` uses [`checkmate`](https://github.com/vic/checkmate) for testing. +### Submitting Contributions -The test suite can be found in [`checkmate.nix`](checkmate.nix). To run it locally: +1. Create a focused pull request with a clear description +2. Ensure all tests pass +3. Include examples for new features +4. Update documentation as needed -```sh -nix flake check path:checkmate --override-input target path:. -``` +For questions or discussions, feel free to open an issue! -Run the following to format files: +## License -```sh -nix run github:vic/checkmate#fmt -``` +Licensed under the [Apache License 2.0](LICENSE). \ No newline at end of file