yerba 0.1.0-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/exe/x86_64-linux/yerba +0 -0
- data/exe/yerba +6 -0
- data/ext/yerba/extconf.rb +54 -0
- data/lib/yerba/version.rb +5 -0
- data/lib/yerba.rb +105 -0
- data/rust/Cargo.lock +429 -0
- data/rust/Cargo.toml +32 -0
- data/rust/rustfmt.toml +2 -0
- data/rust/src/document.rs +1779 -0
- data/rust/src/error.rs +50 -0
- data/rust/src/json.rs +169 -0
- data/rust/src/lib.rs +22 -0
- data/rust/src/main.rs +856 -0
- data/rust/src/quote_style.rs +42 -0
- data/rust/src/syntax.rs +186 -0
- data/rust/src/yerbafile.rs +563 -0
- data/sig/yerba.rbs +3 -0
- data/yerba.gemspec +48 -0
- metadata +69 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
use clap::ValueEnum;
|
|
2
|
+
use yaml_parser::SyntaxKind;
|
|
3
|
+
|
|
4
|
+
#[derive(Debug, Clone, PartialEq, ValueEnum)]
|
|
5
|
+
pub enum QuoteStyle {
|
|
6
|
+
Plain,
|
|
7
|
+
#[value(alias = "single-quoted")]
|
|
8
|
+
Single,
|
|
9
|
+
#[value(alias = "double-quoted")]
|
|
10
|
+
Double,
|
|
11
|
+
#[value(alias = "block-literal")]
|
|
12
|
+
Literal,
|
|
13
|
+
#[value(alias = "block-folded")]
|
|
14
|
+
Folded,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl std::str::FromStr for QuoteStyle {
|
|
18
|
+
type Err = String;
|
|
19
|
+
|
|
20
|
+
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
|
21
|
+
match string {
|
|
22
|
+
"plain" => Ok(QuoteStyle::Plain),
|
|
23
|
+
"single" | "single-quoted" => Ok(QuoteStyle::Single),
|
|
24
|
+
"double" | "double-quoted" => Ok(QuoteStyle::Double),
|
|
25
|
+
"literal" | "block-literal" => Ok(QuoteStyle::Literal),
|
|
26
|
+
"folded" | "block-folded" => Ok(QuoteStyle::Folded),
|
|
27
|
+
_ => Err(format!("unknown quote style: '{}'", string)),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl QuoteStyle {
|
|
33
|
+
pub(crate) fn to_syntax_kind(&self) -> SyntaxKind {
|
|
34
|
+
match self {
|
|
35
|
+
QuoteStyle::Plain => SyntaxKind::PLAIN_SCALAR,
|
|
36
|
+
QuoteStyle::Single => SyntaxKind::SINGLE_QUOTED_SCALAR,
|
|
37
|
+
QuoteStyle::Double => SyntaxKind::DOUBLE_QUOTED_SCALAR,
|
|
38
|
+
QuoteStyle::Literal => SyntaxKind::BLOCK_SCALAR_TEXT,
|
|
39
|
+
QuoteStyle::Folded => SyntaxKind::BLOCK_SCALAR_TEXT,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
data/rust/src/syntax.rs
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
use rowan::ast::AstNode;
|
|
2
|
+
use rowan::{TextRange, TextSize};
|
|
3
|
+
|
|
4
|
+
use yaml_parser::ast::{BlockMap, BlockMapEntry};
|
|
5
|
+
use yaml_parser::{SyntaxKind, SyntaxNode, SyntaxToken};
|
|
6
|
+
|
|
7
|
+
pub fn is_map_key(token: &SyntaxToken) -> bool {
|
|
8
|
+
token
|
|
9
|
+
.parent_ancestors()
|
|
10
|
+
.any(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_KEY)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub fn find_entry_by_key(map: &BlockMap, key: &str) -> Option<BlockMapEntry> {
|
|
14
|
+
map.entries().find(|entry| {
|
|
15
|
+
entry
|
|
16
|
+
.key()
|
|
17
|
+
.and_then(|key_node| extract_scalar_text(key_node.syntax()))
|
|
18
|
+
.map(|key_text| key_text == key)
|
|
19
|
+
.unwrap_or(false)
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn find_scalar_token(node: &SyntaxNode) -> Option<SyntaxToken> {
|
|
24
|
+
node
|
|
25
|
+
.descendants_with_tokens()
|
|
26
|
+
.filter_map(|element| element.into_token())
|
|
27
|
+
.find(|token| {
|
|
28
|
+
matches!(
|
|
29
|
+
token.kind(),
|
|
30
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn format_scalar_value(value: &str, kind: SyntaxKind) -> String {
|
|
36
|
+
match kind {
|
|
37
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
38
|
+
let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
|
|
39
|
+
format!("\"{}\"", escaped)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
43
|
+
let escaped = value.replace('\'', "''");
|
|
44
|
+
format!("'{}'", escaped)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_ => value.to_string(),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn extract_scalar_text(node: &SyntaxNode) -> Option<String> {
|
|
52
|
+
let token = find_scalar_token(node)?;
|
|
53
|
+
|
|
54
|
+
match token.kind() {
|
|
55
|
+
SyntaxKind::PLAIN_SCALAR => Some(token.text().to_string()),
|
|
56
|
+
|
|
57
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
58
|
+
let text = token.text();
|
|
59
|
+
let inner = &text[1..text.len() - 1];
|
|
60
|
+
|
|
61
|
+
Some(unescape_double_quoted(inner))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
65
|
+
let text = token.text();
|
|
66
|
+
let inner = &text[1..text.len() - 1];
|
|
67
|
+
|
|
68
|
+
Some(unescape_single_quoted(inner))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_ => None,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub fn unescape_double_quoted(text: &str) -> String {
|
|
76
|
+
text.replace("\\\"", "\"").replace("\\\\", "\\")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pub fn unescape_single_quoted(text: &str) -> String {
|
|
80
|
+
text.replace("''", "'")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn preceding_whitespace_indent(node: &SyntaxNode) -> String {
|
|
84
|
+
if let Some(token) = preceding_whitespace_token(node) {
|
|
85
|
+
let text = token.text();
|
|
86
|
+
|
|
87
|
+
if let Some(newline) = text.rfind('\n') {
|
|
88
|
+
return text[newline + 1..].to_string();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let start_offset: usize = node.text_range().start().into();
|
|
93
|
+
let root = node.ancestors().last().unwrap_or_else(|| node.clone());
|
|
94
|
+
let source = root.text().to_string();
|
|
95
|
+
|
|
96
|
+
if start_offset > 0 {
|
|
97
|
+
let before = &source[..start_offset];
|
|
98
|
+
|
|
99
|
+
if let Some(newline_position) = before.rfind('\n') {
|
|
100
|
+
return before[newline_position + 1..].to_string();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
String::new()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub fn preceding_whitespace_token(node: &SyntaxNode) -> Option<SyntaxToken> {
|
|
108
|
+
node
|
|
109
|
+
.prev_sibling_or_token()
|
|
110
|
+
.and_then(|sibling| sibling.into_token())
|
|
111
|
+
.filter(|token| token.kind() == SyntaxKind::WHITESPACE)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pub fn following_whitespace_token(node: &SyntaxNode) -> Option<SyntaxToken> {
|
|
115
|
+
node
|
|
116
|
+
.next_sibling_or_token()
|
|
117
|
+
.and_then(|sibling| sibling.into_token())
|
|
118
|
+
.filter(|token| token.kind() == SyntaxKind::WHITESPACE)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pub fn removal_range(node: &SyntaxNode) -> TextRange {
|
|
122
|
+
let node_range = node.text_range();
|
|
123
|
+
|
|
124
|
+
if let Some(whitespace_token) = preceding_whitespace_token(node) {
|
|
125
|
+
let whitespace_text = whitespace_token.text();
|
|
126
|
+
let whitespace_start = whitespace_token.text_range().start();
|
|
127
|
+
|
|
128
|
+
let remove_from = whitespace_text
|
|
129
|
+
.rfind('\n')
|
|
130
|
+
.map(|offset| whitespace_start + TextSize::from(offset as u32))
|
|
131
|
+
.unwrap_or(whitespace_start);
|
|
132
|
+
|
|
133
|
+
return TextRange::new(remove_from, node_range.end());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if let Some(whitespace_token) = following_whitespace_token(node) {
|
|
137
|
+
return TextRange::new(node_range.start(), whitespace_token.text_range().end());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
node_range
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pub fn is_yaml_non_string(value: &str) -> bool {
|
|
144
|
+
// Null (YAML 1.1 + 1.2)
|
|
145
|
+
if matches!(value, "null" | "Null" | "NULL" | "~" | "") {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Boolean (YAML 1.2)
|
|
150
|
+
if matches!(value, "true" | "True" | "TRUE" | "false" | "False" | "FALSE") {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Boolean (YAML 1.1 extras)
|
|
155
|
+
if matches!(
|
|
156
|
+
value,
|
|
157
|
+
"yes" | "Yes" | "YES" | "no" | "No" | "NO" | "on" | "On" | "ON" | "off" | "Off" | "OFF" | "y" | "Y" | "n" | "N"
|
|
158
|
+
) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Special floats (YAML 1.1 + 1.2)
|
|
163
|
+
if matches!(
|
|
164
|
+
value,
|
|
165
|
+
".inf" | ".Inf" | ".INF" | "-.inf" | "-.Inf" | "-.INF" | "+.inf" | "+.Inf" | "+.INF" | ".nan" | ".NaN" | ".NAN"
|
|
166
|
+
) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Integer
|
|
171
|
+
if value.parse::<i64>().is_ok() {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Octal (0o...) and hex (0x...)
|
|
176
|
+
if value.starts_with("0x") || value.starts_with("0X") || value.starts_with("0o") || value.starts_with("0O") {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Float
|
|
181
|
+
if value.parse::<f64>().is_ok() {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
false
|
|
186
|
+
}
|