|
| 1 | +// -*- coding: utf-8 -*- |
| 2 | +// ------------------------------------------------------------------------------------------------ |
| 3 | +// Copyright © 2021, stack-graphs authors. |
| 4 | +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. |
| 5 | +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. |
| 6 | +// ------------------------------------------------------------------------------------------------ |
| 7 | + |
| 8 | +use anyhow::anyhow; |
| 9 | +use clap::Args; |
| 10 | +use clap::ValueHint; |
| 11 | +use colored::Colorize; |
| 12 | +use std::path::Path; |
| 13 | +use std::path::PathBuf; |
| 14 | +use tree_sitter::CaptureQuantifier; |
| 15 | +use tree_sitter::Node; |
| 16 | + |
| 17 | +use crate::cli::parse::parse; |
| 18 | +use crate::cli::parse::print_node; |
| 19 | +use crate::cli::util::ExistingPathBufValueParser; |
| 20 | +use crate::loader::FileReader; |
| 21 | +use crate::loader::Loader; |
| 22 | +use crate::NoCancellation; |
| 23 | + |
| 24 | +/// Match file |
| 25 | +#[derive(Args)] |
| 26 | +pub struct MatchArgs { |
| 27 | + /// Input file path. |
| 28 | + #[clap( |
| 29 | + value_name = "SOURCE_PATH", |
| 30 | + required = true, |
| 31 | + value_hint = ValueHint::AnyPath, |
| 32 | + value_parser = ExistingPathBufValueParser, |
| 33 | + )] |
| 34 | + pub source_path: PathBuf, |
| 35 | + |
| 36 | + /// Only match stanza on the given line. |
| 37 | + #[clap(long, value_name = "LINE_NUMBER", short = 'S')] |
| 38 | + pub stanza: Vec<usize>, |
| 39 | +} |
| 40 | + |
| 41 | +impl MatchArgs { |
| 42 | + pub fn run(self, mut loader: Loader) -> anyhow::Result<()> { |
| 43 | + let mut file_reader = FileReader::new(); |
| 44 | + let lc = match loader.load_for_file(&self.source_path, &mut file_reader, &NoCancellation)? { |
| 45 | + Some(lc) => lc, |
| 46 | + None => return Err(anyhow!("No stack graph language found")), |
| 47 | + }; |
| 48 | + let source = file_reader.get(&self.source_path)?; |
| 49 | + let tree = parse(lc.language, &self.source_path, source)?; |
| 50 | + if self.stanza.is_empty() { |
| 51 | + lc.sgl.tsg.try_visit_matches(&tree, source, true, |mat| { |
| 52 | + print_matches(lc.sgl.tsg_path(), &self.source_path, source, mat) |
| 53 | + })?; |
| 54 | + } else { |
| 55 | + for line in &self.stanza { |
| 56 | + let stanza = lc |
| 57 | + .sgl |
| 58 | + .tsg |
| 59 | + .stanzas |
| 60 | + .iter() |
| 61 | + .find(|s| s.range.start.row <= line - 1 && line - 1 <= s.range.end.row) |
| 62 | + .ok_or_else(|| { |
| 63 | + anyhow!("No stanza on {}:{}", lc.sgl.tsg_path().display(), line) |
| 64 | + })?; |
| 65 | + stanza.try_visit_matches(&tree, source, |mat| { |
| 66 | + print_matches(lc.sgl.tsg_path(), &self.source_path, source, mat) |
| 67 | + })?; |
| 68 | + } |
| 69 | + } |
| 70 | + Ok(()) |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +fn print_matches( |
| 75 | + tsg_path: &Path, |
| 76 | + source_path: &Path, |
| 77 | + source: &str, |
| 78 | + mat: tree_sitter_graph::Match, |
| 79 | +) -> anyhow::Result<()> { |
| 80 | + println!( |
| 81 | + "{}: stanza query", |
| 82 | + format!( |
| 83 | + "{}:{}:{}", |
| 84 | + tsg_path.display(), |
| 85 | + mat.query_location().row + 1, |
| 86 | + mat.query_location().column + 1 |
| 87 | + ) |
| 88 | + .bold(), |
| 89 | + ); |
| 90 | + { |
| 91 | + let full_capture = mat.full_capture(); |
| 92 | + print!(" matched "); |
| 93 | + print_node(full_capture, true); |
| 94 | + print_node_text(full_capture, source_path, source)?; |
| 95 | + println!(); |
| 96 | + } |
| 97 | + let width = mat |
| 98 | + .capture_names() |
| 99 | + .map(|n| n.len()) |
| 100 | + .max() |
| 101 | + .unwrap_or_default(); |
| 102 | + if width == 0 { |
| 103 | + return Ok(()); |
| 104 | + } |
| 105 | + println!(" and captured"); |
| 106 | + for (name, quantifier, nodes) in mat.named_captures() { |
| 107 | + for (idx, node) in nodes.enumerate() { |
| 108 | + if idx == 0 { |
| 109 | + print!( |
| 110 | + " @{}{}{} = ", |
| 111 | + name, |
| 112 | + quantifier_ch(quantifier), |
| 113 | + " ".repeat(width - name.len()) |
| 114 | + ); |
| 115 | + } else { |
| 116 | + print!(" {} | ", " ".repeat(width)); |
| 117 | + } |
| 118 | + print_node(node, true); |
| 119 | + print_node_text(node, source_path, source)?; |
| 120 | + println!(); |
| 121 | + } |
| 122 | + } |
| 123 | + Ok(()) |
| 124 | +} |
| 125 | + |
| 126 | +fn print_node_text(node: Node, source_path: &Path, source: &str) -> anyhow::Result<()> { |
| 127 | + const MAX_TEXT_LENGTH: usize = 16; |
| 128 | + |
| 129 | + print!(", text: \""); |
| 130 | + let text = node.utf8_text(source.as_bytes())?; |
| 131 | + let summary: String = text |
| 132 | + .chars() |
| 133 | + .take(MAX_TEXT_LENGTH) |
| 134 | + .take_while(|c| *c != '\n') |
| 135 | + .collect(); |
| 136 | + print!("{}", summary.blue()); |
| 137 | + if summary.len() < text.len() { |
| 138 | + print!("{}", "…".dimmed()); |
| 139 | + } |
| 140 | + print!("\""); |
| 141 | + print!( |
| 142 | + ", path: {}:{}:{}", |
| 143 | + source_path.display(), |
| 144 | + node.start_position().row + 1, |
| 145 | + node.start_position().column + 1 |
| 146 | + ); |
| 147 | + Ok(()) |
| 148 | +} |
| 149 | + |
| 150 | +fn quantifier_ch(quantifier: CaptureQuantifier) -> char { |
| 151 | + match quantifier { |
| 152 | + CaptureQuantifier::Zero => '-', |
| 153 | + CaptureQuantifier::ZeroOrOne => '?', |
| 154 | + CaptureQuantifier::ZeroOrMore => '*', |
| 155 | + CaptureQuantifier::One => ' ', |
| 156 | + CaptureQuantifier::OneOrMore => '+', |
| 157 | + } |
| 158 | +} |
0 commit comments