#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GrammarError {
Inline { file: &'static str, key: String },
NoEntry { file: &'static str, entry: String },
NoField {
file: &'static str,
entry: String,
field: &'static str,
},
}
impl std::fmt::Display for GrammarError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Inline { file, key } => write!(
f,
"{file}: {key}: carries an inline value; yog edits only the \
block form litany writes — use the raw editor"
),
Self::NoEntry { file, entry } => write!(
f,
"{file}: no ` {entry}:` block entry; yog edits only the block \
form litany writes — use the raw editor"
),
Self::NoField { file, entry, field } => write!(
f,
"{file}: ` {entry}:` declares no ` {field}:` line; yog edits \
only the block form litany writes — use the raw editor"
),
}
}
}
mod entries;
mod fields;
mod roles;
mod rows;
mod tools;
pub use entries::{entry_names, remove_entry, set_entry};
pub use fields::{entry_field, remove_field, set_field, upsert_field};
pub use roles::{EFFORT, MODEL, PRIORITY, PROVIDER, TOOLS, roles, set_role_model};
pub use rows::is_unknown_row;
pub use tools::{flow_members, flow_value};
pub const PROVIDERS_YAML: &str = "providers.yaml";
pub const ROLES: &str = "roles";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoleModel {
pub role: String,
pub provider: String,
pub model: String,
pub effort: Option<String>,
pub priority: bool,
}
pub(super) enum BlockKey {
At(usize),
Inline,
Absent,
}
pub(super) fn block_key(lines: &[&str], key: &str) -> BlockKey {
for (i, line) in lines.iter().enumerate() {
let Some(rest) = line.strip_prefix(key) else {
continue;
};
let Some(rest) = rest.strip_prefix(':') else {
continue;
};
if rest.trim().is_empty() {
return BlockKey::At(i);
}
return BlockKey::Inline;
}
BlockKey::Absent
}
fn ends_block(line: &str) -> bool {
let trimmed = line.trim_start();
!trimmed.is_empty() && !trimmed.starts_with('#') && !line.starts_with(' ')
}
pub(super) fn entries(lines: &[&str], at: usize) -> Vec<(String, usize)> {
let mut out = Vec::new();
for (offset, line) in lines.iter().enumerate().skip(at + 1) {
if ends_block(line) {
break;
}
if let Some(rest) = line.strip_prefix(" ")
&& !rest.starts_with(' ')
&& let Some(name) = rest.strip_suffix(':')
{
out.push((name.to_string(), offset));
}
}
out
}
pub(super) fn block_end(lines: &[&str], at: usize) -> usize {
lines
.iter()
.enumerate()
.skip(at + 1)
.find(|(_, line)| ends_block(line))
.map_or(lines.len(), |(i, _)| i)
}
pub(super) fn field(lines: &[&str], at: usize, name: &str) -> Option<(String, usize)> {
for (offset, line) in lines.iter().enumerate().skip(at + 1) {
if ends_block(line) {
return None;
}
let Some(rest) = line.strip_prefix(" ") else {
if line.trim().is_empty() || line.trim_start().starts_with('#') {
continue;
}
return None;
};
if let Some(value) = rest.strip_prefix(name).and_then(|r| r.strip_prefix(':')) {
return Some((value.trim().to_string(), offset));
}
}
None
}
pub(super) fn join(lines: &[String]) -> String {
let mut out = lines.join("\n");
out.push('\n');
out
}