1#![allow(non_snake_case)]
84#![allow(unused_imports)]
85#![allow(dead_code)]
86#![allow(unused_variables)]
87use std::collections::HashMap;
90use decimal_rs::Decimal;
91
92#[cfg(feature = "lang-python")]
93use pyo3::prelude::*;
94
95use winnow::ascii::{alpha1, alphanumeric0, alphanumeric1, multispace0};
96use winnow::combinator::alt; use winnow::combinator::opt; use winnow::combinator::preceded; use winnow::combinator::Recognize;
100use winnow::combinator::WithRecognized;
101use winnow::combinator::{delimited, repeat, separated, separated_pair, *};
102use winnow::error::ErrMode;
103use winnow::error::ErrorKind;
104use winnow::error::ParserError;
105use winnow::prelude::*;
106use winnow::seq;
107use winnow::stream::Stream; use winnow::token::one_of; use winnow::{PResult, Parser};
110use winnow::token::take_while;
111
112use serde_json::json;
113use serde::Serialize;
114
115#[cfg(not(target_arch = "wasm32"))]
116#[cfg(feature = "lang-python")]
117mod python;
118#[cfg(not(target_arch = "wasm32"))]
119#[cfg(feature = "lang-python")]
120pub use python::k0mmand3r;
121
122
123#[cfg(target_arch = "wasm32")]
124mod typescript_wasm;
125
126
127
128fn parse_prefix_dash2x<'s>(input: &mut &'s str) -> PResult<&'s str> {
130 "--".parse_next(input)
131}
132
133fn parse_label<'i>(input: &mut &'i str) -> PResult<&'i str> {
134 let label_parser = seq!((alpha1, alphanumeric0));
136 label_parser.recognize().parse_next(input)
137}
138
139fn parse_value_quoted<'i>(input: &mut &'i str) -> PResult<&'i str> {
140 delimited('"', alphanumeric1, '"').parse_next(input)
142}
143
144fn parse_value_unquoted<'i>(input: &mut &'i str) -> PResult<&'i str> {
145 alphanumeric1.parse_next(input)
147}
148
149fn parse_value_quote_agnostic<'i>(input: &mut &'i str) -> PResult<&'i str> {
150 alt((
152 parse_value_quoted, parse_value_unquoted, ))
155 .parse_next(input)
156}
157
158fn parse_slashcommand<'i>(input: &mut &'i str) -> PResult<&'i str> {
159 preceded("/", parse_label).parse_next(input)
161}
162
163fn parse_KmdParameter<'i>(input: &mut &'i str) -> PResult<(&'i str, &'i str)> {
164 preceded(
167 parse_prefix_dash2x,
168 separated_pair(
169 parse_label,
170 opt(delimited(multispace0, '=', multispace0)), opt(parse_value_quote_agnostic), ),
173 )
174 .map(|(label, value)| (label, value.unwrap_or(""))) .parse_next(input)
176}
177
178fn parse_content<'i>(input: &mut &'i str) -> PResult<&'i str> {
182 let trimmed_input = input.trim();
183
184 if trimmed_input.starts_with('/') {
185 let error = winnow::error::ContextError::new();
186 Err(winnow::error::ErrMode::Backtrack(error))
187 } else {
188 let start_index = input.find(trimmed_input).unwrap_or(0);
190 let end_index = start_index + trimmed_input.len();
192
193 let result = &input[start_index..end_index];
195
196 *input = &input[end_index..];
198
199 Ok(result)
200 }
201}
202
203
204#[derive(Debug, PartialEq, Eq, Serialize)]
205pub struct KmdParams<'i> {
206 kvs: HashMap<&'i str, &'i str>,
207}
208
209impl<'i> KmdParams<'i> {
210 pub fn parse(input: &mut &'i str) -> PResult<Self> {
211 let kvs =
212 separated(0.., parse_KmdParameter, terminated(' ', multispace0)).parse_next(input)?;
213
214 Ok(Self { kvs })
215 }
216}
217
218
219#[derive(Debug, PartialEq, Serialize)]
223pub struct KmdLine<'i> {
224 verb: Option<String>,
226 params: Option<KmdParams<'i>>,
228 content: Option<String>,
230}
231
232impl<'i> KmdLine<'i> {
233 pub fn parse(input: &mut &'i str) -> PResult<Self> {
234 let trimmed_input = input.trim();
235
236 if trimmed_input.starts_with('/') {
237 let verb = Some(parse_slashcommand(input)?.to_string());
239
240 if input.trim().is_empty() {
242 return Ok(KmdLine {
244 verb,
245 params: None,
246 content: None,
247 });
248 }
249
250 let _ = multispace0.parse_next(input)?;
252
253 let params = opt(KmdParams::parse).parse_next(input)?;
255
256 let _ = multispace0.parse_next(input)?;
258
259 let content = opt(parse_content).parse_next(input)?.map(|c| c.to_string());
261
262 Ok(KmdLine {
263 verb,
264 params,
265 content,
266 })
267 } else {
268 let content = Some(parse_content(input)?.to_string());
271 Ok(KmdLine {
272 verb: None,
273 params: None,
274 content,
275 })
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn content_without_leading_slash() {
286 let mut input = "this is just content";
287 let expected = "this is just content";
288 let result = parse_content(&mut input).unwrap();
289 assert_eq!(result, expected);
290 }
291
292 #[test]
293 fn content_with_leading_slash() {
294 let mut input = "/not just content";
295 let result = parse_content(&mut input);
296 assert!(result.is_err()); }
298
299 #[test]
300 fn content_with_whitespace() {
301 let mut input = " leading and trailing spaces ";
302 let expected = "leading and trailing spaces";
303 let result = parse_content(&mut input).unwrap();
304 assert_eq!(result, expected);
305 }
306
307 #[test]
308 fn test_parse_prefix_dash2x() {
309 let input = "--";
310 let actual = parse_prefix_dash2x.parse(input).unwrap();
311 let expected = "--";
312 assert_eq!(actual, expected)
313 }
314
315 #[test]
316 fn test_label() {
317 let input = "mylabel";
318 let actual = parse_label.parse(input).unwrap();
319 let expected = "mylabel";
320 assert_eq!(actual, expected)
321 }
322
323 #[test]
324 fn test_labelWithNumber() {
325 let input = "mylabel1";
326 let actual = parse_label.parse(input).unwrap();
327 let expected = "mylabel1";
328 assert_eq!(actual, expected)
329 }
330
331 #[test]
332 fn test_value_quoted() {
333 let input = r#""40""#;
334 let actual = parse_value_quoted.parse(input).unwrap();
335 let expected = "40";
336 assert_eq!(actual, expected)
337 }
338
339 #[test]
340 fn test_value_unquoted() {
341 let input = r#"40"#;
342 let actual = parse_value_unquoted.parse(input).unwrap();
343 let expected = "40";
344 assert_eq!(actual, expected)
345 }
346
347 #[test]
348 fn test_slashcommand() {
349 let input = r#"/command"#;
350 let actual = parse_slashcommand.parse(input).unwrap();
351 let expected = "command";
352 assert_eq!(actual, expected)
353 }
354
355 #[test]
356 fn test_isolatedLabel() {
357 let input = r#"--mylabel"#;
358 let actual = parse_KmdParameter.parse(input).unwrap();
359 let expected = ("mylabel", "");
360 assert_eq!(actual, expected)
361 }
362
363 #[test]
364 fn test_dash2xlabelvalueQUOTED_for_KmdParameter() {
365 let input = r#"--mylabel="40""#;
366 let actual = parse_KmdParameter.parse(input).unwrap();
367 let expected = ("mylabel", "40");
368 assert_eq!(actual, expected)
369 }
370
371 #[test]
372 fn test_dash2xlabelvalueUNQUOTED_for_KmdParameter() {
373 let input = r#"--mylabel=40"#;
374 let actual = parse_KmdParameter.parse(input).unwrap();
375 let expected = ("mylabel", "40");
376 assert_eq!(actual, expected)
377 }
378
379 #[test]
380 fn test_parametersOne() {
381 let input = r#"--onelabel="10""#;
383 let actual = KmdParams::parse.parse(input).unwrap();
384 let expected = KmdParams {
385 kvs: HashMap::from([("onelabel", "10")]),
386 };
387
388 assert_eq!(actual, expected)
389 }
390
391 #[test]
392 fn test_parametersMany() {
393 let input = r#"--mylabel="10" --yourlabel=20"#;
395 let actual = KmdParams::parse.parse(input).unwrap();
396 let expected = KmdParams {
397 kvs: HashMap::from([("mylabel", "10"), ("yourlabel", "20")]),
398 };
399
400 assert_eq!(actual, expected)
401 }
402
403 #[test]
404 fn test_kommand_WithContent() {
405 let mut input = r#"/save --mylabel=myvalue remaining content"#;
406 let actual = KmdLine::parse(&mut input).unwrap();
407 let expected = KmdLine {
408 verb: Some("save".to_string()),
409 params: Some(KmdParams {
410 kvs: HashMap::from([("mylabel", "myvalue")]),
411 }),
412 content: Some("remaining content".to_string()),
413 };
414 assert_eq!(actual, expected);
415 }
416
417 #[test]
418 fn test_kommand_ContentOnly() {
419 let mut input =
420 r#"lots of content with\nhard returns\nand embedded verbs and --parameters like /save"#;
421 let expected = KmdLine {
422 verb: None,
423 params: None,
424 content: Some(input.to_string()),
425 };
426
427 let actual = KmdLine::parse(&mut input).unwrap();
428 assert_eq!(actual, expected);
429 }
430
431
432 #[test]
433 fn test_kommand_VerbOnly() {
434 let mut input =
435 r#"/verbonly"#;
436 let expected = KmdLine {
437 verb: Some("verbonly".to_string()),
438 params: None,
439 content: None
440 };
441
442 let actual = KmdLine::parse(&mut input).unwrap();
443 assert_eq!(actual, expected);
444 }
445
446}
447
448
449