Documentation

Read source

greppy reads by symbol or by path. A symbol read returns the definition's exact source bytes. A file read returns line ranges, pages or, for a long source file, its outline. Any read can also print a handle that pins exactly the printed span for a later edit.

Command Returns
greppy read S [S …] the source of each definition, byte-exact
greppy read-smart S [S …] the source with nested blocks folded into one-line descriptions
greppy read-file PATH [PATH …] file content: a line range, everything, or 400-line pages; a long indexed source file without a range shows its outline

The examples run on serde 1.0.229.

read

greppy read RenameRule::apply_to_variant
greppy read Ctxt::new Ctxt::check
greppy read deserialize_body --head 6        # the first 6 lines
greppy read deserialize_body --tail 4        # the last 4 lines
greppy read RenameRule::or --handle          # plus an edit handle

read prints each definition as source bytes, with the header line file:start-end name. This includes the documentation and attributes the parser attaches to the definition, such as Rust /// comments and #[…] attributes or Python decorators:

$ greppy read Ctxt::new Ctxt::check
serde_derive/src/internals/ctxt.rs:19-26  Ctxt::new
    /// Create a new context object.
    ///
    /// This object contains no errors, but will still trigger a panic if it is not `check`ed.
    pub fn new() -> Self {
        Ctxt {
            errors: RefCell::new(Some(Vec::new())),
        }
    }

serde_derive/src/internals/ctxt.rs:45-58  Ctxt::check
    /// Consume this object, producing a formatted error string if there are errors.
    pub fn check(self) -> syn::Result<()> {
        let mut errors = self.errors.borrow_mut().take().unwrap().into_iter();
…
        Err(combined)
    }
$ greppy read deserialize_in_place_body
serde_derive/src/de.rs:377-380  deserialize_in_place_body
#[cfg(not(feature = "deserialize_in_place"))]
fn deserialize_in_place_body(_cont: &Container, _params: &Parameters) -> Option<Stmts> {
    None
}

Plain // comments above a function are not part of the definition, in Rust and in Go alike. Two such lines sit above build_generics in serde_derive/src/ser.rs, and greppy read serde_derive/src/ser.rs::build_generics starts at line 135 with the signature.

--head and --tail trim each definition, and the header line names the printed range:

$ greppy read deserialize_body --tail 4
serde_derive/src/de.rs:327-330  deserialize_body
            Data::Struct(_, _) => unreachable!("checked in serde_derive_internals"),
        }
    }
}

You qualify a name defined more than once with its file. The ambiguity response gives you the matching command for each definition and ends with exit code 1:

$ greppy read from_ast
`from_ast` is 4 definitions
serde_derive/src/internals/ast.rs:62  serde_derive/src/internals/ast.rs::Container::from_ast — greppy read serde_derive/src/internals/ast.rs::Container::from_ast
serde_derive/src/internals/attr.rs:237  serde_derive/src/internals/attr.rs::Container::from_ast — greppy read serde_derive/src/internals/attr.rs::Container::from_ast
serde_derive/src/internals/attr.rs:748  serde_derive/src/internals/attr.rs::Variant::from_ast — greppy read serde_derive/src/internals/attr.rs::Variant::from_ast
serde_derive/src/internals/attr.rs:1015  serde_derive/src/internals/attr.rs::Field::from_ast — greppy read serde_derive/src/internals/attr.rs::Field::from_ast

--path also picks among same-named definitions: greppy read from_ast --path serde_derive/src/internals/ast.rs reads Container::from_ast from ast.rs.

read takes symbols, not line ranges. Given a file path, it prints the file's definitions with their qualified names and the next commands, not the file text:

