Refactor config structure and improve importer logic
This commit is contained in:
parent
2cb8da91a5
commit
3974d90bc9
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
|
||||
mod yaml;
|
||||
mod path;
|
||||
mod macro_util;
|
||||
mod util;
|
||||
|
||||
pub struct Config {
|
||||
pub label: Option<String>,
|
||||
|
|
|
@ -47,21 +47,9 @@ pub fn calculate_paths<'a>(base_dir: &Path, glob_patterns: impl Iterator<Item =
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::{fs::create_dir_all, path::Path};
|
||||
|
||||
use super::*;
|
||||
use tempdir::TempDir;
|
||||
|
||||
pub fn use_test_directory(callback: impl FnOnce(&Path, &Path, &Path)) {
|
||||
let dir = TempDir::new("tempconfig").unwrap();
|
||||
let match_dir = dir.path().join("match");
|
||||
create_dir_all(&match_dir).unwrap();
|
||||
|
||||
let config_dir = dir.path().join("config");
|
||||
create_dir_all(&config_dir).unwrap();
|
||||
|
||||
callback(&dir.path(), &match_dir, &config_dir);
|
||||
}
|
||||
use crate::util::tests::use_test_directory;
|
||||
use std::{fs::create_dir_all};
|
||||
|
||||
#[test]
|
||||
fn calculate_paths_relative_paths() {
|
||||
|
|
|
@ -150,10 +150,9 @@ impl YAMLConfig {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::config::path::tests::use_test_directory;
|
||||
use crate::util::tests::use_test_directory;
|
||||
use std::iter::FromIterator;
|
||||
use std::{convert::TryInto, fs::create_dir_all};
|
||||
use std::fs::create_dir_all;
|
||||
|
||||
#[test]
|
||||
fn aggregate_includes_empty_config() {
|
||||
|
|
130
espanso-config/src/matches/group/loader/mod.rs
Normal file
130
espanso-config/src/matches/group/loader/mod.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use thiserror::Error;
|
||||
|
||||
use self::yaml::YAMLImporter;
|
||||
|
||||
use super::MatchGroup;
|
||||
|
||||
mod yaml;
|
||||
|
||||
trait Importer {
|
||||
fn is_supported(&self, extension: &str) -> bool;
|
||||
fn load_group(&self, path: &Path) -> Result<MatchGroup>;
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref IMPORTERS: Vec<Box<dyn Importer + Sync + Send>> = vec![
|
||||
Box::new(YAMLImporter::new()),
|
||||
];
|
||||
}
|
||||
|
||||
pub(crate) fn load_match_group(path: &Path) -> Result<MatchGroup> {
|
||||
if let Some(extension) = path.extension() {
|
||||
let extension = extension.to_string_lossy().to_lowercase();
|
||||
|
||||
let importer = IMPORTERS
|
||||
.iter()
|
||||
.find(|importer| importer.is_supported(&extension));
|
||||
|
||||
match importer {
|
||||
Some(importer) => match importer.load_group(path) {
|
||||
Ok(group) => Ok(group),
|
||||
Err(err) => Err(LoadError::ParsingError(err).into()),
|
||||
},
|
||||
None => Err(LoadError::InvalidFormat().into()),
|
||||
}
|
||||
} else {
|
||||
Err(LoadError::MissingExtension().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LoadError {
|
||||
#[error("missing extension in match group file")]
|
||||
MissingExtension(),
|
||||
|
||||
#[error("invalid match group format")]
|
||||
InvalidFormat(),
|
||||
|
||||
#[error("parser reported an error: `{0}`")]
|
||||
ParsingError(anyhow::Error),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::util::tests::use_test_directory;
|
||||
|
||||
#[test]
|
||||
fn load_group_invalid_format() {
|
||||
use_test_directory(|_, match_dir, _| {
|
||||
let file = match_dir.join("base.invalid");
|
||||
std::fs::write(&file, "test").unwrap();
|
||||
|
||||
assert!(matches!(load_match_group(&file).unwrap_err().downcast::<LoadError>().unwrap(), LoadError::InvalidFormat()));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_group_missing_extension() {
|
||||
use_test_directory(|_, match_dir, _| {
|
||||
let file = match_dir.join("base");
|
||||
std::fs::write(&file, "test").unwrap();
|
||||
|
||||
assert!(matches!(load_match_group(&file).unwrap_err().downcast::<LoadError>().unwrap(), LoadError::MissingExtension()));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_group_parsing_error() {
|
||||
use_test_directory(|_, match_dir, _| {
|
||||
let file = match_dir.join("base.yml");
|
||||
std::fs::write(&file, "test").unwrap();
|
||||
|
||||
assert!(matches!(load_match_group(&file).unwrap_err().downcast::<LoadError>().unwrap(), LoadError::ParsingError(_)));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_group_yaml_format() {
|
||||
use_test_directory(|_, match_dir, _| {
|
||||
let file = match_dir.join("base.yml");
|
||||
std::fs::write(&file, r#"
|
||||
matches:
|
||||
- trigger: "hello"
|
||||
replace: "world"
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(load_match_group(&file).unwrap().matches.len(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_group_yaml_format_2() {
|
||||
use_test_directory(|_, match_dir, _| {
|
||||
let file = match_dir.join("base.yaml");
|
||||
std::fs::write(&file, r#"
|
||||
matches:
|
||||
- trigger: "hello"
|
||||
replace: "world"
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(load_match_group(&file).unwrap().matches.len(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_group_yaml_format_casing() {
|
||||
use_test_directory(|_, match_dir, _| {
|
||||
let file = match_dir.join("base.YML");
|
||||
std::fs::write(&file, r#"
|
||||
matches:
|
||||
- trigger: "hello"
|
||||
replace: "world"
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(load_match_group(&file).unwrap().matches.len(), 1);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,52 +1,39 @@
|
|||
use std::{collections::HashMap, convert::{TryFrom, TryInto}, path::Path};
|
||||
|
||||
use crate::matches::{Match, Variable, group::{MatchGroup, path::resolve_imports}};
|
||||
use log::warn;
|
||||
use parse::YAMLMatchGroup;
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use thiserror::Error;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use crate::util::is_yaml_empty;
|
||||
use self::parse::{YAMLMatch, YAMLVariable};
|
||||
use crate::matches::{MatchCause, MatchEffect, TextEffect, TriggerCause};
|
||||
|
||||
use crate::matches::{Match, MatchCause, MatchEffect, TextEffect, TriggerCause, Variable};
|
||||
use super::{MatchGroup};
|
||||
use super::Importer;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct YAMLMatchGroup {
|
||||
#[serde(default)]
|
||||
pub imports: Option<Vec<String>>,
|
||||
mod parse;
|
||||
|
||||
#[serde(default)]
|
||||
pub global_vars: Option<Vec<YAMLVariable>>,
|
||||
pub(crate) struct YAMLImporter {}
|
||||
|
||||
#[serde(default)]
|
||||
pub matches: Option<Vec<YAMLMatch>>,
|
||||
impl YAMLImporter {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl YAMLMatchGroup {
|
||||
pub fn parse_from_str(yaml: &str) -> Result<Self> {
|
||||
// Because an empty string is not valid YAML but we want to support it anyway
|
||||
if is_yaml_empty(yaml) {
|
||||
return Ok(serde_yaml::from_str(
|
||||
"arbitrary_field_that_will_not_block_the_parser: true",
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(serde_yaml::from_str(yaml)?)
|
||||
impl Importer for YAMLImporter {
|
||||
fn is_supported(&self, extension: &str) -> bool {
|
||||
extension == "yaml" || extension == "yml"
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
pub fn parse_from_file(path: &Path) -> Result<Self> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
Self::parse_from_str(&content)
|
||||
}
|
||||
}
|
||||
// TODO: test resolve imports
|
||||
// TODO: test cyclical dependency
|
||||
fn load_group(
|
||||
&self,
|
||||
path: &std::path::Path,
|
||||
) -> anyhow::Result<crate::matches::group::MatchGroup> {
|
||||
let yaml_group = YAMLMatchGroup::parse_from_file(path)?;
|
||||
|
||||
impl TryFrom<YAMLMatchGroup> for MatchGroup {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// TODO: test
|
||||
fn try_from(yaml_match_group: YAMLMatchGroup) -> Result<Self, Self::Error> {
|
||||
let global_vars: Result<Vec<Variable>> = yaml_match_group
|
||||
let global_vars: Result<Vec<Variable>> = yaml_group
|
||||
.global_vars
|
||||
.as_ref()
|
||||
.cloned()
|
||||
|
@ -55,7 +42,7 @@ impl TryFrom<YAMLMatchGroup> for MatchGroup {
|
|||
.map(|var| var.clone().try_into())
|
||||
.collect();
|
||||
|
||||
let matches: Result<Vec<Match>> = yaml_match_group
|
||||
let matches: Result<Vec<Match>> = yaml_group
|
||||
.matches
|
||||
.as_ref()
|
||||
.cloned()
|
||||
|
@ -64,78 +51,17 @@ impl TryFrom<YAMLMatchGroup> for MatchGroup {
|
|||
.map(|m| m.clone().try_into())
|
||||
.collect();
|
||||
|
||||
// Resolve imports
|
||||
let resolved_imports = resolve_imports(path, &yaml_group.imports.unwrap_or_default())?;
|
||||
|
||||
Ok(MatchGroup {
|
||||
imports: yaml_match_group.imports.unwrap_or_default(),
|
||||
imports: resolved_imports,
|
||||
global_vars: global_vars?,
|
||||
matches: matches?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct YAMLMatch {
|
||||
#[serde(default)]
|
||||
pub trigger: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub triggers: Option<Vec<String>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub replace: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub image_path: Option<String>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub form: Option<String>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub form_fields: Option<HashMap<String, Value>>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub vars: Option<Vec<YAMLVariable>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub word: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub left_word: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub right_word: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub propagate_case: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub force_clipboard: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub markdown: Option<String>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub paragraph: Option<bool>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub html: Option<String>, // TODO: map
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct YAMLVariable {
|
||||
pub name: String,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub var_type: String,
|
||||
|
||||
#[serde(default = "default_params")]
|
||||
pub params: Mapping,
|
||||
}
|
||||
|
||||
fn default_params() -> Mapping {
|
||||
Mapping::new()
|
||||
}
|
||||
|
||||
impl TryFrom<YAMLMatch> for Match {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
|
@ -183,7 +109,9 @@ impl TryFrom<YAMLMatch> for Match {
|
|||
MatchEffect::None
|
||||
};
|
||||
|
||||
// TODO: log none match effect
|
||||
if let MatchEffect::None = effect {
|
||||
warn!("match caused by {:?} does not produce any effect. Did you forget the 'replace' field?", cause);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
cause,
|
||||
|
@ -208,15 +136,10 @@ impl TryFrom<YAMLVariable> for Variable {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum YAMLConversionError {
|
||||
// TODO
|
||||
//#[error("unknown data store error")]
|
||||
//Unknown,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_yaml::{Mapping, Value};
|
||||
|
||||
use super::*;
|
||||
use crate::matches::Match;
|
||||
|
101
espanso-config/src/matches/group/loader/yaml/parse.rs
Normal file
101
espanso-config/src/matches/group/loader/yaml/parse.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
|
||||
use crate::util::is_yaml_empty;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct YAMLMatchGroup {
|
||||
#[serde(default)]
|
||||
pub imports: Option<Vec<String>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub global_vars: Option<Vec<YAMLVariable>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub matches: Option<Vec<YAMLMatch>>,
|
||||
}
|
||||
|
||||
impl YAMLMatchGroup {
|
||||
pub fn parse_from_str(yaml: &str) -> Result<Self> {
|
||||
// Because an empty string is not valid YAML but we want to support it anyway
|
||||
if is_yaml_empty(yaml) {
|
||||
return Ok(serde_yaml::from_str(
|
||||
"arbitrary_field_that_will_not_block_the_parser: true",
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(serde_yaml::from_str(yaml)?)
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
pub fn parse_from_file(path: &Path) -> Result<Self> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
Self::parse_from_str(&content)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct YAMLMatch {
|
||||
#[serde(default)]
|
||||
pub trigger: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub triggers: Option<Vec<String>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub replace: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub image_path: Option<String>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub form: Option<String>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub form_fields: Option<HashMap<String, Value>>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub vars: Option<Vec<YAMLVariable>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub word: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub left_word: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub right_word: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub propagate_case: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub force_clipboard: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub markdown: Option<String>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub paragraph: Option<bool>, // TODO: map
|
||||
|
||||
#[serde(default)]
|
||||
pub html: Option<String>, // TODO: map
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct YAMLVariable {
|
||||
pub name: String,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub var_type: String,
|
||||
|
||||
#[serde(default = "default_params")]
|
||||
pub params: Mapping,
|
||||
}
|
||||
|
||||
fn default_params() -> Mapping {
|
||||
Mapping::new()
|
||||
}
|
|
@ -1,22 +1,18 @@
|
|||
use anyhow::Result;
|
||||
use log::error;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
convert::TryInto,
|
||||
path::{Path, PathBuf},
|
||||
path::{Path},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{Match, Variable};
|
||||
|
||||
mod yaml;
|
||||
mod loader;
|
||||
mod path;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct MatchGroup {
|
||||
imports: Vec<String>,
|
||||
pub imports: Vec<String>,
|
||||
pub global_vars: Vec<Variable>,
|
||||
pub matches: Vec<Match>,
|
||||
|
||||
pub resolved_imports: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for MatchGroup {
|
||||
|
@ -25,7 +21,6 @@ impl Default for MatchGroup {
|
|||
imports: Vec::new(),
|
||||
global_vars: Vec::new(),
|
||||
matches: Vec::new(),
|
||||
resolved_imports: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,101 +28,6 @@ impl Default for MatchGroup {
|
|||
impl MatchGroup {
|
||||
// TODO: test
|
||||
pub fn load(group_path: &Path) -> Result<Self> {
|
||||
if let Some(extension) = group_path.extension() {
|
||||
let extension = extension.to_string_lossy().to_lowercase();
|
||||
|
||||
if extension == "yml" || extension == "yaml" {
|
||||
match yaml::YAMLMatchGroup::parse_from_file(group_path) {
|
||||
Ok(yaml_group) => {
|
||||
let match_group: Result<MatchGroup, _> = yaml_group.try_into();
|
||||
match match_group {
|
||||
Ok(mut group) => {
|
||||
group.resolve_imports(group_path)?;
|
||||
Ok(group)
|
||||
}
|
||||
Err(err) => Err(MatchGroupError::ParsingError(err).into()),
|
||||
loader::load_match_group(group_path)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(MatchGroupError::ParsingError(err).into()),
|
||||
}
|
||||
} else {
|
||||
Err(MatchGroupError::InvalidFormat().into())
|
||||
}
|
||||
} else {
|
||||
Err(MatchGroupError::MissingExtension().into())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
fn resolve_imports(&mut self, group_path: &Path) -> Result<()> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
if !group_path.exists() {
|
||||
return Err(
|
||||
MatchGroupError::ResolveImportFailed(format!(
|
||||
"unable to resolve imports for match group at path: {:?}",
|
||||
group_path
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Get the containing directory
|
||||
let current_dir = if group_path.is_file() {
|
||||
if let Some(parent) = group_path.parent() {
|
||||
parent
|
||||
} else {
|
||||
return Err(
|
||||
MatchGroupError::ResolveImportFailed(format!(
|
||||
"unable to resolve imports for match group starting from current path: {:?}",
|
||||
group_path
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
group_path
|
||||
};
|
||||
|
||||
for import in self.imports.iter() {
|
||||
let import_path = PathBuf::from(import);
|
||||
|
||||
// Absolute or relative import
|
||||
let full_path = if import_path.is_relative() {
|
||||
current_dir.join(import_path)
|
||||
} else {
|
||||
import_path
|
||||
};
|
||||
|
||||
if full_path.exists() && full_path.is_file() {
|
||||
paths.push(full_path)
|
||||
} else {
|
||||
// Best effort imports
|
||||
error!("unable to resolve import at path: {:?}", full_path);
|
||||
}
|
||||
}
|
||||
|
||||
let string_paths = paths
|
||||
.into_iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect();
|
||||
self.resolved_imports = string_paths;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MatchGroupError {
|
||||
#[error("missing extension in match group file")]
|
||||
MissingExtension(),
|
||||
|
||||
#[error("invalid match group format")]
|
||||
InvalidFormat(),
|
||||
|
||||
#[error("parser reported an error: `{0}`")]
|
||||
ParsingError(anyhow::Error),
|
||||
|
||||
#[error("resolve import failed: `{0}`")]
|
||||
ResolveImportFailed(String),
|
||||
}
|
||||
|
|
57
espanso-config/src/matches/group/path.rs
Normal file
57
espanso-config/src/matches/group/path.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
use thiserror::Error;
|
||||
|
||||
// TODO: test
|
||||
pub fn resolve_imports(group_path: &Path, imports: &[String]) -> Result<Vec<String>> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
// Get the containing directory
|
||||
let current_dir = if group_path.is_file() {
|
||||
if let Some(parent) = group_path.parent() {
|
||||
parent
|
||||
} else {
|
||||
return Err(
|
||||
ResolveImportError::Failed(format!(
|
||||
"unable to resolve imports for match group starting from current path: {:?}",
|
||||
group_path
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
group_path
|
||||
};
|
||||
|
||||
for import in imports.iter() {
|
||||
let import_path = PathBuf::from(import);
|
||||
|
||||
// Absolute or relative import
|
||||
let full_path = if import_path.is_relative() {
|
||||
current_dir.join(import_path)
|
||||
} else {
|
||||
import_path
|
||||
};
|
||||
|
||||
if full_path.exists() && full_path.is_file() {
|
||||
paths.push(full_path)
|
||||
} else {
|
||||
// Best effort imports
|
||||
error!("unable to resolve import at path: {:?}", full_path);
|
||||
}
|
||||
}
|
||||
|
||||
let string_paths = paths
|
||||
.into_iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
Ok(string_paths)
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ResolveImportError {
|
||||
#[error("resolve import failed: `{0}`")]
|
||||
Failed(String),
|
||||
}
|
|
@ -5,7 +5,7 @@ use crate::counter::{next_id, StructId};
|
|||
mod group;
|
||||
mod store;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Match {
|
||||
cause: MatchCause,
|
||||
effect: MatchEffect,
|
||||
|
@ -28,6 +28,12 @@ impl Default for Match {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Match {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cause == other.cause && self.effect == other.effect && self.label == other.label
|
||||
}
|
||||
}
|
||||
|
||||
// Causes
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -84,7 +90,7 @@ impl Default for TextEffect {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Variable {
|
||||
pub name: String,
|
||||
pub var_type: String,
|
||||
|
@ -104,3 +110,9 @@ impl Default for Variable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Variable {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name && self.var_type == other.var_type && self.params == other.params
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,11 @@ use std::{
|
|||
path::PathBuf,
|
||||
};
|
||||
|
||||
use super::MatchStore;
|
||||
use crate::{counter::StructId, matches::{group::MatchGroup, Match, Variable}};
|
||||
use super::{MatchSet, MatchStore};
|
||||
use crate::{
|
||||
counter::StructId,
|
||||
matches::{group::MatchGroup, Match, Variable},
|
||||
};
|
||||
|
||||
// TODO: implement store according to notes
|
||||
pub(crate) struct DefaultMatchStore {
|
||||
|
@ -32,11 +35,27 @@ impl MatchStore for DefaultMatchStore {
|
|||
|
||||
// TODO: test
|
||||
// TODO: test for cyclical imports
|
||||
fn query_set(&self, paths: &[String]) -> super::MatchSet {
|
||||
fn query_set(&self, paths: &[String]) -> MatchSet {
|
||||
let mut matches: Vec<&Match> = Vec::new();
|
||||
let mut global_vars: Vec<&Variable> = Vec::new();
|
||||
let mut visited_paths = HashSet::new();
|
||||
let mut visited_matches = HashSet::new();
|
||||
let mut visited_global_vars = HashSet::new();
|
||||
|
||||
todo!()
|
||||
query_matches_for_paths(
|
||||
&self.groups,
|
||||
&mut visited_paths,
|
||||
&mut visited_matches,
|
||||
&mut visited_global_vars,
|
||||
&mut matches,
|
||||
&mut global_vars,
|
||||
paths,
|
||||
);
|
||||
|
||||
MatchSet {
|
||||
matches,
|
||||
global_vars,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,9 +65,9 @@ fn load_match_groups_recursively(groups: &mut HashMap<String, MatchGroup>, paths
|
|||
let group_path = PathBuf::from(path);
|
||||
match MatchGroup::load(&group_path) {
|
||||
Ok(group) => {
|
||||
load_match_groups_recursively(groups, &group.resolved_imports);
|
||||
load_match_groups_recursively(groups, &group.imports);
|
||||
groups.insert(path.clone(), group);
|
||||
},
|
||||
}
|
||||
Err(error) => {
|
||||
error!("unable to load match group: {:?}", error);
|
||||
}
|
||||
|
@ -57,8 +76,9 @@ fn load_match_groups_recursively(groups: &mut HashMap<String, MatchGroup>, paths
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
fn query_matches_for_paths<'a>(
|
||||
groups: &'a mut HashMap<String, MatchGroup>,
|
||||
groups: &'a HashMap<String, MatchGroup>,
|
||||
visited_paths: &mut HashSet<String>,
|
||||
visited_matches: &mut HashSet<StructId>,
|
||||
visited_global_vars: &mut HashSet<StructId>,
|
||||
|
@ -83,7 +103,15 @@ fn query_matches_for_paths<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: here we should visit the imported paths recursively
|
||||
query_matches_for_paths(
|
||||
groups,
|
||||
visited_paths,
|
||||
visited_matches,
|
||||
visited_global_vars,
|
||||
matches,
|
||||
global_vars,
|
||||
&group.imports,
|
||||
)
|
||||
}
|
||||
|
||||
visited_paths.insert(path.clone());
|
||||
|
|
|
@ -13,8 +13,21 @@ pub fn is_yaml_empty(yaml: &str) -> bool {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use std::{fs::create_dir_all, path::Path};
|
||||
use tempdir::TempDir;
|
||||
|
||||
pub fn use_test_directory(callback: impl FnOnce(&Path, &Path, &Path)) {
|
||||
let dir = TempDir::new("tempconfig").unwrap();
|
||||
let match_dir = dir.path().join("match");
|
||||
create_dir_all(&match_dir).unwrap();
|
||||
|
||||
let config_dir = dir.path().join("config");
|
||||
create_dir_all(&config_dir).unwrap();
|
||||
|
||||
callback(&dir.path(), &match_dir, &config_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_yaml_empty_document_empty() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user