{"id":44550256,"url":"https://github.com/erichutchins/fstsed","last_synced_at":"2026-02-13T19:28:21.655Z","repository":{"id":65696387,"uuid":"510183814","full_name":"erichutchins/fstsed","owner":"erichutchins","description":"Search and enrich/decorate strings at grep speed","archived":false,"fork":false,"pushed_at":"2026-01-24T05:55:43.000Z","size":88,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-24T16:46:33.904Z","etag":null,"topics":["cli","command-line","dfir","fst","grep","sed"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erichutchins.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-07-04T02:13:28.000Z","updated_at":"2026-01-24T05:55:38.000Z","dependencies_parsed_at":"2024-01-04T04:28:01.101Z","dependency_job_id":"34a7da92-ba90-4d75-812a-456dd580bfaa","html_url":"https://github.com/erichutchins/fstsed","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/erichutchins/fstsed","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erichutchins%2Ffstsed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erichutchins%2Ffstsed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erichutchins%2Ffstsed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erichutchins%2Ffstsed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erichutchins","download_url":"https://codeload.github.com/erichutchins/fstsed/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erichutchins%2Ffstsed/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29415411,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-13T06:24:03.484Z","status":"ssl_error","status_checked_at":"2026-02-13T06:23:12.830Z","response_time":78,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line","dfir","fst","grep","sed"],"created_at":"2026-02-13T19:28:20.276Z","updated_at":"2026-02-13T19:28:21.637Z","avatar_url":"https://github.com/erichutchins.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fstsed\n\nSearch **and enrich** at near-ripgrep speed\n\n[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE)\n[![Rust](https://img.shields.io/badge/rust-1.93%2B-orange.svg)](https://www.rust-lang.org)\n[![Claude](https://img.shields.io/badge/Claude-D97757?logo=claude\u0026logoColor=fff)](https://claude.ai)\n[![Gemini](https://img.shields.io/badge/Gemini-8E75FF?logo=googlegemini\u0026logoColor=fff)](https://antigravity.google)\n[![Zed](https://img.shields.io/badge/Zed-000?logo=zedindustries\u0026logoColor=fff)](https://zed.dev)\n\n**fstsed** is a high-performance search-and-enrichment tool for cases where every match needs its own context. It's not a faster ripgrep -- instead, fstsed brings ripgrep-class performance to a different problem:\n\n\u003e Searching for many strings at once, where each string carries its own structured metadata.\n\nfstsed is built on [BurntSushi's `fst` crate](https://github.com/BurntSushi/fst) and is inspired by the blog post _[Index 1,600,000,000 Keys with Automata and Rust](https://burntsushi.net/transducers/)_ and of course [ripgrep](https://github.com/BurntSushi/ripgrep) itself.\n\nMy other tool, [**geoipsed**](https://github.com/erichutchins/geoipsed), uses regexes to match only IP addresses and enrich with only the metadata from vendors MaxMind and Spur. In contrast, **fstsed** is completely flexible, allowing you to match any string and bundle any metadata you want.\n\n---\n\n## Table of Contents\n\n- [Why Inline Enrichment Matters](#why-inline-enrichment-matters)\n- [Use Cases](#use-cases)\n- [Key Features](#key-features)\n- [Quick Start](#quick-start)\n- [Performance](#performance)\n- [Examples](#examples)\n- [When to Use fstsed](#when-to-use-fstsed)\n- [Installation](#installation)\n- [See Also](#see-also)\n- [Acknowledgements](#acknowledgements)\n- [License](#license)\n\n---\n\n## Why Inline Enrichment Matters\n\nWhen investigating logs, reports, or documents, raw matches are never enough. You need context.\n\n**Before**\n\n```text\n2024-01-10 14:23:45 Connection established to 192.168.1.105\n2024-01-10 14:23:51 DNS query: flowersbyirene.com\n2024-01-10 14:24:02 HTTP request to suspicious-domain.net\n```\n\n**After enrichment with fstsed**\n\n```text\n2024-01-10 14:23:45 Connection established to 192.168.1.105 (High-priority APT indicator, last seen: Jan 8)\n2024-01-10 14:23:51 DNS query: flowersbyirene.com (Attributed to APT99, confidence: high)\n2024-01-10 14:24:02 HTTP request to suspicious-domain.net (Phishing infrastructure)\n```\n\nInline enrichment eliminates context switching. You read enriched output directly, even when working with **tens or hundreds of thousands of indicators**.\n\n---\n\n## Use Cases\n\n| Tool                                            | Patterns                | Enrichment                                                                                                                                                                                                      | Scaling                  |\n| :---------------------------------------------- | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------- |\n| `grep -f searchterms.txt`                       | Regex and fixed strings | No capability                                                                                                                                                                                                   | Linear with keys         |\n| `rg -f searchterms.txt --replace \"replacement\"` | Regex and fixed strings | One replacement string for all searchterms (although that replacement string can include [named capture groups](https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#replacements) from the regex search) | Search optimized         |\n| `fstsed -f searchterms.fst`                     | Fixed strings           | Replacement text **per search term**                                                                                                                                                                            | Constant time complexity |\n\nfstsed trades a small amount of raw speed (vs pure ripgrep) to gain **rich, structured, per-indicator context**.\n\n---\n\n## Key Features\n\n- **Per-indicator metadata**  \n  Each search term has a full JSON object of metadata\n\n- **Template-driven output**  \n  Use `--template` to control which fields from the JSON metadata object appear in output\n\n- **Predictable performance at scale**  \n  Search time remains stable from 10 to 100,000+ indicators\n\n- **JSON-aware search mode**  \n  Optionally search only inside JSON string values, with proper decoding and re-encoding\n\n- **Word-boundary-aware matching**  \n  Prevents partial matches (`apple` won't match `pineapples`)\n\n- **Nested metadata access**  \n  Reference deeply nested fields using JSON Pointer (RFC 6901)\n\n- **Compact storage**  \n  FST databases are Zstd-compressed for minimal disk usage\n\n---\n\n## Quick Start\n\nBuild once, reuse everywhere.\n\n```bash\n# Build an FST database from JSON threat intelligence.\n# All the values from .indicator_key can be enriched with any part of the json record\n# {\"indicator_key\": \"indicator_value\", \"threat_actor\": \"APT99\", \"severity\": \"high\", \"alias\": \"Operation Red\"}\nfstsed --build -f intel.fst -k indicator_key threat_intel.json\n\n# Enrich logs with selected fields\ncat logs.txt | fstsed -f intel.fst \\\n  --template \"{key} | {threat_actor} | severity: {severity}\"\n\n# Same database, different analysis context\nfstsed -f intel.fst logs.txt \\\n  --template \"{key} ({campaign})\"\n\n# JSON-only search mode (search within JSON string values)\nfstsed -f intel.fst --json events.json \\\n  --template \"{key} | remediate: {remediation_steps}\"\n```\n\nTemplates are fstsed's core abstraction to adapt output without rebuilding your database.\n\n---\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eUsage Reference\u003c/strong\u003e\u003c/summary\u003e\n\n```text\nFind and replace/decorate text at scale using finite state transducers (fst)\n\nUsage: fstsed [OPTIONS] -f \u003cFST\u003e [PATH]...\n\nArguments:\n  [PATH]...\n          Input file(s) or directory(s) to process. Leave empty or use \"-\" to read from stdin. (In build mode, only the first path is used)\n\nOptions:\n  -o, --only-matching\n          Show only nonempty parts of lines that match\n\n      --color [\u003cWHEN\u003e]\n          This flag controls when to use colors to highlight matched (non-empty) strings and the rendered template.\n\n          fstsed will suppress color output by default in some other\n          circumstances as well. These include, but are not limited to:\n\n          • When the NO_COLOR environment variable is set (regardless of value).\n\n          Possible values:\n          - always: Always use color highlighting\n          - never:  Never use color highlighting\n          - auto:   Use color highlighting only when writing to a terminal (default)\n\n          [default: auto]\n\n  -f \u003cFST\u003e\n          Specify fst db to use in search or to create in build mode\n\n      --build\n          Build mode. Build a fst from json data instead of querying one. Specify output path with the -f --fst\n          parameter. Only first file input parameter or stdin is used to make the fst\n\n  -k, --key \u003cKEY\u003e\n          When building a fst, extract the given json field to use as the key in the fst database. Key may also be\n          provided as a jsonpointer, e.g. /obj/array/1/item\n\n          [default: key]\n\n      --sorted\n          When building a fst, set this if the keys of input json are already lexicographically sorted. This will\n          make build construction much faster. If this is set but the keys are not sorted, the fst creation will\n          error\n\n  -t, --template \u003cTEMPLATE\u003e\n          Specify the format of the fstsed match decoration. Field names are enclosed in {}, for example \"{field1}\n          any fixed string {field2} \u0026 {field3}\". Fields may be json keys or jsonpointers {/obj/array/1/item}\n\n  -j, --json\n          Json search mode. Fstsed will treat input as json, searching only inside quoted strings. All strings are\n          deserialized/decoded before json before searching, and all template decorations are properly json-encoded\n          in the output for subsequent processing\n\n  -w, --threads \u003cNUM\u003e\n          The number of threads to use for searching\n\n  -u, --no-ignore\n          Do not respect ignore files (.gitignore, .ignore, etc.)\n\n      --hidden\n          For recursive directory scanning, search hidden files and directories\n\n  -h, --help\n          Print help (see a summary with '-h')\n\n  -V, --version\n          Print version\n\nExamples:\n  Build an FST database from JSON:\n    fstsed --build -f data.fst -k key data.json\n\n  Basic find and replace:\n    echo \"match\" | fstsed -f data.fst\n\n  Use a template for decoration:\n    echo \"match\" | fstsed -f data.fst --template \"{key} ({info})\"\n\n  JSON search mode (only search inside quoted JSON strings):\n    fstsed -f data.fst --json input.json\n\n```\n\n\u003c/details\u003e\n\n\u003csummary\u003e\u003cstrong\u003eTemplate Syntax\u003c/strong\u003e\u003c/summary\u003e\n\nTemplates control how matches are rendered in output.\n\n### Variables\n\n| Syntax             | Description                              |\n| ------------------ | ---------------------------------------- |\n| `{key}`            | The matched search term                  |\n| `{value}`          | Full JSON payload (entire record)        |\n| `{fieldname}`      | Top-level JSON field value               |\n| `{/path/to/field}` | Nested field via JSON Pointer (RFC 6901) |\n\n### Examples\n\n```bash\n# Simple replacement\n--template \"{key}\"\n\n# Key with single field\n--template \"{key} ({threat_actor})\"\n\n# Multiple fields with formatting\n--template \"[{severity}] {key} - {description}\"\n\n# Nested JSON access\n--template \"{key}: {/metadata/attribution/actor}\"\n\n# Include literal braces by doubling them\n--template \"{{literal braces}} {key}\"\n```\n\n### Default Template\n\nWhen no template is specified, fstsed uses:\n\n```text\n\u003c{key}|{value}\u003e\n```\n\nThis outputs the matched key and its full JSON payload, useful for debugging or when you need all metadata.\n\n---\n\n## Performance\n\nfstsed's defining characteristic is **predictable, ripgrep-scale performance as key counts grow**.\n\nBenchmarks below were run against a 100MB realistic log corpus (~1% positive match rate) on a modern multicore system.\n\n| Tool       |    Keys | Time (s) | Throughput (MB/s) |\n| ---------- | ------: | -------: | ----------------: |\n| fstsed     |      10 |     0.41 |               245 |\n| fstsed     | 100,000 |     0.92 |               110 |\n| rg -F -w   |      10 |     0.08 |              1314 |\n| rg -F -w   | 100,000 |     0.98 |               103 |\n| grep -F -w |     100 |      102 |                ~1 |\n\n### Takeaways\n\n- **ripgrep** remains the fastest general-purpose search tool, especially for small pattern sets\n- **fstsed** converges with ripgrep throughput at large key counts\n- **grep** becomes impractical beyond ~100 patterns\n- fstsed shines when you need **custom per-match transformations** that ripgrep can't provide\n\n---\n\n## Examples\n\n### IOC Enrichment with Real Threat Intel (Volexity)\n\nThis example demonstrates building a real-world IOC database and enriching text with source-aware metadata.\n\n**1. Get the data**\n\n```bash\ngit clone https://github.com/volexity/threat-intel.git\n```\n\n```bash\nuvx --with pandas ipython\n```\n\n**2. Convert CSV files to JSON**\n\n```python\nimport pandas as pd\nimport glob\n\ncsvs = glob.glob(\"threat-intel/**/*.csv\", recursive=True)\n\ndef conv(csv):\n    df = pd.read_csv(csv)\n    df[\"path\"] = csv\n    return df.to_json(orient=\"records\", lines=True)\n\niocjson = \"\\n\".join(map(conv, csvs))\nwith open(\"volexity.json\", \"w\") as f:\n    f.write(iocjson)\n```\n\n**3. Build FST database**\n\n```bash\nfstsed --build -f volexity.fst -k value volexity.json\n\n# or pipe from stdin\ncat volexity.json | fstsed --build -f volexity.fst -k value\n```\n\n**4. Enrich and analyze**\n\nBasic search (default template shows full JSON record):\n\n```bash\n$ echo \"test of avsvmcloud.com metadata\" | fstsed -f volexity.fst\n\ntest of \u003cavsvmcloud.com|{\"value\":\"avsvmcloud.com\",\"type\":\"hostname\",\"notes\":null,\"path\":\"2020/2020-12-14 - DarkHalo Leverages SolarWinds Compromise to Breach Organizations/indicators/indicators.csv\"}\u003e metadata\n```\n\nCustom template for cleaner output:\n\n```bash\n$ echo \"test of avsvmcloud.com metadata\" | fstsed -f volexity.fst \\\n    --template \"{key} (a {type} from {path} report)\"\n\ntest of avsvmcloud.com (a hostname from 2020/2020-12-14 - DarkHalo Leverages SolarWinds Compromise to Breach Organizations/indicators/indicators.csv report) metadata\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eMore Examples\u003c/strong\u003e\u003c/summary\u003e\n\n### MITRE ATT\u0026CK Technique Tagging\n\nEnrich code or logs with ATT\u0026CK technique context:\n\n```bash\n# Build from ATT\u0026CK data\ncat attack_patterns.json | fstsed --build -f attack.fst -k pattern\n\n# Tag findings with technique IDs and tactics\nfstsed -f attack.fst suspicious_script.ps1 \\\n  --template \"{key} [ATT\u0026CK: {technique_id} - {tactic}]\"\n```\n\n### Translation and Highlighting\n\nfstsed is not limited to infosec use cases. Here's a translation highlighting example:\n\n```bash\n# Build translation database\necho '{\"key\":\"ဗိုလ်ချုပ်မှူးကြီး\",\"translated\":\"Senior General of Myanmar Army\"}' \\\n  | fstsed -f myanmar.fst --build -k key\n\n# Highlight and translate in foreign text\nfstsed -f myanmar.fst article.txt --template \"\u003c{key}\u003e ({translated})\"\n```\n\nThen, taking the lede from a [BBC article](https://www.bbc.com/burmese/burma-57432310) as a test case, the output shows matched terms with inline translations:\n\n```bash\nfstsed -f myanmar.fst bbc.txt --template \"\u003c{key}\u003e ({translated})\"\n```\n\n\u003e လွန်ခဲ့တဲ့ ၅ နှစ်က တပ်မတော်ကာကွယ်ရေးဦးစီးချုပ် ရဲ့သက်တမ်းဟာ အကန့်အသတ်မရှိတဲ့ သဘောဖြစ်နေလို့ ၆၅ နှစ်ကန့်သတ်ပြီးပြင်ခဲ့တယ်လို့ **\u003cဗိုလ်ချုပ်မှူးကြီး\u003e (Senior General of Myanmar Army)** မင်းအောင်လှိုင်က ပြောခဲ့ပြီး သူ့အသက် ၆၅ နှစ်ပြည့်ဖို့ လပိုင်းအလိုမှာ အာဏာသိမ်းကာ အဲ့ဒီ့ကန့်သတ်ချက်ကို ပယ်ဖျက်လိုက်တဲ့ အတွက် တပ်မတော်ကာကွယ်ရေးဦးစီးချုပ်သက်တမ်းဟာ အကန့်အသတ်မဲ့ ပြန်ဖြစ်သွားပါတယ်။\n\nEven if I can't read any of the Burmese, I still know which key phrase matched, what that phrase means in my native tongue, and where generally the match occurred in the document.\n\n\u003c/details\u003e\n\n---\n\n## When to Use fstsed\n\n**Use fstsed when:**\n\n- You have **many search terms** (100s to 100,000s)\n- Each term has **associated metadata** (threat intel, translations, annotations)\n- Inline context matters more than raw matching speed\n- You want to **reuse the same database** with different output templates\n\n**Use ripgrep when:**\n\n- You need the fastest possible literal search\n- You have a handful of patterns\n- You don't need per-match metadata\n- You need regex or capture group substitution\n\n---\n\n\u003csummary\u003e\u003cstrong\u003eLimitations\u003c/strong\u003e\u003c/summary\u003e\n\n### Current Constraints\n\n\u003e [!NOTE]\n\u003e fstsed is optimized for literal string matching with rich metadata. The following constraints are by design.\n\n- **Word boundaries only**  \n  Matches must start and end at word boundaries. `apple` won't match inside `pineapple` or even in `apples`.\n\n- **Literal strings only**  \n  Patterns are exact strings, not regular expressions. Use ripgrep for regex needs.\n\n- **No null bytes in keys**  \n  Keys must not contain null bytes (`\\0`). The SENTINEL character is used internally to separate keys from values.\n\n- **Immutable databases**  \n  FST databases cannot be updated incrementally. Any changes require a full rebuild.\n\n\u003csummary\u003e\u003cstrong\u003eMatching Semantics \u0026 Build Notes\u003c/strong\u003e\u003c/summary\u003e\n\n### Boundary Matching\n\n\u003e [!IMPORTANT]\n\u003e Keys beginning or ending with word characters (`[a-zA-Z0-9_]`) must be bounded by non-word characters in input text.\n\n| Key           | Input            | Matches? |\n| ------------- | ---------------- | -------- |\n| `apple`       | `an apple`       | ✅ Yes   |\n| `apple`       | `an apple.`      | ✅ Yes   |\n| `apple`       | `an apple,`      | ✅ Yes   |\n| `apple`       | `pineapple`      | ❌ No    |\n| `apple`       | `apples`         | ❌ No    |\n| `192.168.1.1` | `ip:192.168.1.1` | ✅ Yes   |\n| `192.168.1.1` | `192.168.1.15`   | ❌ No    |\n| `10.10.1.1`   | `110.10.1.1`     | ❌ No    |\n\n### Shadowed Keys (Prefix Handling)\n\n**Important:** Due to word boundary requirements, purely alphanumeric keys like `abc` and `abcde` do NOT shadow each other. They match independently when word-bounded.\n\n**True shadowing** occurs when a shorter key followed by a **non-word character** forms a longer key:\n\n| Shorter Key | Longer Key    | Shadowing? | Reason                                         |\n| ----------- | ------------- | ---------- | ---------------------------------------------- |\n| `API`       | `API-KEY`     | ✅ Yes     | Hyphen is non-word char                        |\n| `user`      | `user@domain` | ✅ Yes     | `@` is non-word char                           |\n| `file`      | `file.txt`    | ✅ Yes     | Dot is non-word char                           |\n| `abc`       | `abcde`       | ❌ No      | Both need word boundaries; match independently |\n| `test`      | `testing`     | ❌ No      | Both need word boundaries; match independently |\n\n**Examples with shadowed keys:**\n\n| Input              | Match         | Why                               |\n| ------------------ | ------------- | --------------------------------- |\n| `use API-KEY here` | `API-KEY`     | `API` is shadowed, never matches  |\n| `send user@domain` | `user@domain` | `user` is shadowed, never matches |\n| `read file.txt`    | `file.txt`    | `file` is shadowed, never matches |\n\n**Examples WITHOUT shadowing:**\n\n| Input            | Match   | Why                                          |\n| ---------------- | ------- | -------------------------------------------- |\n| `hello abc test` | `abc`   | Word-bounded match                           |\n| `foo abcde test` | `abcde` | Word-bounded match                           |\n| `abc and abcde`  | Both    | Each matches independently when word-bounded |\n\n**Keys can contain internal non-word characters:**\n\n- User-Agent strings: `Mozilla/5.0`\n- IP addresses: `192.168.1.1`\n- File paths: `C:\\\\Windows\\\\System32`\n- API endpoints: `api-v2-endpoint`\n- Email patterns: `user@example.com`\n\nThese are valid keys because the non-word characters are internal, not at the boundaries where matching occurs.\n\n### Build Process\n\n1. Input JSON records are parsed\n2. The specified key field (`-k`) becomes the search term\n3. The entire JSON record becomes the metadata payload\n4. Records are sorted and compiled into an FST\n5. The FST is Zstd-compressed and written to disk\n\n### Database Inspection\n\n```bash\n# Optional: install fst-bin for database inspection\ncargo install fst-bin\n\n# Dump all keys in an FST\nfst range your.fst\n\n# Check if a specific key exists\nfst grep your.fst \"exact-key\"\n```\n\n---\n\n## Installation\n\n\u003c!-- ### Using Cargo\n\n```bash\ncargo install fstsed\n``` --\u003e\n\n### From Source\n\n```bash\ngit clone https://github.com/erichutchins/fstsed.git\ncd fstsed\ncargo build --release\n\n# Install to ~/.cargo/bin\ncargo install --path .\n```\n\n---\n\n## See Also\n\n- [ripgrep](https://github.com/BurntSushi/ripgrep) — Fast line-oriented search tool\n- [fst](https://github.com/BurntSushi/fst) — Finite state transducers in Rust\n- [aho-corasick](https://github.com/BurntSushi/aho-corasick) — Multi-pattern string matching\n- [geoipsed](https://github.com/erichutchins/geoipsed) — IP geolocation enrichment tool\n\n---\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.\n\n---\n\n## Acknowledgements\n\nfstsed is built on BurntSushi's excellent `fst` crate and is inspired by the performance principles behind ripgrep. This project would not exist without that foundational work.\n\nThis project was developed with significant assistance from **Claude 4.5 Sonnet** and **Google Gemini**, via **Zed** and **AntiGravity** editors, respectively.\n\n---\n\n## License\n\nMIT OR Apache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferichutchins%2Ffstsed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferichutchins%2Ffstsed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferichutchins%2Ffstsed/lists"}