$ greppy read serde_derive/src/internals/ctxt.rs
`serde_derive/src/internals/ctxt.rs` is a file — read a symbol:
serde_derive/src/internals/ctxt.rs:12  serde_derive/src/internals/ctxt.rs::Class::Ctxt  class
serde_derive/src/internals/ctxt.rs:22  serde_derive/src/internals/ctxt.rs::Ctxt::new  method
serde_derive/src/internals/ctxt.rs:31  serde_derive/src/internals/ctxt.rs::Ctxt::error_spanned_by  method
serde_derive/src/internals/ctxt.rs:41  serde_derive/src/internals/ctxt.rs::Ctxt::syn_error  method
serde_derive/src/internals/ctxt.rs:46  serde_derive/src/internals/ctxt.rs::Ctxt::check  method
serde_derive/src/internals/ctxt.rs:62  serde_derive/src/internals/ctxt.rs::Ctxt::drop  method
read one: greppy read serde_derive/src/internals/ctxt.rs::Class::Ctxt · lines: greppy read-file serde_derive/src/internals/ctxt.rs --lines A:B

read-smart

greppy read-smart RenameRule::apply_to_variant
greppy read-smart RenameRule::apply_to_variant --depth 2

read-smart keeps blocks above the given structural depth raw and folds the blocks at that depth into one-line descriptions. The default depth is 1. Each folded line names its line range and an expand ID for the hidden block. This shows you the shape of a long function before you read the part you need:

$ greppy read-smart RenameRule::apply_to_variant
serde_derive/src/internals/case.rs:56-79  RenameRule::apply_to_variant
    /// Apply a renaming rule to an enum variant, returning the version expected in the source.
    pub fn apply_to_variant(self, variant: &str) -> String {
        … 58-78 Converts a variant into its canonical form. — greppy expand 55fb8674dc2a501a
    }
