pub mod mapping;
pub mod source;
pub mod verify;
use crate::mapping::SnippetMapExt as _;
use anyhow::Context as _;
pub use codesnip_attr::{entry, skip};
use codesnip_core::{Error::FileNotFound, SnippetMap};
use serde_json::to_string;
use source::Sources;
use std::{
fs::File,
io::{Read as _, Write as _, stdout},
path::{Path, PathBuf},
};
use structopt::{
StructOpt,
clap::AppSettings::{DeriveDisplayOrder, InferSubcommands},
};
#[derive(Debug, StructOpt)]
#[structopt(
bin_name = "cargo",
global_settings = &[DeriveDisplayOrder, InferSubcommands]
)]
pub enum Opt {
Codesnip(Config),
}
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab-case")]
pub struct Config {
#[structopt(long, value_name = "FILE", parse(from_os_str))]
pub use_cache: Vec<PathBuf>,
#[structopt(long, value_name = "FILE", parse(from_os_str))]
pub source_config: Option<PathBuf>,
#[structopt(subcommand)]
pub cmd: Command,
}
#[derive(Debug, StructOpt)]
pub enum Command {
Cache {
#[structopt(value_name = "FILE", parse(from_os_str))]
output: PathBuf,
},
List {
#[structopt(long)]
not_hide: bool,
},
Snippet {
#[structopt(value_name = "FILE", parse(from_os_str))]
output: Option<PathBuf>,
#[structopt(long)]
ignore_include: bool,
},
Bundle {
#[structopt(value_name = "NAME")]
name: String,
#[structopt(short, long, value_name = "NAME")]
excludes: Vec<String>,
},
Verify {
#[structopt(long, value_name = "TOOLCHAIN", default_value = "stable")]
toolchain: String,
#[structopt(long, value_name = "EDITION", default_value = "2021")]
edition: String,
#[structopt(long)]
verbose: bool,
},
}
impl Opt {
pub fn from_args() -> Self {
StructOpt::from_args()
}
pub fn execute(&self) -> anyhow::Result<()> {
let Opt::Codesnip(opt) = self;
opt.execute()
}
}
impl Config {
pub fn execute(&self) -> anyhow::Result<()> {
let mut map = if let Some(source_config) = &self.source_config {
let target_config = Sources::load(source_config)?;
target_config.snippet_map()?
} else {
SnippetMap::new()
};
let mut buf = Vec::new();
for cache in self.use_cache.iter() {
buf.clear();
let mut file = File::open(cache).map_err(|err| FileNotFound(cache.clone(), err))?;
file.read_to_end(&mut buf)?;
let (mapt, _): (SnippetMap, _) =
bincode::serde::decode_from_slice(&buf, bincode::config::standard())?;
map.extend(mapt);
}
self.cmd.execute(map)
}
}
impl Command {
pub fn execute(&self, map: SnippetMap) -> anyhow::Result<()> {
match self {
Self::Cache { output } => {
create_recursive(output)?.write_all(&bincode::serde::encode_to_vec(
&map,
bincode::config::standard(),
)?)?;
}
Self::List { not_hide } => {
let list = map.keys(!not_hide).join(" ");
stdout().write_all(list.as_bytes())?;
}
Self::Snippet {
output,
ignore_include,
} => {
let snippet = to_string(&map.to_vscode(*ignore_include))?;
match output {
Some(file) => create_recursive(file)?.write_all(snippet.as_bytes())?,
None => stdout().write_all(snippet.as_bytes())?,
}
}
Self::Bundle { name, excludes } => {
let link = map
.map
.get(name)
.with_context(|| format!("snippet `{}` not found", name))?;
let excludes = excludes.iter().map(|s| s.as_str()).collect();
stdout().write_all(map.bundle(name, link, excludes, true).as_bytes())?;
}
Self::Verify {
toolchain,
verbose,
edition,
} => {
verify::execute(map, toolchain, edition, *verbose)?;
}
}
Ok(())
}
}
fn create_recursive<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)?;
}
File::create(path)
}