Skip to content

Commit

Permalink
Removes regex dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
cuducos committed Aug 31, 2024
1 parent 8cfa6ce commit a87d85d
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 77 deletions.
45 changes: 0 additions & 45 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ edition = "2021"
anyhow = "1.0.86"
clap = "4.5.16"
rand = "0.8.5"
regex = "1.10.6"
116 changes: 85 additions & 31 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ use std::{

use anyhow::Result;
use rand::{thread_rng, Rng};
use regex::Regex;

use crate::model::{AutoGeneratedVariable, Block, Comment, SimpleVariable, VariableType};

const NAME_PATTERN: &str = r"^[A-Z0-9_]+$";
const RANDOM_VARIABLE_PATTERN: &str = r"\<random(:(?P<size>\d*))?\>";
const AUTO_GENERATED_PATTERN: &str = r"\{[A-Z0-9_]+\}";

const FIRST_CHAR: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const NAME_CHARS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
const RANDOM_VARIABLE_PREFIX: &str = "<random";
const HELP_TITLE: &str = "This is the first line of a block. A block is a \
group of lines separated from others by one (or more) empty line(s). \
The first line of a block is expected to be a title, that is to say, to \
Expand All @@ -32,6 +30,48 @@ const HELP_VARIABLE: &str = "This line was expected to be a variable line. The \
followed by an equal sign. No spaces before the equal sign. This line does \
not match this expected pattern.";

fn is_valid_name(name: &str) -> bool {
match name.chars().next() {
Some(c) => {
if !FIRST_CHAR.contains(c) {
return false;
}
}
None => return false,
}
for c in name.chars() {
if !NAME_CHARS.contains(c) {
return false;
}
}
true
}

fn is_auto_generated_variable(value: &str) -> bool {
if let Some(first) = value.find('{') {
if let Some(second) = value[first + 1..].find('}') {
let name = &value[first + 1..first + second];
return is_valid_name(name);
}
}
false
}

fn is_random_variable(value: &str) -> (bool, Option<usize>) {
if let Some(rest) = value.strip_prefix(RANDOM_VARIABLE_PREFIX) {
if let Some(number) = rest.strip_suffix('>') {
if number.is_empty() {
return (true, None);
} else if !number.starts_with(':') {
return (false, None);
} else if let Ok(n) = number[1..].parse() {
return (true, Some(n));
}
}
}
(false, None)
}

enum Expecting {
Title,
DescriptionOrVariables,
Expand All @@ -54,9 +94,6 @@ pub struct Parser {
path: String,
random_chars: String,
use_default: bool,
name_pattern: Regex,
random_pattern: Regex,
auto_generated_pattern: Regex,
state: Expecting,
buffer: Option<Block>,
pub blocks: Vec<Block>,
Expand All @@ -68,9 +105,6 @@ impl Parser {
path: path.to_string(),
random_chars: random_chars.to_string(),
use_default: *use_default,
name_pattern: Regex::new(NAME_PATTERN)?,
random_pattern: Regex::new(RANDOM_VARIABLE_PATTERN)?,
auto_generated_pattern: Regex::new(AUTO_GENERATED_PATTERN)?,
state: Expecting::Title,
buffer: None,
blocks: vec![],
Expand All @@ -82,38 +116,31 @@ impl Parser {
name: &str,
description: Option<&str>,
value: &str,
) -> Result<SimpleVariable> {
if let Some(matches) = self.random_pattern.captures(value) {
) -> Option<SimpleVariable> {
let (is_random, size) = is_random_variable(value);
if is_random {
let mut rng = thread_rng();
let length = matches
.name("size")
.map(|m| m.as_str().parse::<usize>())
.transpose()?
.unwrap_or(rng.gen_range(64..=128));
let length = size.unwrap_or(rng.gen_range(64..=128));
let max_chars_idx = self.random_chars.chars().count();
let mut value: String = String::from("");
for _ in 0..length {
let pos = rng.gen_range(0..max_chars_idx);
value.push(self.random_chars.chars().nth(pos).unwrap())
}
Ok(SimpleVariable::new(name, Some(value.as_str()), description))
} else {
Err(anyhow::anyhow!("Invalid random variable: {}", value))
return Some(SimpleVariable::new(name, Some(value.as_str()), description));
}
None
}

fn parse_auto_generated_variable(
&self,
name: &str,
value: &str,
) -> Result<AutoGeneratedVariable> {
if self.auto_generated_pattern.find(value).is_some() {
return Ok(AutoGeneratedVariable::new(name, value));
) -> Option<AutoGeneratedVariable> {
if is_auto_generated_variable(value) {
return Some(AutoGeneratedVariable::new(name, value));
}
Err(anyhow::anyhow!(
"Invalid auto-generated variable: {}",
value
))
None
}

fn parse_variable(&self, pos: usize, line: &str) -> Result<VariableType> {
Expand All @@ -123,7 +150,7 @@ impl Parser {
line,
HELP_VARIABLE
))?;
if !self.name_pattern.is_match(name) {
if !is_valid_name(name) {
return Err(anyhow::anyhow!(
"Invalid variable name on line {}: {}\nHint :{}",
pos,
Expand All @@ -139,10 +166,10 @@ impl Parser {
if val.is_empty() {
default = None;
} else {
if let Ok(v) = self.parse_random_variable(name, description, val) {
if let Some(v) = self.parse_random_variable(name, description, val) {
return Ok(VariableType::Input(v));
}
if let Ok(v) = self.parse_auto_generated_variable(name, val) {
if let Some(v) = self.parse_auto_generated_variable(name, val) {
return Ok(VariableType::AutoGenerated(v));
}
}
Expand Down Expand Up @@ -250,6 +277,33 @@ mod tests {
use super::*;
use crate::DEFAULT_RANDOM_CHARS;

#[test]
fn test_is_valid_name() {
assert!(is_valid_name("HELLO_WORLD"));
assert!(is_valid_name("HELLO_WORLD_42"));
assert!(!is_valid_name("42HELLO"));
assert!(!is_valid_name("Hello World"));
assert!(!is_valid_name("HELLO-WORLD"));
}

#[test]
fn test_is_auto_generated_variable() {
assert!(!is_auto_generated_variable("42"));
assert!(!is_auto_generated_variable("Hello, world!"));
assert!(!is_auto_generated_variable("Hello, {world}!"));
assert!(is_auto_generated_variable("Hello, {WORLD}!"));
}

#[test]
fn test_is_random_variable() {
assert!(!is_random_variable("random:42").0);
assert_eq!(is_random_variable("random:42").1, None);
assert!(is_random_variable("<random:42>").0);
assert_eq!(is_random_variable("<random:42>").1, Some(42));
assert!(is_random_variable("<random>").0);
assert_eq!(is_random_variable("<random>").1, None);
}

#[test]
fn test_parser() {
let sample = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
Expand Down

0 comments on commit a87d85d

Please sign in to comment.