$ greppy read-smart RenameRule::apply_to_variant --depth 2
serde_derive/src/internals/case.rs:56-79  RenameRule::apply_to_variant
    /// Apply a renaming rule to an enum variant, returning the version expected in the source.
    pub fn apply_to_variant(self, variant: &str) -> String {
        match self {
            None | PascalCase => variant.to_owned(),
            LowerCase => variant.to_ascii_lowercase(),
            UpperCase => variant.to_ascii_uppercase(),
            CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
            … 63-72 Builds a snake case string from variant characters. — greppy expand 78207fd6ca1ef576
…

expand opens a folded block folded again, one level at a time:

$ greppy expand 78207fd6ca1ef576
            SnakeCase => {
                let mut snake = String::new();
                … 65-70 Converts a character sequence into lowercase snake case using variant characters. — greppy expand 63c6b28b84f5e000
                snake
            }

read-file

greppy read-file serde_derive/src/internals/case.rs --lines 112:118   # inclusive, 1-based
greppy read-file serde_derive/src/internals/case.rs --all             # every line
greppy read-file serde_derive/src/internals/case.rs                   # see below

You use read-file for paths. With --lines or --all it prints exactly that text:

$ greppy read-file serde_derive/src/internals/case.rs --lines 112:118
serde_derive/src/internals/case.rs:112-118
    pub fn or(self, rule_b: Self) -> Self {
        match self {
            None => rule_b,
            _ => self,
        }
    }
}

These rules apply when you pass neither --lines nor --all:

  • A file of up to 60 lines prints whole. serde_derive/src/de/unit.rs has 52 lines and comes back as serde_derive/src/de/unit.rs:1-52.
  • A longer indexed source file shows its outline, the same definition list with next commands as greppy read PATH. --outline asks for it explicitly.
  • Any other file prints in 400-line pages. A cut page ends with how to continue, for a file of 551 lines for example 151 more lines — greppy expand cb724cce764197c4 continues at 401.
$ greppy read-file serde_derive/src/internals/case.rs
Source outline for `serde_derive/src/internals/case.rs` — fingerprint-verified indexed definitions:
serde_derive/src/internals/case.rs:9  serde_derive/src/internals/case.rs::Enum::RenameRule  enum
serde_derive/src/internals/case.rs:45  serde_derive/src/internals/case.rs::RenameRule::from_str  method
serde_derive/src/internals/case.rs:57  serde_derive/src/internals/case.rs::RenameRule::apply_to_variant  method
serde_derive/src/internals/case.rs:82  serde_derive/src/internals/case.rs::RenameRule::apply_to_field  method
serde_derive/src/internals/case.rs:112  serde_derive/src/internals/case.rs::RenameRule::or  method
serde_derive/src/internals/case.rs:120  serde_derive/src/internals/case.rs::Class::ParseError  class
serde_derive/src/internals/case.rs:125  serde_derive/src/internals/case.rs::ParseError::fmt  method
serde_derive/src/internals/case.rs:140  serde_derive/src/internals/case.rs::Function::rename_variants  function
serde_derive/src/internals/case.rs:174  serde_derive/src/internals/case.rs::Function::rename_fields  function
read one: greppy read serde_derive/src/internals/case.rs::Enum::RenameRule · lines: greppy read-file serde_derive/src/internals/case.rs --lines A:B

Paths can be repository-relative or absolute. A relative path with ../ stays confined to the repository and ends with no such file outside it. An explicitly absolute path outside it, such as a downloaded build artifact, works.

Exact reads with --lines and --all do not need the graph store. They keep working while a repository is being indexed.

Handles

--handle on read, read-smart or read-file prints a handle for the printed span. A handle pins the file, the byte range and the content hashes. It names exactly the printed span: when the output is cut, it covers the shown part.

$ greppy read RenameRule::or --handle
serde_derive/src/internals/case.rs:111-117  RenameRule::or
    /// Returns the `RenameRule` if it is not `None`, `rule_b` otherwise.
    pub fn or(self, rule_b: Self) -> Self {
        match self {
            None => rule_b,
            _ => self,
        }
    }
handle: geh2:AgpflQfNuU6DcfwlKqLlHpwi1QVrm02DIQ

Folded source is not an exact span. For it, read-smart --handle prints the way to a handle instead of a handle:

$ greppy read-smart RenameRule::or --handle
serde_derive/src/internals/case.rs:111-117  RenameRule::or
    /// Returns the `RenameRule` if it is not `None`, `rule_b` otherwise.
    pub fn or(self, rule_b: Self) -> Self {
        … 113-116 Returns rule_b when self is None, otherwise self. — greppy expand 9eafa1d9fda51840
    }
note: no edit handle for folded source; request greppy read SYMBOL --handle or read-file PATH --lines A:B --handle

You pass the handle to replace-span instead of locating the code again:

greppy read RenameRule::or --handle
greppy replace-span H < or_if.rs

replace-span writes nothing if the file changed since the handle was printed. You find an example with a receipt under Edit transactionally.

Chain from a query

Every command also takes its input from the pipe, so search and navigation results go straight into a read:

greppy search-symbol apply_to_field --json | greppy read -
greppy callees visit_content_map --json | greppy read -
$ greppy callees visit_content_map --json | greppy read -
serde/src/private/de.rs:1608-1615  MapDeserializer::new
        fn new(content: Vec<(Content<'de>, Content<'de>)>) -> Self {
            MapDeserializer {
                iter: content.into_iter(),
                value: None,
                count: 0,
                error: PhantomData,
            }
        }

Output flags

Flag Effect
--handle prints an edit handle for each printed span
--path P results under that file or directory; repeatable. For read, it also picks among same-named definitions
--max-bytes N a hard budget for the printed payload
--root DIR reads from another repository

A budget cuts the content before the status and continuation lines. The response tells you how to continue:

$ greppy read RenameRule::apply_to_variant --max-bytes 300
truncated: true
total: 25
offset: 0
try: greppy read RenameRule::apply_to_variant --max-bytes 300 --offset 2
serde_derive/src/internals/case.rs:56-79  RenameRule::apply_to_variant
    /// Apply a renaming rule to an enum variant, returning the version expected in the source.

Freshness

Reads return current bytes, never a stale span. When the indexed spans no longer match the file, greppy refreshes the graph or fails closed and tells you when to retry. While the graph is refreshing, read reports a temporary failure instead of pairing current bytes with an old span.