#![warn(
// ---------- Stylistic
future_incompatible,
nonstandard_style,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
// ---------- Public
missing_debug_implementations,
missing_docs,
unreachable_pub,
// ---------- Unsafe
unsafe_code,
// ---------- Unused
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_results,
)]
#[macro_use]
extern crate log;
use atelier_core::error::{Error, ErrorKind, Result};
use atelier_core::io::ModelReader;
use atelier_core::model::Model;
use atelier_json as json;
use atelier_smithy as smithy;
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::convert::TryFrom;
use std::env;
use std::fmt::{Debug, Display, Formatter};
use std::fs::{read_dir, File};
use std::path::{Path, PathBuf};
use std::rc::Rc;
pub type FileReader = fn(&mut File) -> Result<Model>;
#[derive(Clone)]
pub struct FileType {
display_name: String,
mime_type: Option<String>,
reader_fn: FileReader,
}
#[derive(Clone)]
pub struct FileTypeRegistry {
by_extension: BTreeMap<String, Rc<FileType>>,
by_mime_type: BTreeMap<String, Rc<FileType>>,
}
#[derive(Clone, Debug)]
pub struct SearchPath {
var_name: String,
paths: BTreeSet<PathBuf>,
}
#[derive(Clone, Debug)]
pub struct ModelAssembler {
file_types: FileTypeRegistry,
paths: HashSet<PathBuf>,
}
impl Debug for FileType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl Display for FileType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"FileType{{{}{}}}",
self.display_name,
match &self.mime_type {
None => String::new(),
Some(mime_type) => format!(": {}", mime_type),
}
)
}
}
impl FileType {
pub fn new(name: &str, reader_fn: FileReader) -> Rc<Self> {
Rc::new(Self {
display_name: name.to_string(),
mime_type: None,
reader_fn,
})
}
pub fn new_with_mime_type(name: &str, reader_fn: FileReader, mime_type: &str) -> Rc<Self> {
Rc::new(Self {
display_name: name.to_string(),
mime_type: Some(mime_type.to_string()),
reader_fn,
})
}
pub fn name(&self) -> &String {
&self.display_name
}
pub fn mime_type(&self) -> &Option<String> {
&self.mime_type
}
pub fn reader(&self) -> &FileReader {
&self.reader_fn
}
}
impl Debug for FileTypeRegistry {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.by_extension.keys()).finish()
}
}
impl Default for FileTypeRegistry {
fn default() -> Self {
let mut new_self = Self::empty();
new_self.register(
FileType::new("JSON AST", |file| {
let mut reader = json::JsonReader::default();
reader.read(file)
}),
json::FILE_EXTENSION,
);
new_self.register(
FileType::new("Smithy IDL", |file| {
let mut reader = smithy::SmithyReader::default();
reader.read(file)
}),
smithy::FILE_EXTENSION,
);
new_self
}
}
impl FileTypeRegistry {
pub fn empty() -> Self {
Self {
by_extension: Default::default(),
by_mime_type: Default::default(),
}
}
pub fn register(&mut self, file_type: Rc<FileType>, extension: &str) {
let _ = self
.by_extension
.insert(extension.to_lowercase(), file_type.clone());
if let Some(mime_type) = &file_type.mime_type {
let _ = self
.by_mime_type
.insert(mime_type.to_lowercase(), file_type.clone());
}
}
pub fn register_all(&mut self, file_type: Rc<FileType>, extensions: &[&str]) {
for extension in extensions {
let _ = self
.by_extension
.insert(extension.to_lowercase(), file_type.clone());
}
}
pub fn contains(&self, extension: &str) -> bool {
self.by_extension.contains_key(&extension.to_lowercase())
}
pub fn get(&self, extension: &str) -> Option<&Rc<FileType>> {
self.by_extension.get(&extension.to_lowercase())
}
pub fn get_by_mime_type(&self, mime_type: &str) -> Option<&Rc<FileType>> {
self.by_mime_type.get(&mime_type.to_lowercase())
}
pub fn remove(&mut self, extension: &str) -> Option<Rc<FileType>> {
self.by_extension.remove(&extension.to_lowercase())
}
pub fn extensions(&self) -> impl Iterator<Item = &String> {
self.by_extension.keys()
}
}
impl Default for SearchPath {
fn default() -> Self {
Self::from_env(Self::ENV_PATH_NAME)
}
}
impl SearchPath {
const PATH_SEP: &'static str = ":";
const ENV_PATH_NAME: &'static str = "SMITHY_PATH";
pub fn from_env(env_name: &str) -> Self {
if let Ok(search_path) = env::var(env_name) {
if !search_path.is_empty() {
let mut paths: BTreeSet<PathBuf> = Default::default();
info!(
"SearchPath::from_env({:?}) - parsing search path '{}'",
env_name, search_path
);
for path in search_path.split(Self::PATH_SEP) {
let path = path.trim();
if !path.is_empty() {
let _ = paths.insert(PathBuf::from(path));
}
}
Self {
var_name: env_name.to_string(),
paths,
}
} else {
warn!(
"SearchPath::from_env({:?}) - search_path is empty",
env_name
);
Self {
var_name: env_name.to_string(),
paths: Default::default(),
}
}
} else {
warn!(
"SearchPath::from_env({:?}) - no value found for `env::var`",
env_name
);
Self {
var_name: env_name.to_string(),
paths: Default::default(),
}
}
}
pub fn env_name(&self) -> &String {
&self.var_name
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn paths(&self) -> impl Iterator<Item = &PathBuf> {
self.paths.iter()
}
}
impl Default for ModelAssembler {
fn default() -> Self {
Self::new(FileTypeRegistry::default(), Some(SearchPath::default()))
}
}
impl TryFrom<ModelAssembler> for Model {
type Error = Error;
fn try_from(value: ModelAssembler) -> std::result::Result<Self, Self::Error> {
let mut value = value;
Model::try_from(&mut value)
}
}
impl TryFrom<&mut ModelAssembler> for Model {
type Error = Error;
fn try_from(value: &mut ModelAssembler) -> std::result::Result<Self, Self::Error> {
info!("Model::try_from::<ModelAssembler>(...)");
if value.is_empty() {
Ok(Model::default())
} else {
let models: std::result::Result<Vec<Model>, Self::Error> = value
.expand_file_paths()
.iter()
.map(|file_name| value.read_model(&file_name))
.collect();
debug!(
"Model::try_from::<ModelAssembler>(...): found models => {:#?}",
&models
);
match models {
Ok(mut models) => {
if models.is_empty() {
warn!(
"Model::try_from::<ModelAssembler>(...): No models found to assemble!"
);
Ok(Model::default())
} else {
let mut merged = models.remove(0);
for other in models {
merged.merge(other)?;
}
Ok(merged)
}
}
Err(err) => Err(err),
}
}
}
}
impl ModelAssembler {
pub fn new(file_types: FileTypeRegistry, search_path: Option<SearchPath>) -> Self {
info!("ModelAssembler::new()");
let new_self = Self {
file_types,
paths: Default::default(),
};
match search_path {
None => new_self,
Some(search_path) => Self::include_search_path(new_self, &search_path),
}
}
pub fn push(&mut self, path: &Path) -> &mut Self {
info!("ModelAssembler::push({:?})", path);
let _ = self.paths.insert(PathBuf::from(path));
self
}
pub fn push_str(&mut self, path: &str) -> &mut Self {
info!("ModelAssembler::push_str({})", path);
let _ = self.paths.insert(PathBuf::from(path));
self
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn paths(&self) -> impl Iterator<Item = &Path> {
self.paths.iter().map(|p| p.as_ref())
}
pub fn expand_file_paths(&self) -> Vec<PathBuf> {
info!("ModelAssembler::expand_file_paths()");
let mut results = Vec::default();
for path in &self.paths {
self.expand_path(path, &mut results);
}
results
}
fn include_search_path(self, search_path: &SearchPath) -> Self {
debug!(
"ModelAssembler::include_search_path - using environment variable '{}'",
search_path.env_name()
);
let mut mut_self = self;
for path in search_path.paths() {
let _ = mut_self.push(&PathBuf::from(path));
}
mut_self
}
#[allow(clippy::ptr_arg)]
fn expand_path(&self, path: &PathBuf, results: &mut Vec<PathBuf>) {
info!("ModelAssembler::expand_path({:?})", path);
if path.is_file() {
if let Some(extension) = path.extension() {
let extension = extension.to_string_lossy();
if self.file_types.contains(extension.as_ref()) {
debug!("ModelAssembler::expand_path - adding file path {:?}", path);
let _ = results.push(path.clone());
}
}
} else if path.is_dir() {
debug!("ModelAssembler::expand_path - reading dir path {:?}", path);
for entry in read_dir(path).unwrap() {
let entry = entry.unwrap();
self.expand_path(&entry.path(), results);
}
}
}
fn read_model(&self, path: &Path) -> Result<Model> {
info!("ModelAssembler::read_model({:?})", path);
if let Some(extension) = path.extension() {
let extension = extension.to_string_lossy().to_lowercase();
if let Some(file_type) = self.file_types.get(extension.as_ref()) {
let mut file = File::open(path).unwrap();
Ok(file_type.reader()(&mut file)?)
} else {
error!("ModelAssembler::read_model - not a known extension");
Err(ErrorKind::InvalidRepresentation("unknown".to_string()).into())
}
} else {
error!("ModelAssembler::read_model - has no extension");
Err(ErrorKind::InvalidRepresentation("none".to_string()).into())
}
}
}