Compare commits
No commits in common. "43e40b61e392c6c4fa18aaddde04cdb472e4754e" and "4ff52a2bd39c85de4d3bcaad2b93bf62986e5985" have entirely different histories.
43e40b61e3
...
4ff52a2bd3
15 changed files with 19 additions and 2991 deletions
227
Cargo.lock
generated
227
Cargo.lock
generated
|
|
@ -100,16 +100,7 @@ version = "0.5.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec 0.8.0",
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -118,12 +109,6 @@ version = "0.6.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
|
@ -248,17 +233,6 @@ dependencies = [
|
|||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.10.0"
|
||||
|
|
@ -442,12 +416,6 @@ version = "1.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
|
|
@ -479,16 +447,10 @@ version = "0.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
|
||||
dependencies = [
|
||||
"bit-set 0.5.3",
|
||||
"bit-set",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.3"
|
||||
|
|
@ -651,18 +613,6 @@ dependencies = [
|
|||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.47.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e"
|
||||
dependencies = [
|
||||
"console",
|
||||
"once_cell",
|
||||
"similar",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.11"
|
||||
|
|
@ -819,16 +769,6 @@ version = "0.4.29"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "logparse"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.7.2"
|
||||
|
|
@ -1102,7 +1042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand 0.8.5",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1154,15 +1094,6 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
|
|
@ -1182,42 +1113,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
|
||||
dependencies = [
|
||||
"bit-set 0.8.0",
|
||||
"bit-vec 0.8.0",
|
||||
"bitflags 2.11.0",
|
||||
"num-traits",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
"tempfile",
|
||||
"unarray",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest-derive"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c57924a81864dddafba92e1bf92f9bf82f97096c44489548a60e888e1547549b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
|
|
@ -1239,27 +1134,7 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.5",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1268,24 +1143,6 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||
dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.30.0"
|
||||
|
|
@ -1428,7 +1285,6 @@ dependencies = [
|
|||
"dumpster",
|
||||
"itertools",
|
||||
"jiff",
|
||||
"logparse",
|
||||
"nix 0.31.1",
|
||||
"ratatui",
|
||||
"ratatui-themes",
|
||||
|
|
@ -1467,18 +1323,6 @@ version = "1.0.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"quick-error",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
|
|
@ -1603,12 +1447,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
|
|
@ -1676,19 +1514,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminfo"
|
||||
version = "0.9.0"
|
||||
|
|
@ -1893,12 +1718,6 @@ version = "0.1.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "unarray"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
|
@ -1973,15 +1792,6 @@ dependencies = [
|
|||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
|
|
@ -2203,15 +2013,6 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
|
|
@ -2300,26 +2101,6 @@ dependencies = [
|
|||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ edition = "2024"
|
|||
name = "lv"
|
||||
path = "./src/main.rs"
|
||||
|
||||
[workspace]
|
||||
members = [".", "./logparse/"]
|
||||
default-members = [".", "./logparse/"]
|
||||
|
||||
[dependencies]
|
||||
clap = {version="4.5", features=["derive", "string"]}
|
||||
jiff = {version = "0.2", features = ["serde"]}
|
||||
|
|
@ -25,4 +21,3 @@ nix = {version = "0.31", features = ["process", "signal"]}
|
|||
regex = "1"
|
||||
crossterm = "*"
|
||||
dumpster = "2.1"
|
||||
logparse = {path = "./logparse/", version="0.1.0"}
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
[package]
|
||||
name = "logparse"
|
||||
version = "0.1.2"
|
||||
edition = "2024"
|
||||
description = "parse arbitrary messages containing rust-like debug output to syntax highlight them"
|
||||
authors = ["Jana Dönszelmann <cratesio@donsz.nl>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
documentation = "https://docs.rs/logparse"
|
||||
homepage = "https://git.donsz.nl/jana/logviewer"
|
||||
repository = "https://git.donsz.nl/jana/logviewer"
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
winnow = {version="1", features=["parser"]}
|
||||
proptest = "1"
|
||||
proptest-derive = "0.8"
|
||||
insta = "1"
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
# Logparse
|
||||
|
||||
This crate makes a best-effort parsing for arbitrary text that may contain rust-like debug output.
|
||||
This is intended for logs, that contain rust's debug output, and that you might want to highlight opposing parentheses, brackets, etc in.
|
||||
|
||||
Log messages, when parsed, can *always* be pretty printed back into their textual form perfectly.
|
||||
The syntax tree unambiguously records any spaces in the source text.
|
||||
There are tests that ensure this property for a very large number of inputs.
|
||||
|
||||
In addition, *any* log message can be parsed. `parse_input` returns a result,
|
||||
but as far as I'm aware,
|
||||
the parser is flexible enough to *never* reject,
|
||||
and always falls back to categorizing text as *just* text.
|
||||
There are also property tests to ensure this.
|
||||
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
use proptest_derive::Arbitrary;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// See [`Token::Separated`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Separator {
|
||||
Eq,
|
||||
Colon,
|
||||
DoubleColon,
|
||||
}
|
||||
|
||||
/// See [`Token::String`].
|
||||
#[derive(Copy, Clone, Debug, Arbitrary, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum QuoteType {
|
||||
Single,
|
||||
Double,
|
||||
Backtick,
|
||||
}
|
||||
|
||||
/// See [`Token::Delimited`].
|
||||
#[derive(Clone, Debug, Arbitrary, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Delimiter {
|
||||
Paren,
|
||||
Bracket,
|
||||
Brace,
|
||||
Angle,
|
||||
}
|
||||
|
||||
/// See [`Token::String`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AnyString<'a> {
|
||||
/// Any text immediately preceeding the string, like the `f` prefix in `f"mrrp"`.
|
||||
pub prefix: Cow<'a, str>,
|
||||
/// The kind of string quote used.
|
||||
pub ty: QuoteType,
|
||||
/// The string's text.
|
||||
pub contents: Cow<'a, str>,
|
||||
/// How may hashtags were used around the string.
|
||||
/// Stings have to open and close with an equal number of hashtags.
|
||||
pub num_hashtags: usize,
|
||||
/// Any text immediately following the string.
|
||||
pub suffix: Cow<'a, str>,
|
||||
}
|
||||
|
||||
/// Any sequence of 0 or more spaces.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Space<'a>(pub Cow<'a, str>);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Arbitrary)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum PathSep {
|
||||
/// Happens at the start of paths, for the no leading / case
|
||||
None,
|
||||
Slash,
|
||||
Backslash,
|
||||
}
|
||||
|
||||
/// A segment of a path, with a leading separator.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct PathSegment<'a> {
|
||||
pub leading_separator: PathSep,
|
||||
pub segment: Cow<'a, str>,
|
||||
}
|
||||
|
||||
/// See [`Path`]. This is the `:3:4` part in `mrrp.rs:3:4`.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FileLocation<'a> {
|
||||
/// Line number.
|
||||
pub line: Cow<'a, str>,
|
||||
/// Optionally, a column offset.
|
||||
pub offset: Option<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
/// See [`Path`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct FileName<'a> {
|
||||
pub leading_separator: PathSep,
|
||||
pub segment: Cow<'a, str>,
|
||||
pub ext_excluding_dot: Option<Cow<'a, str>>,
|
||||
pub location: Option<FileLocation<'a>>,
|
||||
}
|
||||
|
||||
/// See [`Token::Path`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct Path<'a> {
|
||||
pub drive_excluding_colon: Option<char>,
|
||||
pub segments: Vec<PathSegment<'a>>,
|
||||
|
||||
/// A filename: the last path segment, optionally with an extension and file/line number.
|
||||
pub filename: FileName<'a>,
|
||||
}
|
||||
|
||||
/// See [`Token::Number`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Number<'a>(pub Cow<'a, str>);
|
||||
|
||||
/// Anything that doesn't contain spaces, and that can be a prefix of `Delimited`.
|
||||
/// i.e. an english word, or rust `::`-separated Path
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Atom<'a> {
|
||||
/// Raw text
|
||||
Text(Cow<'a, str>),
|
||||
}
|
||||
|
||||
/// The main AST node: each of these are treated as one unit, that may be separated by spaces.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Token<'a> {
|
||||
/// A literal `true`.
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_true, Token::True);
|
||||
/// assert!(is_true("true"));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_true("14"));
|
||||
/// assert!(!is_true("`mrow!`"));
|
||||
/// assert!(!is_true("false"));
|
||||
/// ```
|
||||
True,
|
||||
/// A literal `false`.
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_false, Token::False);
|
||||
/// assert!(is_false("false"));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_false("14"));
|
||||
/// assert!(!is_false("`mrow!`"));
|
||||
/// assert!(!is_false("true"));
|
||||
/// ```
|
||||
False,
|
||||
/// A literal `None`.
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_none, Token::None);
|
||||
/// assert!(is_none("None"));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_none("14"));
|
||||
/// assert!(!is_none("`mrow!`"));
|
||||
/// assert!(!is_none("true"));
|
||||
/// ```
|
||||
None,
|
||||
|
||||
/// A path, anything that looks vaguely path-like.
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_path, Token::Path(_));
|
||||
/// assert!(is_path("/"));
|
||||
/// assert!(is_path("/home"));
|
||||
/// assert!(is_path("C:/Users"));
|
||||
/// assert!(is_path("C:\\Users"));
|
||||
/// assert!(is_path("/home/some_path/file-some_where.rs"));
|
||||
/// assert!(is_path("/home/some_path/file-and-line.rs:3"));
|
||||
/// assert!(is_path("/home/some_path/file-and-line.rs:3:4"));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_path(":14"));
|
||||
/// assert!(!is_path(":14:15"));
|
||||
/// assert!(!is_path("14"));
|
||||
/// assert!(!is_path("`mrow!`"));
|
||||
/// assert!(!is_path("true"));
|
||||
/// ```
|
||||
Path(Path<'a>),
|
||||
/// A string literal, with quite flexible rules on what's considered a string.
|
||||
/// Any rust string literal parses, and a little more.
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_string, Token::String(_));
|
||||
/// assert!(is_string(r##""mrrp""##));
|
||||
/// assert!(is_string(r##"'mrrp'"##));
|
||||
/// assert!(is_string(r##"`mrrp`"##));
|
||||
/// assert!(is_string(r##"b"mrrp""##));
|
||||
/// assert!(is_string(r##"f"mrrp""##));
|
||||
/// assert!(is_string(r##"c"mrrp""##));
|
||||
/// assert!(is_string(r##"r"mrrp""##));
|
||||
/// assert!(is_string(r##"r#"mrrp"#"##));
|
||||
/// assert!(is_string(r##"b#"mrrp"#"##));
|
||||
/// assert!(is_string(r####"anyprefix###"mrrp"###anysuffix"####));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_string("14"));
|
||||
/// assert!(!is_string("true"));
|
||||
/// assert!(!is_string("/home"));
|
||||
///
|
||||
/// // non-matching hashtags
|
||||
/// assert!(!is_string(r#"r#"mrrp""#));
|
||||
/// assert!(!is_string(r###"r#"mrrp"##"###));
|
||||
/// assert!(!is_string(r##"r"mrrp"#"##));
|
||||
///
|
||||
/// // non-matching quotes
|
||||
/// assert!(!is_string(r#"`mrrp""#));
|
||||
/// assert!(!is_string(r#"`mrrp'"#));
|
||||
/// ```
|
||||
String(AnyString<'a>),
|
||||
/// A number, float or int.
|
||||
/// There must not be any alphabetic character after the number, without a space inbetween.
|
||||
/// That's to guard against finding numbers inside hashes for example.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_number, Token::Number(_));
|
||||
/// assert!(is_number("1"));
|
||||
/// assert!(is_number("99999999"));
|
||||
/// assert!(is_number("1.5"));
|
||||
/// assert!(is_number("1e10"));
|
||||
/// assert!(is_number("-1"));
|
||||
/// assert!(is_number("-1.5"));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_number("`mrow!`"));
|
||||
/// assert!(!is_number("true"));
|
||||
///
|
||||
/// // suffix
|
||||
/// assert!(!is_number("14a"));
|
||||
/// ```
|
||||
Number(Number<'a>),
|
||||
|
||||
// TODO: RustPath
|
||||
/// Any token, separated by a [`Separator`], followed by another segment.
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_sep, Token::Separated {..});
|
||||
/// assert!(is_sep("a = 3"));
|
||||
/// assert!(is_sep("a::b::c"));
|
||||
/// assert!(is_sep("a: 5"));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_sep("`mrow!`"));
|
||||
/// assert!(!is_sep("true"));
|
||||
/// assert!(!is_sep("14"));
|
||||
/// ```
|
||||
Separated {
|
||||
/// The part before the separator.
|
||||
before: Box<Token<'a>>,
|
||||
/// The space between the `before` part, and the separator.
|
||||
space_before: Space<'a>,
|
||||
/// The separator itself.
|
||||
separator: Separator,
|
||||
/// The segment after the separator.
|
||||
after: Box<Segment<'a>>,
|
||||
},
|
||||
/// A segment, delimited by parentheses, braces or brackets, with an optional prefix.
|
||||
/// The prefix will be classified as a constructor if the delimiter is braces or parentheses.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_delim, Token::Delimited(_));
|
||||
/// assert!(is_delim("()"));
|
||||
/// assert!(is_delim("[]"));
|
||||
/// assert!(is_delim("{}"));
|
||||
/// assert!(is_delim("(1)"));
|
||||
/// assert!(is_delim("[1]"));
|
||||
/// assert!(is_delim("{1}"));
|
||||
/// assert!(is_delim("{/home/mrrp.rs}"));
|
||||
/// assert!(is_delim("constructor{/home/mrrp.rs}"));
|
||||
/// assert!(is_delim("constructor {/home/mrrp.rs}"));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_delim("`mrow!`"));
|
||||
/// assert!(!is_delim("true"));
|
||||
/// assert!(!is_delim("14"));
|
||||
/// ```
|
||||
Delimited(Delimited<'a>),
|
||||
|
||||
/// Any other text, that couldn't otherwise be categorized.
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # logparse::generate_ast_recognizer!(is_atom, Token::Atom(_));
|
||||
/// assert!(is_atom("mrrp"));
|
||||
///
|
||||
/// // some counterexamples
|
||||
/// assert!(!is_atom("`mrow!`"));
|
||||
/// assert!(!is_atom("true"));
|
||||
/// assert!(!is_atom("14"));
|
||||
/// ```
|
||||
Atom(Atom<'a>),
|
||||
}
|
||||
|
||||
/// See [`Token::Delimited`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Delimited<'a> {
|
||||
/// Delimiter prefix, i.e. the constructor part in `Some(mrrp)`.
|
||||
pub prefix: Option<(Atom<'a>, Space<'a>)>,
|
||||
/// The kind of delimiter itself.
|
||||
pub delimiter: Delimiter,
|
||||
/// The contents of the delimited segment.
|
||||
pub contents: Segments<'a>,
|
||||
}
|
||||
|
||||
/// A Segment of text, with optional leading space.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Segment<'a> {
|
||||
/// Any spaces (or empty) before this segment.
|
||||
pub leading_space: Space<'a>,
|
||||
/// The segment's contents itself.
|
||||
pub token: Token<'a>,
|
||||
}
|
||||
|
||||
/// Segments, with possible trailing space.
|
||||
///
|
||||
/// Any input is split up into many segments, with their surrounding spaces.
|
||||
/// Each segment might be a meaningless atom, or may be recognized to have more meaning.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Segments<'a> {
|
||||
/// The segments themselves.
|
||||
pub segments: Vec<Segment<'a>>,
|
||||
/// Any spaces (or empty) after this sequence of segments.
|
||||
pub trailing_space: Space<'a>,
|
||||
}
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
use super::ast::*;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
impl Display for Separator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Separator::Eq => write!(f, "="),
|
||||
Separator::Colon => write!(f, ":"),
|
||||
Separator::DoubleColon => write!(f, "::"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for QuoteType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
QuoteType::Single => write!(f, "\'"),
|
||||
QuoteType::Double => write!(f, "\""),
|
||||
QuoteType::Backtick => write!(f, "`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Delimiter {
|
||||
fn fmt_start(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Delimiter::Paren => write!(f, "("),
|
||||
Delimiter::Bracket => write!(f, "["),
|
||||
Delimiter::Brace => write!(f, "{{"),
|
||||
Delimiter::Angle => write!(f, "<"),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_end(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Delimiter::Paren => write!(f, ")"),
|
||||
Delimiter::Bracket => write!(f, "]"),
|
||||
Delimiter::Brace => write!(f, "}}"),
|
||||
Delimiter::Angle => write!(f, ">"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for AnyString<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.prefix)?;
|
||||
for _ in 0..self.num_hashtags {
|
||||
write!(f, "#")?;
|
||||
}
|
||||
write!(f, "{}", self.ty)?;
|
||||
write!(f, "{}", self.contents)?;
|
||||
write!(f, "{}", self.ty)?;
|
||||
|
||||
for _ in 0..self.num_hashtags {
|
||||
write!(f, "#")?;
|
||||
}
|
||||
write!(f, "{}", self.suffix)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Space<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PathSep {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PathSep::Slash => write!(f, "/"),
|
||||
PathSep::Backslash => write!(f, "\\"),
|
||||
PathSep::None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for PathSegment<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.leading_separator)?;
|
||||
write!(f, "{}", self.segment)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for FileLocation<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, ":{}", self.line)?;
|
||||
if let Some(offset) = &self.offset {
|
||||
write!(f, ":{offset}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for FileName<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.leading_separator)?;
|
||||
write!(f, "{}", self.segment)?;
|
||||
if let Some(ext) = &self.ext_excluding_dot {
|
||||
write!(f, ".{ext}")?;
|
||||
}
|
||||
if let Some(loc) = &self.location {
|
||||
write!(f, "{loc}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Path<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self {
|
||||
drive_excluding_colon,
|
||||
segments,
|
||||
filename,
|
||||
} = self;
|
||||
|
||||
if let Some(drive) = &drive_excluding_colon {
|
||||
write!(f, "{drive}:")?;
|
||||
}
|
||||
|
||||
for segment in segments {
|
||||
write!(f, "{segment}")?;
|
||||
}
|
||||
write!(f, "{filename}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Number<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Atom<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Atom::Text(text) => write!(f, "{text}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Token<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Token::Number(number) => write!(f, "{number}"),
|
||||
Token::True => write!(f, "true"),
|
||||
Token::False => write!(f, "false"),
|
||||
Token::None => write!(f, "None"),
|
||||
Token::Atom(atom) => write!(f, "{atom}"),
|
||||
Token::Path(path) => write!(f, "{path}"),
|
||||
Token::Separated {
|
||||
before,
|
||||
space_before,
|
||||
separator,
|
||||
after,
|
||||
} => write!(f, "{before}{space_before}{separator}{after}"),
|
||||
Token::Delimited(delimited) => write!(f, "{delimited}"),
|
||||
Token::String(s) => write!(f, "{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Delimited<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some((prefix, space)) = &self.prefix {
|
||||
write!(f, "{prefix}")?;
|
||||
write!(f, "{space}")?;
|
||||
}
|
||||
self.delimiter.fmt_start(f)?;
|
||||
write!(f, "{}", self.contents)?;
|
||||
self.delimiter.fmt_end(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Segment<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.leading_space)?;
|
||||
write!(f, "{}", self.token)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Segments<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for segment in &self.segments {
|
||||
write!(f, "{segment}")?;
|
||||
}
|
||||
write!(f, "{}", self.trailing_space)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
#![deny(missing_docs)]
|
||||
#![deny(warnings)]
|
||||
#![doc=include_str!("../README.md")]
|
||||
|
||||
/// The structure of parsed log lines
|
||||
pub mod ast;
|
||||
mod display;
|
||||
mod parse;
|
||||
mod spans;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! generate_ast_recognizer {
|
||||
($name: ident, $pattern: pat) => {
|
||||
fn $name(s: &str) -> bool {
|
||||
use logparse::{ast::*, *};
|
||||
let l = parse_input(s).unwrap();
|
||||
if !l.trailing_space.0.is_empty() {
|
||||
eprintln!("trailing space {l:?}");
|
||||
return false;
|
||||
}
|
||||
if l.segments.len() != 1 {
|
||||
eprintln!("more segments {l:?}");
|
||||
return false;
|
||||
}
|
||||
if !l.segments[0].leading_space.0.is_empty() {
|
||||
eprintln!("leading space {l:?}");
|
||||
return false;
|
||||
}
|
||||
if let $pattern = &l.segments[0].token {
|
||||
true
|
||||
} else {
|
||||
eprintln!("pattern {l:?}");
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod proptesting;
|
||||
|
||||
pub use parse::parse_input;
|
||||
pub use spans::{Config, Span, SpanKind, into_spans};
|
||||
|
|
@ -1,769 +0,0 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use super::ast::*;
|
||||
use winnow::{Parser, combinator::trace, error::ParserError};
|
||||
|
||||
impl<'a> AnyString<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
let quote = alt((
|
||||
'`'.value(QuoteType::Backtick),
|
||||
'\''.value(QuoteType::Single),
|
||||
'\"'.value(QuoteType::Double),
|
||||
));
|
||||
|
||||
macro_rules! surrounding {
|
||||
() => {
|
||||
take_while(0.., |b: char| {
|
||||
!b.is_whitespace() && b.is_alphabetic() && !['\'', '"', '`', '#'].contains(&b)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
let preamble = (
|
||||
surrounding!(),
|
||||
take_while(0.., |c| c == '#').map(|i: &'a str| i.len()),
|
||||
quote,
|
||||
);
|
||||
|
||||
trace(
|
||||
"string",
|
||||
preamble.flat_map(
|
||||
|(prefix, num_hashtags, quote): (&'a str, usize, QuoteType)| {
|
||||
let end = (
|
||||
match quote {
|
||||
QuoteType::Single => '\'',
|
||||
QuoteType::Double => '\"',
|
||||
QuoteType::Backtick => '`',
|
||||
},
|
||||
repeat::<_, _, Cow<'a, str>, _, _>(
|
||||
num_hashtags..=num_hashtags,
|
||||
literal("#"),
|
||||
),
|
||||
);
|
||||
|
||||
let contents = repeat_till(0.., any, end).map(|(contents, _)| contents);
|
||||
|
||||
(contents, surrounding!()).map(
|
||||
move |(contents, suffix): (Cow<'a, str>, &'a str)| Self {
|
||||
prefix: prefix.into(),
|
||||
ty: quote,
|
||||
contents,
|
||||
num_hashtags,
|
||||
suffix: suffix.into(),
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Space<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{prelude::*, token::*};
|
||||
|
||||
trace(
|
||||
"space",
|
||||
take_while(0.., |b: char| b.is_whitespace()).map(|i: &'a str| Self(i.into())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PathSep {
|
||||
fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*};
|
||||
|
||||
trace(
|
||||
"pathsep",
|
||||
alt(('/'.value(Self::Slash), '\\'.value(Self::Backslash))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Separator {
|
||||
fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
trace(
|
||||
"separator",
|
||||
alt((
|
||||
"::".value(Self::DoubleColon),
|
||||
(literal('=')).value(Self::Eq),
|
||||
(literal(':')).value(Self::Colon),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FileLocation<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{ascii::dec_uint, combinator::*, prelude::*};
|
||||
|
||||
let colon_number = || {
|
||||
(":".value(()), dec_uint::<_, u64, _>.take())
|
||||
.map(|(_, number): (_, &str)| Cow::Borrowed(number))
|
||||
};
|
||||
let line_offset = (colon_number(), opt(colon_number()));
|
||||
|
||||
trace(
|
||||
"file location",
|
||||
line_offset.map(|(line, offset)| FileLocation { line, offset }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FileName<'a> {
|
||||
fn parse(segment: PathSegment<'a>, location: Option<FileLocation<'a>>) -> Self {
|
||||
fn rsplit<'a>(
|
||||
input: Cow<'a, str>,
|
||||
delimiter: char,
|
||||
) -> Option<(Cow<'a, str>, Cow<'a, str>)> {
|
||||
match input {
|
||||
Cow::Borrowed(s) => s
|
||||
.rsplit_once(delimiter)
|
||||
.map(|(a, b)| (Cow::Borrowed(a), Cow::Borrowed(b))),
|
||||
Cow::Owned(s) => s
|
||||
.rsplit_once(delimiter)
|
||||
.map(|(a, b)| (Cow::Owned(a.to_string()), Cow::Owned(b.to_string()))),
|
||||
}
|
||||
}
|
||||
|
||||
let (new_segment, ext_excluding_dot) =
|
||||
if let Some((segment, ext_excluding_dot)) = rsplit(segment.segment.clone(), '.') {
|
||||
(segment, Some(ext_excluding_dot))
|
||||
} else {
|
||||
(segment.segment, None)
|
||||
};
|
||||
|
||||
Self {
|
||||
leading_separator: segment.leading_separator,
|
||||
segment: new_segment,
|
||||
ext_excluding_dot,
|
||||
location,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Path<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
let terminator = || {
|
||||
alt((
|
||||
eof.value(()),
|
||||
any::<&'a str, E>
|
||||
.verify(|i: &char| {
|
||||
(*i).is_whitespace()
|
||||
|| !(i.is_alphanumeric()
|
||||
|| ['_', '-', '\"', '\'', '.', '/', '\\'].contains(i))
|
||||
})
|
||||
.value(()),
|
||||
))
|
||||
};
|
||||
|
||||
let terminator_or_sep = || alt((PathSep::parse().value(()), terminator()));
|
||||
|
||||
let till_next_sep = || {
|
||||
trace(
|
||||
"till next sep",
|
||||
repeat_till(0.., any::<&'a str, E>, peek(terminator_or_sep()))
|
||||
.map(|(segment, _)| segment),
|
||||
)
|
||||
};
|
||||
|
||||
let sep_and_next =
|
||||
(PathSep::parse(), till_next_sep()).map(|(leading_separator, segment)| PathSegment {
|
||||
leading_separator,
|
||||
segment,
|
||||
});
|
||||
let opt_sep_and_next =
|
||||
(opt(PathSep::parse()), till_next_sep()).map(|(leading_separator, segment)| {
|
||||
PathSegment {
|
||||
leading_separator: leading_separator.unwrap_or(PathSep::None),
|
||||
segment,
|
||||
}
|
||||
});
|
||||
|
||||
let drive = opt((
|
||||
any::<&'a str, E>.verify(|x: &char| matches!(*x, 'A'..='Z' | 'a' ..= 'z')),
|
||||
':',
|
||||
))
|
||||
.map(|i| i.map(|(letter, _): (char, char)| letter));
|
||||
let drive_and_segments = (
|
||||
drive,
|
||||
opt_sep_and_next,
|
||||
repeat_till(0.., sep_and_next, peek(terminator()))
|
||||
.map(|(segments, _): (Vec<PathSegment>, _)| segments),
|
||||
opt(FileLocation::parse()),
|
||||
);
|
||||
|
||||
trace(
|
||||
"path",
|
||||
drive_and_segments
|
||||
.map(|(drive, segment, segments, location)| {
|
||||
let (segments, last) = {
|
||||
let mut segments = segments;
|
||||
segments.insert(0, segment);
|
||||
let last = segments.pop().unwrap();
|
||||
(segments, last)
|
||||
};
|
||||
|
||||
let filename = FileName::parse(last, location);
|
||||
|
||||
Self {
|
||||
drive_excluding_colon: drive,
|
||||
segments,
|
||||
filename,
|
||||
}
|
||||
})
|
||||
.verify(|i| {
|
||||
!i.segments.is_empty()
|
||||
|| i.filename.ext_excluding_dot.is_some()
|
||||
|| !matches!(i.filename.leading_separator, PathSep::None)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Atom<'a> {
|
||||
fn parse<E: ParserError<&'a str>, T: 'a>(
|
||||
terminated_by: impl Parser<&'a str, T, E>,
|
||||
) -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
let text = repeat::<_, _, Cow<'a, str>, _, _>(
|
||||
1..,
|
||||
(
|
||||
peek(not(terminated_by)),
|
||||
any::<&str, _>.verify(move |i: &char| !(*i).is_whitespace()),
|
||||
)
|
||||
.map(|(_, i)| i),
|
||||
)
|
||||
.map(Self::Text);
|
||||
|
||||
trace("atom", alt((text,)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
fn parse_without_separator<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*};
|
||||
|
||||
let delimited: Box<dyn Parser<&'a str, Self, E>> =
|
||||
Box::new(Delimited::parse().map(Self::Delimited));
|
||||
|
||||
trace(
|
||||
"token-without-sep",
|
||||
alt((
|
||||
"true".value(Self::True),
|
||||
"false".value(Self::False),
|
||||
"None".value(Self::None),
|
||||
Number::parse().map(Self::Number),
|
||||
Path::parse().map(Self::Path),
|
||||
AnyString::parse().map(Self::String),
|
||||
delimited,
|
||||
Atom::parse(alt((Separator::parse().value(""), ")", "]", "}", ">")))
|
||||
.map(Self::Atom),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
fn parse<E: ParserError<&'a str> + 'a>() -> Box<dyn Parser<&'a str, Self, E> + 'a> {
|
||||
use winnow::{combinator::*, prelude::*};
|
||||
|
||||
let before = Self::parse_without_separator();
|
||||
|
||||
Box::new(trace(
|
||||
"token",
|
||||
alt((
|
||||
(
|
||||
before,
|
||||
opt(
|
||||
(Space::parse(), Separator::parse()).flat_map(|(space, sep)| {
|
||||
let box_dyn_segment: Box<dyn Parser<_, _, _>> =
|
||||
Box::new(Segment::parse());
|
||||
box_dyn_segment.map(move |segment| (space.clone(), sep, segment))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.map(|(before, trailer)| {
|
||||
if let Some((space_before, separator, after)) = trailer {
|
||||
Token::Separated {
|
||||
before: Box::new(before),
|
||||
space_before,
|
||||
separator,
|
||||
after: Box::new(after),
|
||||
}
|
||||
} else {
|
||||
before
|
||||
}
|
||||
}),
|
||||
Atom::parse(fail::<_, (), _>).map(Self::Atom),
|
||||
)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Delimited<'a> {
|
||||
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
trace(
|
||||
"delimited",
|
||||
(
|
||||
opt((
|
||||
Atom::parse(alt(("(", "[", "{", "<", Separator::parse().value("")))),
|
||||
Space::parse(),
|
||||
)),
|
||||
alt((
|
||||
literal('(').map(|_| literal(')').value(Delimiter::Paren)),
|
||||
literal('[').map(|_| literal(']').value(Delimiter::Bracket)),
|
||||
literal('{').map(|_| literal('}').value(Delimiter::Brace)),
|
||||
literal('<').map(|_| literal('>').value(Delimiter::Angle)),
|
||||
))
|
||||
.flat_map(|end| Segments::parse(end)),
|
||||
)
|
||||
.map(|(prefix, (contents, delimiter))| Self {
|
||||
prefix,
|
||||
delimiter,
|
||||
contents,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Segments<'a> {
|
||||
fn parse<E: ParserError<&'a str> + 'a, End: 'a>(
|
||||
end: impl Parser<&'a str, End, E> + 'a,
|
||||
) -> Box<dyn Parser<&'a str, (Self, End), E> + 'a> {
|
||||
use winnow::{combinator::*, prelude::*};
|
||||
|
||||
Box::new(trace(
|
||||
"segments",
|
||||
repeat_till(0.., Segment::parse(), (Space::parse(), end)).map(
|
||||
|(segments, (trailing_space, end)): (Vec<_>, _)| {
|
||||
(
|
||||
Self {
|
||||
segments,
|
||||
trailing_space,
|
||||
},
|
||||
end,
|
||||
)
|
||||
},
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Number<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{ascii::*, combinator::*, prelude::*, token::*};
|
||||
|
||||
trace(
|
||||
"number",
|
||||
(
|
||||
alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take())),
|
||||
peek(not(any::<&'a str, E>.verify(|x: &char| x.is_alphabetic()))),
|
||||
)
|
||||
.map(|(i, _): (&str, _)| Self(i.into())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Segment<'a> {
|
||||
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::prelude::*;
|
||||
|
||||
trace(
|
||||
"segment",
|
||||
(Space::parse(), Token::parse()).map(|(leading_space, token)| Self {
|
||||
leading_space,
|
||||
token,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an input string (a log line) into an ast.
|
||||
///
|
||||
/// This *should* never error.
|
||||
/// Many tests ensure that arbitrary input can be parsed.
|
||||
/// Even if non-structured or completely random.
|
||||
/// The parser will gracefully accept such strings anyway, and just categorize them suboptimally.
|
||||
pub fn parse_input<'a>(i: &'a str) -> Result<Segments<'a>, String> {
|
||||
use winnow::combinator::eof;
|
||||
Segments::parse(eof::<&str, winnow::error::EmptyError>)
|
||||
.map(|(segments, _)| segments)
|
||||
.parse(i)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
use winnow::Parser;
|
||||
|
||||
use crate::{
|
||||
ast::{Path, Segments},
|
||||
parse_input,
|
||||
};
|
||||
|
||||
fn parse_path_only<'a>(i: &'a str) -> Path<'a> {
|
||||
Path::parse::<winnow::error::EmptyError>().parse(i).unwrap()
|
||||
}
|
||||
|
||||
fn parse<'a>(input: &'a str) -> Segments<'a> {
|
||||
parse_input(input).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_path() {
|
||||
assert_debug_snapshot!(parse_path_only(r#"tests/ui/impl-trait/unsized_coercion.rs"#), @r#"
|
||||
Path {
|
||||
drive_excluding_colon: None,
|
||||
segments: [
|
||||
PathSegment {
|
||||
leading_separator: None,
|
||||
segment: "tests",
|
||||
},
|
||||
PathSegment {
|
||||
leading_separator: Slash,
|
||||
segment: "ui",
|
||||
},
|
||||
PathSegment {
|
||||
leading_separator: Slash,
|
||||
segment: "impl-trait",
|
||||
},
|
||||
],
|
||||
filename: FileName {
|
||||
leading_separator: Slash,
|
||||
segment: "unsized_coercion",
|
||||
ext_excluding_dot: Some(
|
||||
"rs",
|
||||
),
|
||||
location: None,
|
||||
},
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_path_with_file_line() {
|
||||
assert_debug_snapshot!(parse_path_only(r#"tests/ui/impl-trait/unsized_coercion.rs:3:4"#), @r#"
|
||||
Path {
|
||||
drive_excluding_colon: None,
|
||||
segments: [
|
||||
PathSegment {
|
||||
leading_separator: None,
|
||||
segment: "tests",
|
||||
},
|
||||
PathSegment {
|
||||
leading_separator: Slash,
|
||||
segment: "ui",
|
||||
},
|
||||
PathSegment {
|
||||
leading_separator: Slash,
|
||||
segment: "impl-trait",
|
||||
},
|
||||
],
|
||||
filename: FileName {
|
||||
leading_separator: Slash,
|
||||
segment: "unsized_coercion",
|
||||
ext_excluding_dot: Some(
|
||||
"rs",
|
||||
),
|
||||
location: Some(
|
||||
FileLocation {
|
||||
line: "3",
|
||||
offset: Some(
|
||||
"4",
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty() {
|
||||
assert_debug_snapshot!(parse(r#""#), @r#"
|
||||
Segments {
|
||||
segments: [],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_text() {
|
||||
assert_debug_snapshot!(parse(r#"abc"#), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: Atom(
|
||||
Text(
|
||||
"abc",
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_boolean() {
|
||||
assert_debug_snapshot!(parse(r#"true"#), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: True,
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r#"false"#), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: False,
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string() {
|
||||
assert_debug_snapshot!(parse(r##""foo""##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "",
|
||||
ty: Double,
|
||||
contents: "foo",
|
||||
num_hashtags: 0,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r##"#"foo"#"##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "",
|
||||
ty: Double,
|
||||
contents: "foo",
|
||||
num_hashtags: 1,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r##"r#"foo"#"##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "r",
|
||||
ty: Double,
|
||||
contents: "foo",
|
||||
num_hashtags: 1,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r##"c"foo""##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "c",
|
||||
ty: Double,
|
||||
contents: "foo",
|
||||
num_hashtags: 0,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r##"b"foo""##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "b",
|
||||
ty: Double,
|
||||
contents: "foo",
|
||||
num_hashtags: 0,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r##"'a'"##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "",
|
||||
ty: Single,
|
||||
contents: "a",
|
||||
num_hashtags: 0,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r##"`b`"##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "",
|
||||
ty: Backtick,
|
||||
contents: "b",
|
||||
num_hashtags: 0,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r##"b'foo'"##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "b",
|
||||
ty: Single,
|
||||
contents: "foo",
|
||||
num_hashtags: 0,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
assert_debug_snapshot!(parse(r##"b`foo`"##), @r#"
|
||||
Segments {
|
||||
segments: [
|
||||
Segment {
|
||||
leading_space: Space(
|
||||
"",
|
||||
),
|
||||
token: String(
|
||||
AnyString {
|
||||
prefix: "b",
|
||||
ty: Backtick,
|
||||
contents: "foo",
|
||||
num_hashtags: 0,
|
||||
suffix: "",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
trailing_space: Space(
|
||||
"",
|
||||
),
|
||||
}
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
use super::ast::*;
|
||||
use crate::parse_input;
|
||||
use crate::{Config, into_spans};
|
||||
use proptest::prelude::*;
|
||||
use proptest::proptest;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn proptest_from_segments(original in Segments::arb(Token::arb())) {
|
||||
let stringified = original.to_string();
|
||||
let parsed = parse_input(&stringified).unwrap();
|
||||
let stringified_again = parsed.to_string();
|
||||
assert_eq!(stringified, stringified_again, "parsed: `{parsed:#?}`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proptest_from_random_text(original in ".*") {
|
||||
let parsed = parse_input(&original).unwrap();
|
||||
let stringified_again = parsed.to_string();
|
||||
assert_eq!(original, stringified_again, "parsed: `{parsed:#?}`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proptest_into_spans(original in Segments::arb(Token::arb())) {
|
||||
let stringified = original.to_string();
|
||||
let spans = into_spans(original, Config {collapse_space: false});
|
||||
let spans_concatenated = spans.into_iter().map(|i| i.text).collect::<String>();
|
||||
assert_eq!(stringified, spans_concatenated);
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyString<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
let prefix = "\\w*";
|
||||
let ty = any::<QuoteType>();
|
||||
let contents = "\\w*";
|
||||
let num_hashtags = 0usize..3;
|
||||
let suffix = "\\w*";
|
||||
|
||||
(prefix, ty, contents, num_hashtags, suffix).prop_map(
|
||||
|(prefix, ty, contents, num_hashtags, suffix)| Self {
|
||||
prefix: prefix.into(),
|
||||
ty,
|
||||
contents: contents.into(),
|
||||
num_hashtags,
|
||||
suffix: suffix.into(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PathSegment<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
(any::<PathSep>(), "\\w*").prop_map(|(leading_separator, segment)| Self {
|
||||
leading_separator,
|
||||
segment: segment.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FileLocation<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
use proptest::option::*;
|
||||
("[0-9]{0,4}", of("[0-9]{0,4}")).prop_map(|(line, offset)| Self {
|
||||
line: line.into(),
|
||||
offset: offset.map(Into::into),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Space<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
" *".prop_map(|spaces| Self(spaces.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FileName<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
use proptest::option::*;
|
||||
(
|
||||
any::<PathSep>(),
|
||||
"\\w*",
|
||||
of(".{0,3}"),
|
||||
of(FileLocation::arb()),
|
||||
)
|
||||
.prop_map(
|
||||
|(leading_separator, segment, ext_excluding_dot, location)| Self {
|
||||
leading_separator,
|
||||
segment: segment.into(),
|
||||
ext_excluding_dot: ext_excluding_dot.map(Into::into),
|
||||
location,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Path<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
use proptest::{char::*, collection::*, option::*};
|
||||
(
|
||||
of(range('A', 'Z')),
|
||||
vec(PathSegment::arb(), 0..3),
|
||||
FileName::arb(),
|
||||
)
|
||||
.prop_map(|(drive_excluding_colon, segments, filename)| Self {
|
||||
drive_excluding_colon,
|
||||
segments,
|
||||
filename,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Number<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
prop_oneof![
|
||||
any::<i64>().prop_map(|number| Self(number.to_string().into())),
|
||||
any::<f64>().prop_map(|number| Self(number.to_string().into()))
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Atom<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
"[a-zA-Z]+".prop_map(|i| Self::Text(i.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Token<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb() -> impl Strategy<Value = Self> {
|
||||
let leaf = prop_oneof![
|
||||
Just(Self::True),
|
||||
Just(Self::False),
|
||||
Just(Self::None),
|
||||
Path::arb().prop_map(Self::Path),
|
||||
AnyString::arb().prop_map(Self::String),
|
||||
Number::arb().prop_map(Self::Number),
|
||||
Atom::arb().prop_map(Self::Atom),
|
||||
];
|
||||
|
||||
leaf.prop_recursive(4, 64, 16, |token| {
|
||||
Delimited::arb(token).prop_map(Self::Delimited).boxed()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Delimited<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
||||
use proptest::option::*;
|
||||
(
|
||||
of((Atom::arb(), Space::arb())),
|
||||
any::<Delimiter>(),
|
||||
Segments::arb(token),
|
||||
)
|
||||
.prop_map(|(prefix, delimiter, contents)| Self {
|
||||
prefix,
|
||||
delimiter,
|
||||
contents,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Segment<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
||||
(Space::arb(), token).prop_map(|(leading_space, token)| Self {
|
||||
leading_space,
|
||||
token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Segments<'static> {
|
||||
#[cfg(test)]
|
||||
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
||||
use proptest::collection::*;
|
||||
|
||||
(vec(Segment::arb(token), 1..10), Space::arb()).prop_map(|(segments, trailing_space)| {
|
||||
Self {
|
||||
segments,
|
||||
trailing_space,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,992 +0,0 @@
|
|||
use super::ast::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Text categories, based on the parsing.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum SpanKind {
|
||||
/// Parentheses e.g.
|
||||
///
|
||||
/// Stores the delimiter depth, for e.g. rainbow delimiters.
|
||||
Delimiter(usize),
|
||||
/// Separators like `=`, ':' and ','
|
||||
Separator,
|
||||
/// Numbers
|
||||
Number,
|
||||
/// Known literals, like `true`, `false`, `None`, `Ok`, `Err`
|
||||
Literal,
|
||||
/// Strings
|
||||
String,
|
||||
/// Paths
|
||||
Path,
|
||||
/// Spaces (returns original number of spaces)
|
||||
Space(usize),
|
||||
|
||||
/// Constructor: the prefix of a delimited block.
|
||||
/// i.e. `Some` in `Some(3)`
|
||||
Constructor,
|
||||
|
||||
/// String prefix, suffix, hashtags, etc
|
||||
StringSurroundings,
|
||||
|
||||
/// Any other text (the default)
|
||||
Text,
|
||||
}
|
||||
|
||||
/// A `Span` is a piece of categorized text, based on the parsing done by
|
||||
/// [`parse_input`](crate::parse_input).
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Span<'a> {
|
||||
/// The segment of text.
|
||||
pub text: Cow<'a, str>,
|
||||
/// Its category.
|
||||
pub kind: SpanKind,
|
||||
}
|
||||
|
||||
/// Configuration options for [`into_spans`]
|
||||
pub struct Config {
|
||||
/// Turn sequences of more than 1 space into exactly 1 space.
|
||||
pub collapse_space: bool,
|
||||
}
|
||||
|
||||
pub trait IntoSpans<'a>: private::IntoSpansImpl<'a> {}
|
||||
|
||||
/// Turn an ast node into [`Span`]s.
|
||||
pub fn into_spans<'a>(ast: impl IntoSpans<'a>, config: Config) -> Vec<Span<'a>> {
|
||||
let mut cx = private::Context {
|
||||
config,
|
||||
res: Vec::new(),
|
||||
depth: 0,
|
||||
};
|
||||
ast.into_spans(&mut cx);
|
||||
cx.res
|
||||
}
|
||||
|
||||
mod private {
|
||||
use super::*;
|
||||
|
||||
pub struct Context<'a> {
|
||||
pub config: Config,
|
||||
pub res: Vec<Span<'a>>,
|
||||
pub depth: usize,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
fn push(&mut self, text: impl Into<Cow<'a, str>>, kind: SpanKind) {
|
||||
self.res.push(Span {
|
||||
text: text.into(),
|
||||
kind,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoSpansImpl<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>);
|
||||
}
|
||||
|
||||
impl<'a, T> IntoSpans<'a> for T where T: IntoSpansImpl<'a> {}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Separator {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
match self {
|
||||
Separator::Eq => cx.push("=", SpanKind::Separator),
|
||||
Separator::Colon => cx.push(":", SpanKind::Separator),
|
||||
Separator::DoubleColon => cx.push("::", SpanKind::Separator),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for QuoteType {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
match self {
|
||||
QuoteType::Single => cx.push("'", SpanKind::Separator),
|
||||
QuoteType::Double => cx.push("\"", SpanKind::Separator),
|
||||
QuoteType::Backtick => cx.push("`", SpanKind::Separator),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for AnyString<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
let Self {
|
||||
prefix,
|
||||
ty,
|
||||
contents,
|
||||
num_hashtags,
|
||||
suffix,
|
||||
} = self;
|
||||
cx.push(prefix, SpanKind::StringSurroundings);
|
||||
for _ in 0..num_hashtags {
|
||||
cx.push("#", SpanKind::StringSurroundings)
|
||||
}
|
||||
|
||||
ty.into_spans(cx);
|
||||
cx.push(contents, SpanKind::String);
|
||||
ty.into_spans(cx);
|
||||
|
||||
for _ in 0..num_hashtags {
|
||||
cx.push("#", SpanKind::StringSurroundings)
|
||||
}
|
||||
cx.push(suffix, SpanKind::StringSurroundings);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Path<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
cx.push(self.to_string(), SpanKind::Path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Number<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
cx.push(self.0, SpanKind::Number)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Atom<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
match self {
|
||||
Atom::Text(text) => cx.push(text, SpanKind::Text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Space<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
match self.0.len() {
|
||||
0 => {}
|
||||
1 => cx.push(self.0, SpanKind::Space(1)),
|
||||
n if cx.config.collapse_space => cx.push(" ", SpanKind::Space(n)),
|
||||
n => cx.push(self.0, SpanKind::Space(n)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Token<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
match self {
|
||||
Token::True => cx.push("true", SpanKind::Literal),
|
||||
Token::False => cx.push("false", SpanKind::Literal),
|
||||
Token::None => cx.push("None", SpanKind::Literal),
|
||||
Token::Path(path) => path.into_spans(cx),
|
||||
Token::String(string) => string.into_spans(cx),
|
||||
Token::Number(number) => number.into_spans(cx),
|
||||
Token::Separated {
|
||||
before,
|
||||
space_before,
|
||||
separator,
|
||||
after,
|
||||
} => {
|
||||
before.into_spans(cx);
|
||||
space_before.into_spans(cx);
|
||||
separator.into_spans(cx);
|
||||
after.into_spans(cx);
|
||||
}
|
||||
Token::Delimited(delimited) => {
|
||||
delimited.into_spans(cx);
|
||||
}
|
||||
Token::Atom(atom) => {
|
||||
atom.into_spans(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Segment<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
let Self {
|
||||
leading_space,
|
||||
token,
|
||||
} = self;
|
||||
leading_space.into_spans(cx);
|
||||
token.into_spans(cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Segments<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
let Self {
|
||||
segments,
|
||||
trailing_space,
|
||||
} = self;
|
||||
for segment in segments {
|
||||
segment.into_spans(cx);
|
||||
}
|
||||
trailing_space.into_spans(cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSpansImpl<'a> for Delimited<'a> {
|
||||
fn into_spans(self, cx: &mut Context<'a>) {
|
||||
let Self {
|
||||
prefix,
|
||||
delimiter,
|
||||
contents,
|
||||
} = self;
|
||||
|
||||
match prefix {
|
||||
Some((Atom::Text(text), space)) => {
|
||||
cx.push(
|
||||
text,
|
||||
match delimiter {
|
||||
Delimiter::Bracket => SpanKind::Constructor,
|
||||
Delimiter::Paren if space.0.is_empty() => SpanKind::Constructor,
|
||||
Delimiter::Paren => SpanKind::Text,
|
||||
Delimiter::Brace => SpanKind::Text,
|
||||
Delimiter::Angle if space.0.is_empty() => SpanKind::Constructor,
|
||||
Delimiter::Angle => SpanKind::Text,
|
||||
},
|
||||
);
|
||||
space.into_spans(cx);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
match delimiter {
|
||||
Delimiter::Paren => cx.push("(", SpanKind::Delimiter(cx.depth)),
|
||||
Delimiter::Bracket => cx.push("[", SpanKind::Delimiter(cx.depth)),
|
||||
Delimiter::Brace => cx.push("{", SpanKind::Delimiter(cx.depth)),
|
||||
Delimiter::Angle => cx.push("<", SpanKind::Delimiter(cx.depth)),
|
||||
}
|
||||
|
||||
cx.depth += 1;
|
||||
contents.into_spans(cx);
|
||||
cx.depth -= 1;
|
||||
|
||||
match delimiter {
|
||||
Delimiter::Paren => cx.push(")", SpanKind::Delimiter(cx.depth)),
|
||||
Delimiter::Bracket => cx.push("]", SpanKind::Delimiter(cx.depth)),
|
||||
Delimiter::Brace => cx.push("}", SpanKind::Delimiter(cx.depth)),
|
||||
Delimiter::Angle => cx.push(">", SpanKind::Delimiter(cx.depth)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use super::SpanKind;
|
||||
use crate::{Config, into_spans, parse_input};
|
||||
|
||||
fn spans(input: &str) -> Vec<(String, SpanKind)> {
|
||||
let res = parse_input(input).unwrap();
|
||||
into_spans(
|
||||
res,
|
||||
Config {
|
||||
collapse_space: true,
|
||||
},
|
||||
)
|
||||
.into_iter()
|
||||
.map(|i| (i.text.into_owned(), i.kind))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spans_ex1() {
|
||||
assert_debug_snapshot!(spans(
|
||||
r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"#
|
||||
), @r#"
|
||||
[
|
||||
(
|
||||
"def_id",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"=",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"DefId",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"0",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
":",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"3",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"~",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"unsized_coercion",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"[",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"10fa",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"]",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"::",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"Trait",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
]
|
||||
"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spans_ex2() {
|
||||
assert_debug_snapshot!(spans(
|
||||
r#"data=TypeNs("MetaSized") visible_parent=DefId(2:3984 ~ core[bcc4]::marker) actual_parent=Some(DefId(2:3984 ~ core[bcc4]::marker))"#
|
||||
), @r#"
|
||||
[
|
||||
(
|
||||
"data",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"=",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"TypeNs",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"",
|
||||
StringSurroundings,
|
||||
),
|
||||
(
|
||||
"\"",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"MetaSized",
|
||||
String,
|
||||
),
|
||||
(
|
||||
"\"",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"",
|
||||
StringSurroundings,
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"visible_parent",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"=",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"DefId",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"2",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
":",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"3984",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"~",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"core",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"[",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"bcc4",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"]",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"::",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"marker",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"actual_parent",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"=",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"Some",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"DefId",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"2",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
":",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"3984",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"~",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"core",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"[",
|
||||
Delimiter(
|
||||
2,
|
||||
),
|
||||
),
|
||||
(
|
||||
"bcc4",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"]",
|
||||
Delimiter(
|
||||
2,
|
||||
),
|
||||
),
|
||||
(
|
||||
"::",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"marker",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
]
|
||||
"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spans_ex3() {
|
||||
assert_debug_snapshot!(spans(
|
||||
r#"insert(DefId(0:4 ~ unsized_coercion[10fa]::{impl#0})): inserting TraitRef <u32 as Trait> into specialization graph"#
|
||||
), @r#"
|
||||
[
|
||||
(
|
||||
"insert",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"DefId",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"0",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
":",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"4",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"~",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"unsized_coercion",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"[",
|
||||
Delimiter(
|
||||
2,
|
||||
),
|
||||
),
|
||||
(
|
||||
"10fa",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"]",
|
||||
Delimiter(
|
||||
2,
|
||||
),
|
||||
),
|
||||
(
|
||||
"::",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"{",
|
||||
Delimiter(
|
||||
2,
|
||||
),
|
||||
),
|
||||
(
|
||||
"impl#0",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"}",
|
||||
Delimiter(
|
||||
2,
|
||||
),
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
":",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"inserting",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"TraitRef",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"<",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"u32",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"as",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Trait",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
">",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"into",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"specialization",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"graph",
|
||||
Text,
|
||||
),
|
||||
]
|
||||
"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spans_ex4() {
|
||||
assert_debug_snapshot!(spans(
|
||||
r#"inspecting def_id=DefId(3:662 ~ alloc[ef11]::boxed::Box) span=tests/ui/impl-trait/unsized_coercion.rs:12:15: 12:30 (#0)"#
|
||||
), @r##"
|
||||
[
|
||||
(
|
||||
"inspecting",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"def_id",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"=",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"DefId",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"3",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
":",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"662",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"~",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"alloc",
|
||||
Constructor,
|
||||
),
|
||||
(
|
||||
"[",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"ef11",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"]",
|
||||
Delimiter(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"::",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"boxed",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"::",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"Box",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"span",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
"=",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"tests/ui/impl-trait/unsized_coercion.rs:12:15",
|
||||
Path,
|
||||
),
|
||||
(
|
||||
":",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"12",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
":",
|
||||
Separator,
|
||||
),
|
||||
(
|
||||
"30",
|
||||
Number,
|
||||
),
|
||||
(
|
||||
" ",
|
||||
Space(
|
||||
1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"(",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"#0",
|
||||
Text,
|
||||
),
|
||||
(
|
||||
")",
|
||||
Delimiter(
|
||||
0,
|
||||
),
|
||||
),
|
||||
]
|
||||
"##)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 255ec5cdb18e04e16d465fb94cbf15040fb1d5d704c53226ce3a2e9333c6de7c # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Path(Path { drive_excluding_colon: None, segments: [], filename: FileName { leading_separator: Slash, segment: "", ext_excluding_dot: None, location: None } }) }], trailing_space: Space("") }
|
||||
cc 809c7a1b555b4e553cc8855c8a27de7a87e94c0d22b55ae9d7f6d4dff5f6cde0 # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Delimited(Delimited { prefix: Text("a"), delimiter: Paren, contents: Segments { segments: [Segment { leading_space: Space(" "), token: Path(Path { drive_excluding_colon: None, segments: [], filename: FileName { leading_separator: Slash, segment: "", ext_excluding_dot: Some("\\"), location: None } }) }, Segment { leading_space: Space(" "), token: True }], trailing_space: Space("") } }) }], trailing_space: Space("") }
|
||||
cc 0b39a0bcfdada4d65ebbcbc80ed000c6b9cc1b60e7de6ffd004a7ee7db350d6c # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Delimited(Delimited { prefix: Text("A"), delimiter: Paren, contents: Segments { segments: [Segment { leading_space: Space(""), token: Path(Path { drive_excluding_colon: None, segments: [], filename: FileName { leading_separator: Slash, segment: "", ext_excluding_dot: Some(":"), location: None } }) }], trailing_space: Space(" ") } }) }], trailing_space: Space("") }
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 1dac24f74cdeb63f61d662876f276058bc71481f1df552aeea1293e22b682d59 # shrinks to original = "¡{}"
|
||||
cc f7a17a233c11246ea8182505c41e30dc2a2f1c9020d5108f95403eb2de179fac # shrinks to original = ")"
|
||||
cc 7bfbe4d3505dc0e94e5b87ae86e4b4554d9af477f4d0770161c2403ce39627f3 # shrinks to original = "!/\t"
|
||||
|
|
@ -5,7 +5,7 @@ use crossterm::{
|
|||
},
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui_themes::{Color, Theme, ThemeName};
|
||||
use ratatui_themes::{Theme, ThemeName};
|
||||
use std::{
|
||||
fs::{self, DirEntry},
|
||||
io::{self, Stdout},
|
||||
|
|
@ -493,7 +493,6 @@ impl App {
|
|||
|
||||
fn styles(&self) -> Styles {
|
||||
let palette = self.theme.palette();
|
||||
|
||||
let default = Style::new().fg(palette.fg).bg(palette.bg);
|
||||
let highlighted = Style::new().fg(palette.accent).bg(palette.selection);
|
||||
let border = Style::new().fg(palette.fg).bg(palette.bg);
|
||||
|
|
@ -506,11 +505,6 @@ impl App {
|
|||
border,
|
||||
border_highlighted,
|
||||
error,
|
||||
|
||||
literal: palette.secondary,
|
||||
faded: palette.muted,
|
||||
delimiter: palette.accent,
|
||||
string: palette.accent,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -535,11 +529,6 @@ pub struct Styles {
|
|||
border: Style,
|
||||
border_highlighted: Style,
|
||||
error: Style,
|
||||
|
||||
string: Color,
|
||||
literal: Color,
|
||||
faded: Color,
|
||||
delimiter: Color,
|
||||
}
|
||||
|
||||
impl Widget for &mut App {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ impl Widget for Styled<'_, &Items<'_>> {
|
|||
};
|
||||
let line_text = entry.line_text(prefix, self.filters);
|
||||
|
||||
let mut line = line_text.styled(self.styles);
|
||||
let mut line = line_text.styled(&self.styles);
|
||||
if idx == self.selected_offset
|
||||
&& let InputState::None
|
||||
| InputState::Target(InputTarget::This)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use ratatui::text::{Line, Span, Text};
|
||||
|
||||
use crate::tui::widgets::styled::Styled;
|
||||
use logparse::{self as lp, Config, SpanKind, into_spans, parse_input};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Highlighted {
|
||||
None,
|
||||
All,
|
||||
|
|
@ -41,124 +37,6 @@ impl LineText {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HighlightedSpan<'a> {
|
||||
span: lp::Span<'a>,
|
||||
highlighted: bool,
|
||||
}
|
||||
|
||||
fn cow_split_at<'a>(inp: Cow<'a, str>, offset: usize) -> (Cow<'a, str>, Cow<'a, str>) {
|
||||
match inp {
|
||||
Cow::Borrowed(t) => {
|
||||
let (a, b) = t.split_at(offset);
|
||||
(Cow::Borrowed(a), Cow::Borrowed(b))
|
||||
}
|
||||
Cow::Owned(t) => {
|
||||
let (a, b) = t.split_at(offset);
|
||||
(Cow::Owned(a.to_string()), Cow::Owned(b.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn span_len(span: &lp::Span<'_>) -> usize {
|
||||
match span.kind {
|
||||
SpanKind::Space(n) => n,
|
||||
_ => span.text.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_spans<'a>(
|
||||
i: impl Iterator<Item = lp::Span<'a>>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
) -> impl Iterator<Item = HighlightedSpan<'a>> {
|
||||
let mut curr_offset = 0;
|
||||
|
||||
i.flat_map(move |span| {
|
||||
// entirely before
|
||||
if curr_offset + span.text.len() <= start {
|
||||
curr_offset += span_len(&span);
|
||||
return vec![HighlightedSpan {
|
||||
span,
|
||||
highlighted: false,
|
||||
}];
|
||||
}
|
||||
|
||||
let lp::Span { text, kind } = span;
|
||||
|
||||
let mut res = Vec::new();
|
||||
|
||||
// over the start, push what's before and continue
|
||||
let text = if curr_offset < start && curr_offset + text.len() > start {
|
||||
let until_start = start - curr_offset;
|
||||
let (before, after) = cow_split_at(text, until_start);
|
||||
|
||||
let before_span = lp::Span { kind, text: before };
|
||||
|
||||
let l = span_len(&before_span);
|
||||
if l != 0 {
|
||||
curr_offset += l;
|
||||
|
||||
res.push(HighlightedSpan {
|
||||
span: before_span,
|
||||
highlighted: false,
|
||||
});
|
||||
}
|
||||
|
||||
after
|
||||
} else {
|
||||
text
|
||||
};
|
||||
|
||||
if text.is_empty() {
|
||||
return res;
|
||||
}
|
||||
|
||||
// entirely within
|
||||
if curr_offset + text.len() <= end {
|
||||
let span = lp::Span { kind, text };
|
||||
curr_offset += span_len(&span);
|
||||
res.push(HighlightedSpan {
|
||||
span,
|
||||
highlighted: true,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
// over the end boundary?
|
||||
let text = if curr_offset < end && curr_offset + text.len() > end {
|
||||
let until_start = end - curr_offset;
|
||||
let (before, after) = cow_split_at(text, until_start);
|
||||
|
||||
let before_span = lp::Span { kind, text: before };
|
||||
|
||||
curr_offset += span_len(&before_span);
|
||||
res.push(HighlightedSpan {
|
||||
span: before_span,
|
||||
highlighted: true,
|
||||
});
|
||||
|
||||
after
|
||||
} else {
|
||||
text
|
||||
};
|
||||
|
||||
if text.is_empty() {
|
||||
return res;
|
||||
}
|
||||
|
||||
let after = lp::Span { kind, text };
|
||||
|
||||
curr_offset += span_len(&after);
|
||||
res.push(HighlightedSpan {
|
||||
span: after,
|
||||
highlighted: false,
|
||||
});
|
||||
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
impl Into<Line<'static>> for Styled<'_, LineText> {
|
||||
fn into(self) -> Line<'static> {
|
||||
let mut spans = Vec::new();
|
||||
|
|
@ -167,72 +45,20 @@ impl Into<Line<'static>> for Styled<'_, LineText> {
|
|||
spans.push(Span::from("┃ "));
|
||||
spans.push(Span::from(self.inner.tree));
|
||||
|
||||
spans.extend(if let Ok(parsed) = parse_input(&self.inner.message) {
|
||||
let spans = into_spans(
|
||||
parsed,
|
||||
Config {
|
||||
collapse_space: true,
|
||||
},
|
||||
);
|
||||
|
||||
let spans: Vec<_> = match self.inner.highlighted {
|
||||
Highlighted::None => spans
|
||||
.into_iter()
|
||||
.map(|span| HighlightedSpan {
|
||||
span,
|
||||
highlighted: false,
|
||||
})
|
||||
.collect(),
|
||||
Highlighted::All => spans
|
||||
.into_iter()
|
||||
.map(|span| HighlightedSpan {
|
||||
span,
|
||||
highlighted: true,
|
||||
})
|
||||
.collect(),
|
||||
Highlighted::Range { from, to } => {
|
||||
highlight_spans(spans.into_iter(), from, to).collect()
|
||||
match self.inner.highlighted {
|
||||
Highlighted::None => {
|
||||
spans.push(Span::from(self.inner.message).style(self.styles.default))
|
||||
}
|
||||
Highlighted::All => {
|
||||
spans.push(Span::from(self.inner.message).style(self.styles.highlighted))
|
||||
}
|
||||
Highlighted::Range { from, to } => spans.extend_from_slice(&[
|
||||
Span::from(self.inner.message[..from].to_string()).style(self.styles.default),
|
||||
Span::from(self.inner.message[from..to].to_string()).style(self.styles.highlighted),
|
||||
Span::from(self.inner.message[to..].to_string()).style(self.styles.default),
|
||||
]),
|
||||
};
|
||||
|
||||
let spans: Vec<_> = spans
|
||||
.into_iter()
|
||||
.map(
|
||||
|HighlightedSpan {
|
||||
span: lp::Span { text, kind },
|
||||
highlighted,
|
||||
}| {
|
||||
let span = Span::from(text.into_owned());
|
||||
|
||||
let style = if highlighted {
|
||||
self.styles.highlighted
|
||||
} else {
|
||||
self.styles.default
|
||||
};
|
||||
|
||||
let style = match kind {
|
||||
SpanKind::Delimiter(_) => style.fg(self.styles.delimiter).bold(),
|
||||
SpanKind::Separator => style.fg(self.styles.faded),
|
||||
SpanKind::Number => style.fg(self.styles.literal),
|
||||
SpanKind::Literal => style.fg(self.styles.literal).dim(),
|
||||
SpanKind::String => style.fg(self.styles.string),
|
||||
SpanKind::Path => style.fg(self.styles.literal).underlined(),
|
||||
SpanKind::Space(_) => style,
|
||||
SpanKind::Constructor => style.fg(self.styles.literal),
|
||||
SpanKind::StringSurroundings => style.fg(self.styles.faded),
|
||||
SpanKind::Text => style,
|
||||
};
|
||||
|
||||
span.style(style)
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
spans
|
||||
} else {
|
||||
vec![Span::from(self.inner.message).style(self.styles.default)]
|
||||
});
|
||||
|
||||
Line::from(spans)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue