use super::Value;
use super::Variable;
use crate::source::{Location, Source};
use either::{Left, Right};
use std::borrow::Cow;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Quirk {
LineNumber,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Expansion<'a> {
Unset,
Scalar(Cow<'a, str>),
Array(Cow<'a, [String]>),
}
impl Default for Expansion<'_> {
fn default() -> Self {
Self::Unset
}
}
impl From<String> for Expansion<'static> {
fn from(value: String) -> Self {
Expansion::Scalar(Cow::Owned(value))
}
}
impl<'a> From<&'a str> for Expansion<'a> {
fn from(value: &'a str) -> Self {
Expansion::Scalar(Cow::Borrowed(value))
}
}
impl<'a> From<&'a String> for Expansion<'a> {
fn from(value: &'a String) -> Self {
Expansion::Scalar(Cow::Borrowed(value))
}
}
impl From<Option<String>> for Expansion<'static> {
fn from(value: Option<String>) -> Self {
match value {
Some(value) => value.into(),
None => Expansion::Unset,
}
}
}
impl From<Vec<String>> for Expansion<'static> {
fn from(values: Vec<String>) -> Self {
Expansion::Array(Cow::Owned(values))
}
}
impl<'a> From<&'a [String]> for Expansion<'a> {
fn from(values: &'a [String]) -> Self {
Expansion::Array(Cow::Borrowed(values))
}
}
impl<'a> From<&'a Vec<String>> for Expansion<'a> {
fn from(values: &'a Vec<String>) -> Self {
Expansion::Array(Cow::Borrowed(values))
}
}
impl From<Value> for Expansion<'static> {
fn from(value: Value) -> Self {
match value {
Value::Scalar(value) => Expansion::from(value),
Value::Array(values) => Expansion::from(values),
}
}
}
impl<'a> From<&'a Value> for Expansion<'a> {
fn from(value: &'a Value) -> Self {
match value {
Value::Scalar(value) => Expansion::from(value),
Value::Array(values) => Expansion::from(values),
}
}
}
impl From<Option<Value>> for Expansion<'static> {
fn from(value: Option<Value>) -> Self {
match value {
Some(value) => value.into(),
None => Expansion::Unset,
}
}
}
impl<'a, V> From<Option<&'a V>> for Expansion<'a>
where
Expansion<'a>: From<&'a V>,
{
fn from(value: Option<&'a V>) -> Self {
match value {
Some(value) => value.into(),
None => Expansion::Unset,
}
}
}
impl From<Expansion<'_>> for Option<Value> {
fn from(expansion: Expansion<'_>) -> Option<Value> {
match expansion {
Expansion::Unset => None,
Expansion::Scalar(value) => Some(Value::Scalar(value.into_owned())),
Expansion::Array(values) => Some(Value::Array(values.into_owned())),
}
}
}
impl<'a> From<&'a Expansion<'a>> for Expansion<'a> {
fn from(expansion: &'a Expansion<'a>) -> Expansion<'a> {
match expansion {
Expansion::Unset => Expansion::Unset,
Expansion::Scalar(value) => value.as_ref().into(),
Expansion::Array(values) => values.as_ref().into(),
}
}
}
impl Expansion<'_> {
#[must_use]
pub fn into_owned(self) -> Option<Value> {
self.into()
}
#[must_use]
pub fn as_ref(&self) -> Expansion<'_> {
self.into()
}
#[must_use]
pub fn len(&self) -> usize {
match self {
Expansion::Unset => 0,
Expansion::Scalar(value) => value.len(),
Expansion::Array(values) => values.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn split(&self) -> impl Iterator<Item = &str> {
match self {
Self::Unset => Right([].iter().map(String::as_str)),
Self::Scalar(value) => Left(value.split(':')),
Self::Array(values) => Right(values.iter().map(String::as_str)),
}
}
}
pub fn expand<'a>(var: &'a Variable, mut location: &Location) -> Expansion<'a> {
match &var.quirk {
None => var.value.as_ref().into(),
Some(Quirk::LineNumber) => {
while let Source::Alias { original, .. } = &*location.code.source {
location = original;
}
let line_number = location.code.line_number(location.range.start);
line_number.to_string().into()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::alias::Alias;
use crate::source::Code;
use std::num::NonZeroU64;
use std::rc::Rc;
#[test]
fn expand_no_quirk() {
let var = Variable::new("foo");
let loc = Location::dummy("somewhere");
let result = var.expand(&loc);
assert_eq!(result, Expansion::Scalar("foo".into()));
}
fn stub_code() -> Rc<Code> {
Code {
value: "foo\nbar\nbaz\n".to_string().into(),
start_line_number: NonZeroU64::new(42).unwrap(),
source: Source::Unknown.into(),
}
.into()
}
#[test]
fn expand_line_number_of_first_line() {
let var = Variable {
quirk: Some(Quirk::LineNumber),
..Default::default()
};
let code = stub_code();
let range = 1..3;
let loc = Location { code, range };
let result = var.expand(&loc);
assert_eq!(result, Expansion::Scalar("42".into()));
}
#[test]
fn expand_line_number_of_third_line() {
let var = Variable {
quirk: Some(Quirk::LineNumber),
..Default::default()
};
let code = stub_code();
let range = 8..12;
let loc = Location { code, range };
let result = var.expand(&loc);
assert_eq!(result, Expansion::Scalar("44".into()));
}
#[test]
fn expand_line_number_in_alias() {
fn to_alias(original: Location) -> Location {
let alias = Alias {
name: "name".to_string(),
replacement: "replacement".to_string(),
global: false,
origin: Location::dummy("alias"),
}
.into();
let code = Code {
value: " \n \n ".to_string().into(),
start_line_number: NonZeroU64::new(15).unwrap(),
source: Source::Alias { original, alias }.into(),
}
.into();
let range = 0..1;
Location { code, range }
}
let var = Variable {
quirk: Some(Quirk::LineNumber),
..Default::default()
};
let code = stub_code();
let range = 8..12;
let loc = to_alias(to_alias(Location { code, range }));
let result = var.expand(&loc);
assert_eq!(result, Expansion::Scalar("44".into()));
}
}