srgn

repository·main·Indexed 21 days ago

https://github.com/alexpovel/srgn

A grep-like 'code surgeon' tool that combines regular expressions with language-aware syntax understanding via tree-sitter. It allows developers to search for and manipulate code by scoping operations to specific syntactical elements like classes, functions, or docstrings. Supported languages include C, C#, Go, HCL, Python, Rust, and TypeScript. Beyond search, it provides actions for text replacement, casing changes, Unicode normalization, and specialized German character handling.

Tokens
19.9K
Snippets
86
Records
101
Agent score
75%

What's inside srgn

  1. Invert the effects of actions

    main

    The -i, --invert flag attempts to undo the effects of the passed actions. This requires a 1:1 mapping between the original and the transformed content.

    Supported for inversion:

    • --symbols: e.g., '!=' $\leftrightarrow$ '≠'.

    Not supported for inversion (will be applied normally instead):

    • --german: Ambiguity in umlaut expansion (e.g., Ä could be Ae or AE).
    • --upper, --lower, --delete, --squeeze: Information loss makes inversion impossible.
  2. Ignore specific parts of a match using _SRGN_IGNORE

    main

    When writing custom Tree-sitter queries, you can prevent certain captured nodes from being included in the transformation by prefixing their capture name with _SRGN_IGNORE. This is helpful when you want to match a large structure (like a macro invocation) but only want to modify a specific sub-part of it.

    cat wrong.rs | srgn --rust-query '((macro_invocation macro: (identifier) @_SRGN_IGNORE_name) @macro)' 'error' -- 'wrong'
  3. Compare srgn with tr

    main

    While srgn is inspired by tr, it uses regular expressions for most operations, providing more flexibility.

    tr flagsrgn implementation
    -c, -C, --complementNot available as a flag; use regex negation (e.g., [^a-z]) instead.
    -d, --deleteAvailable via regex.
    -s, --squeeze-repeatsAvailable via regex.
    -t, --truncate-set1Not available.
  4. Use custom Tree-sitter queries for ad-hoc scoping

    main

    You can create custom, tailor-made scopes using Tree-sitter query syntax. This is useful for building ad-hoc linters or finding complex patterns that standard scopes (like strings or comments) cannot capture.

    Use the --<language>-query flag to pass a query string, or --<language>-query-file to read a query from a .scm file.

    cat cond.py | srgn --python-query '(if_statement consequence: (block (return_statement (identifier))) alternative: (else_clause body: (block (return_statement (identifier))))) @cond' --fail-any
  5. How srgn works: Actions and Scopes

    main

    Core Mental Model

    srgn operates by applying actions (transformations) within specific scopes (areas of interest).

    • Scopes: These define where to look. A scope can be a regular expression pattern or a language grammar-aware scope (e.g., "all Python docstrings"). Scopes can be layered: a regex scope applied after a language scope will only search within the results of that language scope.
    • Actions: These define what to do with the matches found in the scopes. Examples include replacement, uppercasing, or titlecasing.

    Execution Order

    1. Replacement is always performed first and is specified positionally as the last argument. It is disambiguated by -- for safety.
    2. Other actions (provided as command-line flags) are applied after the replacement.
    3. Scopes are evaluated from left to right. If multiple language scopes are provided, they act as a logical AND (intersecting the results) unless the -j flag is used.
    # Basic replacement (Action: replacement, Scope: regex)
    echo 'Hello World!' | srgn '[wW]orld' -- 'there'
    
    # Multiple actions (Action 1: replacement, Action 2: uppercasing)
    echo 'Hello World!' | srgn --upper '[wW]orld' -- 'you'
  6. How srgn works: Scopes and Actions

    main

    The srgn tool operates using two fundamental concepts: scopes and actions.

    • Scopes: These narrow down which parts of the input should be processed. If no scope is specified, the entire input is considered in scope.
    • Actions: These define the processing to be performed on the scoped input.

    Both scopes and actions are composable. You can pass multiple scopes or multiple actions to a single command. Replacements are always applied first in the execution order. The tool is designed to be a more powerful, regex-based alternative to the standard tr utility.

    echo 'input' | srgn <scope> <action>
  7. How language grammar-aware scopes work

    main

    Unlike default regex scopes, language grammar-aware scopes use tree-sitter to perform pattern matching against a tree data structure. This allows srgn to understand the syntax of a language (e.g., distinguishing between a keyword, a comment, or a function call).

    Key behaviors:

    • Priority: Language scopes are applied first. Any subsequent regular expression scope you provide operates only on the text within the matched language constructs.
    • Custom Queries: You can run custom ad-hoc queries using the --lang-query <S EXPRESSION> flag, where lang is the target language (e.g., python).
    • Dynamic Variants: Some prepared queries support a dynamic variant using the ~ separator. The syntax is node_type~pattern. The pattern is a regular expression that applies to the name of the language item (like a struct or class name).
  8. Install srgn in GitHub Actions CI

    main

    To use srgn in a CI pipeline, use the cargo-bins/cargo-binstall action to download the binary quickly without compilation. This method works across all major operating systems.

    jobs:
      srgn:
        name: Install srgn in CI
        runs-on: ubuntu-latest
        steps:
          - uses: cargo-bins/cargo-binstall@main
          - name: Install binary
            run: >
              cargo binstall
              --no-confirm
              srgn
          - name: Use binary
            run: srgn --version
  9. Search within language-specific syntactical elements

    main

    You can use srgn to search for patterns only within specific parts of a programming language's grammar (e.g., only inside Python classes). This is done by providing a language flag (like --python) followed by the syntactical element name and then the regex pattern.

    Search Mode: If no actions (like replacement or casing flags) are specified, srgn enters 'search mode', which prints matching lines and line numbers.

    Intersection (AND) vs. Join (OR):

    • Default (AND): Multiple language scopes intersect. srgn --python 'class' --python 'doc-strings' 'pattern' finds 'pattern' inside docstrings that are inside a class.
    • Join (OR): Use the -j flag to run queries independently and join the results. srgn -j --python 'comments' --python 'doc-strings' 'pattern' finds 'pattern' if it is in a comment or a docstring.
    # Search for 'age' only within Python class definitions
    $ cat birds.py | srgn --python 'class' 'age'
    
    # Search for patterns in either comments OR docstrings using -j
    $ cat birds.py | srgn -j --python 'comments' --python 'doc-strings' 'bird[^s]'
  10. Install srgn via cargo-binstall

    main

    The fastest way to install srgn on systems with the Rust toolchain is using cargo-binstall, which downloads prebuilt binaries instead of compiling from source. If no prebuilt binary is available for your platform, it will automatically fall back to compiling from source.

    # 1. Install the Rust toolchain
    # 2. Install cargo-binstall
    cargo install cargo-binstall
    
    # 3. Install srgn
    cargo binstall srgn