use crate::{extra_keys::ExtraKey, settings::Settings};
use rustc_hash::FxHashSet;
use serde::Deserialize;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Configuration {
pub extra_keys: Option<Vec<ExtraKey>>,
pub drop_empty_cells: Option<bool>,
pub drop_output: Option<bool>,
pub drop_count: Option<bool>,
pub drop_id: Option<bool>,
pub drop_tagged_cells: Option<Vec<String>>,
pub strip_init_cell: Option<bool>,
pub keep_keys: Option<Vec<ExtraKey>>,
}
pub const EXTRA_KEYS: &[&str] = &[
"metadata.signature",
"metadata.widgets",
"cell.metadata.collapsed",
"cell.metadata.ExecuteTime",
"cell.metadata.execution",
"cell.metadata.heading_collapsed",
"cell.metadata.hidden",
"cell.metadata.scrolled",
];
fn default_extra_keys() -> FxHashSet<ExtraKey> {
#[allow(clippy::unwrap_used)]
EXTRA_KEYS
.iter()
.map(|s| ExtraKey::from_str(s).unwrap())
.collect()
}
impl Configuration {
pub fn into_settings(self) -> Settings {
let mut extra_keys = default_extra_keys();
extra_keys.extend(self.extra_keys.unwrap_or_default());
for key in &self.keep_keys.unwrap_or_default() {
extra_keys.remove(key);
}
Settings {
extra_keys,
drop_empty_cells: self.drop_empty_cells.unwrap_or(false),
drop_output: self.drop_output.unwrap_or(true),
drop_count: self.drop_count.unwrap_or(true),
drop_id: self.drop_id.unwrap_or(false),
drop_tagged_cells: self
.drop_tagged_cells
.map(FxHashSet::from_iter)
.unwrap_or_default(),
strip_init_cell: self.strip_init_cell.unwrap_or(false),
}
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
struct Pyproject {
tool: Option<Tools>,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
struct Tools {
nbwipers: Option<Configuration>,
}
pub fn find_pyproject() -> Option<PathBuf> {
let cwd = path_absolutize::path_dedot::CWD.as_path();
for ancestor in cwd.ancestors() {
let pyproject = ancestor.join("pyproject.toml");
if pyproject.is_file() {
return Some(pyproject);
}
}
None
}
#[derive(Debug, Error)]
pub enum PyprojectError {
#[error("Pyproject IO Error")]
IOError(#[from] io::Error),
#[error("Pyproject Parse Error")]
ParseError(#[from] toml::de::Error),
}
pub fn read_pyproject(path: &Path) -> Result<Option<Configuration>, PyprojectError> {
let contents = std::fs::read_to_string(path)?;
let pyproject: Pyproject = toml::from_str(&contents)?;
let config = pyproject.tool.and_then(|tools| tools.nbwipers);
Ok(config)
}
pub fn resolve(config_file: Option<&Path>) -> Result<Configuration, PyprojectError> {
if let Some(config_file) = config_file {
let config = read_pyproject(config_file)?;
Ok(config.unwrap_or_default())
} else if let Some(pyproject_file) = find_pyproject() {
let config = read_pyproject(&pyproject_file)?;
Ok(config.unwrap_or_default())
} else {
Ok(Configuration::default())
}
}