summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: a09f4ef4f7c44579e19865bcacdb5b16cb0c8992 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use std::{path::PathBuf, fs, process::exit};
use rowan::{ast::AstNode, TextSize};
use clap::{arg, command, value_parser};

#[allow(dead_code)]
mod queries;
#[allow(dead_code)]
mod status_reporter;
mod batchmode;
#[allow(dead_code)]
mod util;


fn parse_nexp(path: &PathBuf) -> anyhow::Result<(String, rnix::Root)> {
    let content = fs::read_to_string(path)?;

    let parse = rnix::Root::parse(&content);
    if !parse.errors().is_empty() {
        anyhow::bail!("error: {:?}", parse.errors());
    }
    Ok((content, parse.tree()))
}

fn main() {

    let matches = command!()
        .arg(arg!(--batchmode "run in batch mode")
             .required(false)
        )
        .arg(arg!([query] "query to run")
             .required(true))
        .arg(arg!([file] "file to operate on")
            .required(true)
             .value_parser(value_parser!(PathBuf))
        )
        .arg(arg!(--edit <operation> "what to do")
        .required(false))
        .get_matches();

    let query_string = matches.get_one::<String>("query").unwrap();
    let files = matches.get_one::<PathBuf>("file").unwrap();

    let parse = queries::parse(query_string);
    if parse.errors.len() != 0 {
        eprintln!(
            "syntax {}: \n  {}",
            if parse.errors.len() == 1 { "error" } else { "errors" },
            parse.errors.join("  \n")
        );
        exit(1);
    }

    let (content, nexp) = match parse_nexp(files) {
        Err(e) => {
            eprintln!("could not parse file: {e}");
            exit(2);
        },
        Ok(exp) => exp
    };

    // println!("{nexp:#?}");

    let results = parse.apply(&content, nexp.syntax().clone()).unwrap();


    if let Some(op) = matches.get_one::<String>("edit") {
        match &op[..] {
            "remove" => {
                let new = remove_nodes(content, &results);
                println!("{new}");
            }
            _ => ()
        }
    } else {
        for result in &results {
            println!("{result}");
        }
    }


}

fn remove_nodes(content: String, results: &Vec<rnix::SyntaxNode>) -> String {

    assert!(results.len() == 1);

    let span = &results[0];

    let (before, after) = match (span.prev_sibling_or_token(), span.next_sibling_or_token()) {
        (Some(prev), Some(next))
            if prev.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE
              && next.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE
        => {
            if prev.to_string().lines().count() < next.to_string().lines().count() {
                (prev.text_range().len(), TextSize::new(0))
            } else {
                (TextSize::new(0), next.text_range().len())
            }
        }
        _ => (TextSize::default(),TextSize::default())
    };

    String::new()
        + &content[..Into::<usize>::into(span.text_range().start() - before) - previous_indentation(span).unwrap_or(0)]
        + &content[(span.text_range().end() + after).into()..]
}



fn previous_indentation(node: &rnix::SyntaxNode) -> Option<usize> {
    let whitespace_token = node.prev_sibling_or_token()?;

    if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE {
        Some(whitespace_token.to_string().lines().last().unwrap().len())
    } else {
        None
    }
}