use serde_json::{Map, Value};
pub struct WithPath {
pub import_paths: Vec<String>,
pub oldpath: Vec<String>,
}
impl WithPath {
pub fn new(import_paths: Vec<String>) -> Self {
Self {
import_paths,
oldpath: Vec::new(),
}
}
pub fn enter(&mut self, current_path: &[String]) -> Vec<String> {
self.oldpath = current_path.to_vec();
let mut new_path = self.import_paths.clone();
new_path.extend_from_slice(current_path);
new_path
}
pub fn exit(&self) -> Vec<String> {
self.oldpath.clone()
}
}
#[derive(Debug, Clone)]
pub struct MarkedName {
pub value: String,
pub mark: Option<crate::ported::lint::markedjson::nodes::Mark>,
}
#[derive(Debug, Clone)]
pub enum ImportedFunction {
Qualified(String),
None,
}
#[allow(clippy::too_many_arguments)]
pub fn import_function(
function_type: &str,
name: &MarkedName,
data: &Map<String, Value>,
context: &Map<String, Value>,
mut echoerr: impl FnMut(ImportError),
module: &MarkedName,
) -> ImportedFunction {
debug_assert!(name.mark.is_some());
debug_assert!(module.mark.is_some());
if module.value == "powerline.segments.i3wm" && name.value == "workspaces" {
let key = context
.get("key")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
echoerr(ImportError::Deprecation {
context_key: key,
module: module.value.clone(),
name: name.value.clone(),
name_mark: name.mark.clone(),
module_mark: module.mark.clone(),
});
}
let import_paths: Vec<String> = data
.get("import_paths")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let _ = import_paths;
let _ = function_type;
ImportedFunction::Qualified(format!("{}.{}", module.value, name.value))
}
#[derive(Debug, Clone)]
pub enum ImportError {
Deprecation {
context_key: String,
module: String,
name: String,
name_mark: Option<crate::ported::lint::markedjson::nodes::Mark>,
module_mark: Option<crate::ported::lint::markedjson::nodes::Mark>,
},
ModuleImportFailed {
context_key: String,
module: String,
name_mark: Option<crate::ported::lint::markedjson::nodes::Mark>,
module_mark: Option<crate::ported::lint::markedjson::nodes::Mark>,
},
FunctionNotFound {
context_key: String,
function_type: String,
module: String,
name: String,
name_mark: Option<crate::ported::lint::markedjson::nodes::Mark>,
},
NotCallable {
context_key: String,
module: String,
name: String,
name_mark: Option<crate::ported::lint::markedjson::nodes::Mark>,
module_mark: Option<crate::ported::lint::markedjson::nodes::Mark>,
},
}
pub fn import_segment(
name: &MarkedName,
data: &Map<String, Value>,
context: &Map<String, Value>,
echoerr: impl FnMut(ImportError),
module: &MarkedName,
) -> ImportedFunction {
import_function("segment", name, data, context, echoerr, module)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::lint::markedjson::nodes::Mark;
use serde_json::json;
fn mk_marked(s: &str) -> MarkedName {
MarkedName {
value: s.to_string(),
mark: Some(Mark { line: 0, column: 0 }),
}
}
#[test]
fn with_path_enter_prepends_import_paths() {
let mut wp = WithPath::new(vec!["/extra".to_string(), "/more".to_string()]);
let original = vec!["/usr/lib/python".to_string()];
let new_path = wp.enter(&original);
assert_eq!(
new_path,
vec![
"/extra".to_string(),
"/more".to_string(),
"/usr/lib/python".to_string()
]
);
}
#[test]
fn with_path_enter_snapshots_old_path() {
let mut wp = WithPath::new(vec!["/extra".to_string()]);
let original = vec!["/a".to_string(), "/b".to_string()];
wp.enter(&original);
assert_eq!(wp.oldpath, original);
}
#[test]
fn with_path_exit_returns_old_path() {
let mut wp = WithPath::new(vec!["/extra".to_string()]);
let original = vec!["/orig".to_string()];
wp.enter(&original);
assert_eq!(wp.exit(), original);
}
#[test]
fn with_path_empty_import_paths_passthrough() {
let mut wp = WithPath::new(Vec::new());
let original = vec!["/a".to_string()];
let new_path = wp.enter(&original);
assert_eq!(new_path, original);
}
#[test]
fn import_function_returns_qualified_for_valid_input() {
let name = mk_marked("server_load");
let module = mk_marked("powerline.segments.common.sys");
let data = Map::new();
let context = Map::new();
let mut calls: Vec<ImportError> = Vec::new();
let result = import_function(
"segment",
&name,
&data,
&context,
|e| calls.push(e),
&module,
);
match result {
ImportedFunction::Qualified(q) => {
assert_eq!(q, "powerline.segments.common.sys.server_load");
}
_ => panic!("expected Qualified"),
}
assert!(calls.is_empty());
}
#[test]
fn import_function_emits_deprecation_for_i3wm_workspaces() {
let name = mk_marked("workspaces");
let module = mk_marked("powerline.segments.i3wm");
let data = Map::new();
let mut context = Map::new();
context.insert("key".to_string(), json!("ctx-key"));
let mut calls: Vec<ImportError> = Vec::new();
let _ = import_function(
"segment",
&name,
&data,
&context,
|e| calls.push(e),
&module,
);
assert_eq!(calls.len(), 1);
match &calls[0] {
ImportError::Deprecation {
context_key,
module,
name,
..
} => {
assert_eq!(context_key, "ctx-key");
assert_eq!(module, "powerline.segments.i3wm");
assert_eq!(name, "workspaces");
}
_ => panic!("expected Deprecation"),
}
}
#[test]
fn import_function_does_not_warn_for_other_i3wm_segments() {
let name = mk_marked("mode");
let module = mk_marked("powerline.segments.i3wm");
let data = Map::new();
let context = Map::new();
let mut calls: Vec<ImportError> = Vec::new();
let _ = import_function(
"segment",
&name,
&data,
&context,
|e| calls.push(e),
&module,
);
assert!(calls.is_empty());
}
#[test]
fn import_segment_pins_function_type() {
let name = mk_marked("date");
let module = mk_marked("powerline.segments.common.time");
let data = Map::new();
let context = Map::new();
let mut calls: Vec<ImportError> = Vec::new();
let result = import_segment(&name, &data, &context, |e| calls.push(e), &module);
match result {
ImportedFunction::Qualified(q) => {
assert_eq!(q, "powerline.segments.common.time.date");
}
_ => panic!("expected Qualified"),
}
}
#[test]
fn marked_name_carries_mark() {
let m = mk_marked("foo");
assert!(m.mark.is_some());
assert_eq!(m.value, "foo");
}
#[test]
fn import_paths_field_passed_through_data() {
let name = mk_marked("x");
let module = mk_marked("y");
let mut data = Map::new();
data.insert("import_paths".to_string(), json!(["/a", "/b", "/c"]));
let context = Map::new();
let mut calls: Vec<ImportError> = Vec::new();
let _ = import_function(
"segment",
&name,
&data,
&context,
|e| calls.push(e),
&module,
);
assert!(calls.is_empty());
}
}