pub mod format_config;
pub mod parser;
use format_config::FormatConfig;
use std::error::Error as StdError;
use std::fmt;
#[derive(Debug)]
pub enum ParseError {
EmptyInput,
SyntaxError(String),
InternalError(String),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::EmptyInput => write!(f, "Empty input"),
ParseError::SyntaxError(msg) => write!(f, "Syntax error: {}", msg),
ParseError::InternalError(msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl StdError for ParseError {}
#[derive(Debug, Clone, PartialEq)]
pub enum LiNo<T> {
Link { id: Option<T>, values: Vec<Self> },
Ref(T),
}
impl<T> LiNo<T> {
pub fn is_ref(&self) -> bool {
matches!(self, LiNo::Ref(_))
}
pub fn is_link(&self) -> bool {
matches!(self, LiNo::Link { .. })
}
}
impl<T: ToString + Clone> LiNo<T> {
pub fn format_with_config(&self, config: &FormatConfig) -> String {
match self {
LiNo::Ref(value) => {
let escaped = escape_reference(&value.to_string());
if config.less_parentheses {
escaped
} else {
format!("({})", escaped)
}
}
LiNo::Link { id, values } => {
if id.is_none() && values.is_empty() {
return if config.less_parentheses {
String::new()
} else {
"()".to_string()
};
}
if values.is_empty() {
if let Some(ref id_val) = id {
let escaped_id = escape_reference(&id_val.to_string());
return if config.less_parentheses && !needs_parentheses(&id_val.to_string())
{
escaped_id
} else {
format!("({})", escaped_id)
};
}
return if config.less_parentheses {
String::new()
} else {
"()".to_string()
};
}
let mut should_indent = false;
if config.should_indent_by_ref_count(values.len()) {
should_indent = true;
} else {
let values_str = values
.iter()
.map(|v| format_value(v))
.collect::<Vec<_>>()
.join(" ");
let test_line = if let Some(ref id_val) = id {
let id_str = escape_reference(&id_val.to_string());
if config.less_parentheses {
format!("{}: {}", id_str, values_str)
} else {
format!("({}: {})", id_str, values_str)
}
} else if config.less_parentheses {
values_str.clone()
} else {
format!("({})", values_str)
};
if config.should_indent_by_length(&test_line) {
should_indent = true;
}
}
if should_indent && !config.prefer_inline {
return self.format_indented(config);
}
let values_str = values
.iter()
.map(|v| format_value(v))
.collect::<Vec<_>>()
.join(" ");
if id.is_none() {
if config.less_parentheses {
let all_simple = values.iter().all(|v| matches!(v, LiNo::Ref(_)));
if all_simple {
return values
.iter()
.map(|v| match v {
LiNo::Ref(r) => escape_reference(&r.to_string()),
_ => format_value(v),
})
.collect::<Vec<_>>()
.join(" ");
}
return values_str;
}
return format!("({})", values_str);
}
let id_str = escape_reference(&id.as_ref().unwrap().to_string());
let with_colon = format!("{}: {}", id_str, values_str);
if config.less_parentheses && !needs_parentheses(&id.as_ref().unwrap().to_string())
{
with_colon
} else {
format!("({})", with_colon)
}
}
}
}
fn format_indented(&self, config: &FormatConfig) -> String {
match self {
LiNo::Ref(value) => {
let escaped = escape_reference(&value.to_string());
format!("({})", escaped)
}
LiNo::Link { id, values } => {
if id.is_none() {
values
.iter()
.map(|v| format!("{}{}", config.indent_string, format_value(v)))
.collect::<Vec<_>>()
.join("\n")
} else {
let id_str = escape_reference(&id.as_ref().unwrap().to_string());
let mut lines = vec![format!("{}:", id_str)];
for v in values {
lines.push(format!("{}{}", config.indent_string, format_value(v)));
}
lines.join("\n")
}
}
}
}
}
impl<T: ToString> fmt::Display for LiNo<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LiNo::Ref(value) => write!(f, "{}", value.to_string()),
LiNo::Link { id, values } => {
let id_str = id
.as_ref()
.map(|id| format!("{}: ", id.to_string()))
.unwrap_or_default();
if f.alternate() {
let lines = values
.iter()
.map(|value| {
match value {
LiNo::Ref(_) => format!("{}({})", id_str, value),
_ => format!("{}{}", id_str, value),
}
})
.collect::<Vec<_>>()
.join("\n");
write!(f, "{}", lines)
} else {
let values_str = values
.iter()
.map(|value| value.to_string())
.collect::<Vec<_>>()
.join(" ");
write!(f, "({}{})", id_str, values_str)
}
}
}
}
}
impl From<parser::Link> for LiNo<String> {
fn from(link: parser::Link) -> Self {
if link.values.is_empty() && link.children.is_empty() {
if let Some(id) = link.id {
LiNo::Ref(id)
} else {
LiNo::Link {
id: None,
values: vec![],
}
}
} else {
let values: Vec<LiNo<String>> = link.values.into_iter().map(|v| v.into()).collect();
LiNo::Link {
id: link.id,
values,
}
}
}
}
fn flatten_links(links: Vec<parser::Link>) -> Vec<LiNo<String>> {
let mut result = vec![];
for link in links {
flatten_link_recursive(&link, None, &mut result);
}
result
}
fn flatten_link_recursive(
link: &parser::Link,
parent: Option<&LiNo<String>>,
result: &mut Vec<LiNo<String>>,
) {
if link.is_indented_id
&& link.id.is_some()
&& link.values.is_empty()
&& !link.children.is_empty()
{
let child_values: Vec<LiNo<String>> = link
.children
.iter()
.map(|child| {
if child.values.len() == 1
&& child.values[0].values.is_empty()
&& child.values[0].children.is_empty()
{
if let Some(ref id) = child.values[0].id {
LiNo::Ref(id.clone())
} else {
parser::Link {
id: child.id.clone(),
values: child.values.clone(),
children: vec![],
is_indented_id: false,
}
.into()
}
} else {
parser::Link {
id: child.id.clone(),
values: child.values.clone(),
children: vec![],
is_indented_id: false,
}
.into()
}
})
.collect();
let current = LiNo::Link {
id: link.id.clone(),
values: child_values,
};
let combined = if let Some(parent) = parent {
let wrapped_parent = match parent {
LiNo::Ref(ref_id) => LiNo::Link {
id: None,
values: vec![LiNo::Ref(ref_id.clone())],
},
link => link.clone(),
};
LiNo::Link {
id: None,
values: vec![wrapped_parent, current],
}
} else {
current
};
result.push(combined);
return; }
let current = if link.values.is_empty() {
if let Some(id) = &link.id {
LiNo::Ref(id.clone())
} else {
LiNo::Link {
id: None,
values: vec![],
}
}
} else {
let values: Vec<LiNo<String>> = link
.values
.iter()
.map(|v| {
parser::Link {
id: v.id.clone(),
values: v.values.clone(),
children: vec![],
is_indented_id: false,
}
.into()
})
.collect();
LiNo::Link {
id: link.id.clone(),
values,
}
};
let combined = if let Some(parent) = parent {
let wrapped_parent = match parent {
LiNo::Ref(ref_id) => LiNo::Link {
id: None,
values: vec![LiNo::Ref(ref_id.clone())],
},
link => link.clone(),
};
let wrapped_current = match ¤t {
LiNo::Ref(ref_id) => LiNo::Link {
id: None,
values: vec![LiNo::Ref(ref_id.clone())],
},
link => link.clone(),
};
LiNo::Link {
id: None,
values: vec![wrapped_parent, wrapped_current],
}
} else {
current.clone()
};
result.push(combined.clone());
for child in &link.children {
flatten_link_recursive(child, Some(&combined), result);
}
}
pub fn parse_lino(document: &str) -> Result<LiNo<String>, ParseError> {
if document.trim().is_empty() {
return Ok(LiNo::Link {
id: None,
values: vec![],
});
}
match parser::parse_document(document) {
Ok((_, links)) => {
if links.is_empty() {
Ok(LiNo::Link {
id: None,
values: vec![],
})
} else {
let flattened = flatten_links(links);
Ok(LiNo::Link {
id: None,
values: flattened,
})
}
}
Err(e) => Err(ParseError::SyntaxError(format!("{:?}", e))),
}
}
pub fn parse_lino_to_links(document: &str) -> Result<Vec<LiNo<String>>, ParseError> {
if document.trim().is_empty() {
return Ok(vec![]);
}
match parser::parse_document(document) {
Ok((_, links)) => {
if links.is_empty() {
Ok(vec![])
} else {
let flattened = flatten_links(links);
Ok(flattened)
}
}
Err(e) => Err(ParseError::SyntaxError(format!("{:?}", e))),
}
}
pub fn format_links(links: &[LiNo<String>]) -> String {
links
.iter()
.map(|link| format!("{}", link))
.collect::<Vec<_>>()
.join("\n")
}
pub fn format_links_with_config(links: &[LiNo<String>], config: &FormatConfig) -> String {
if links.is_empty() {
return String::new();
}
let links_to_format = if config.group_consecutive {
group_consecutive_links(links)
} else {
links.to_vec()
};
links_to_format
.iter()
.map(|link| link.format_with_config(config))
.collect::<Vec<_>>()
.join("\n")
}
fn group_consecutive_links(links: &[LiNo<String>]) -> Vec<LiNo<String>> {
if links.is_empty() {
return vec![];
}
let mut grouped = vec![];
let mut i = 0;
while i < links.len() {
let current = &links[i];
if let LiNo::Link {
id: Some(ref current_id),
values: ref current_values,
} = current
{
if !current_values.is_empty() {
let mut same_id_values = current_values.clone();
let mut j = i + 1;
while j < links.len() {
if let LiNo::Link {
id: Some(ref next_id),
values: ref next_values,
} = &links[j]
{
if next_id == current_id && !next_values.is_empty() {
same_id_values.extend(next_values.clone());
j += 1;
} else {
break;
}
} else {
break;
}
}
if j > i + 1 {
grouped.push(LiNo::Link {
id: Some(current_id.clone()),
values: same_id_values,
});
i = j;
continue;
}
}
}
grouped.push(current.clone());
i += 1;
}
grouped
}
fn escape_reference(reference: &str) -> String {
if reference.is_empty() || reference.trim().is_empty() {
return String::new();
}
let has_single_quote = reference.contains('\'');
let has_double_quote = reference.contains('"');
let needs_quoting = reference.contains(':')
|| reference.contains('(')
|| reference.contains(')')
|| reference.contains(' ')
|| reference.contains('\t')
|| reference.contains('\n')
|| reference.contains('\r')
|| has_double_quote
|| has_single_quote;
if has_single_quote && has_double_quote {
return format!("'{}'", reference.replace('\'', "\\'"));
}
if has_double_quote {
return format!("'{}'", reference);
}
if has_single_quote {
return format!("\"{}\"", reference);
}
if needs_quoting {
return format!("'{}'", reference);
}
reference.to_string()
}
fn needs_parentheses(s: &str) -> bool {
s.contains(' ') || s.contains(':') || s.contains('(') || s.contains(')')
}
fn format_value<T: ToString>(value: &LiNo<T>) -> String {
match value {
LiNo::Ref(r) => escape_reference(&r.to_string()),
LiNo::Link { id, values } => {
if values.is_empty() {
if let Some(ref id_val) = id {
return escape_reference(&id_val.to_string());
}
return String::new();
}
format!("{}", value)
}
}
}