use crate::params::BaseParams;
use anyhow::{anyhow, Error};
use argh::FromArgs;
use colored_json::{prelude::*, Color as Colour, Style, Styler};
use serde::Serialize;
use std::{fs, path::PathBuf};
#[cfg(feature = "man")]
use crate::man::Man;
pub mod grpc;
pub mod http;
pub mod params;
pub mod record;
pub mod take;
#[cfg(feature = "man")]
mod man;
pub use filmreel::{
FrError, Frame, MetaFrame, Reel, Register, ToStringHidden, ToStringPretty, VirtualReel,
};
pub struct Logger;
impl log::Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Info
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
println!("{}", record.args());
}
}
fn flush(&self) {}
}
pub const fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(
note = "Use `{command_name} man` for details on filmReel, the JSON format.",
example = "Step through the httpbin test in [-i]nteractive mode:
$ {command_name} -i record ./test_data post",
example = "Echo the origin `${{IP}}` that gets written to the cut register from the httpbin.org POST request:
$ {command_name} --cut-out >(jq .IP) take ./test_data/post.01s.body.fr.json",
example = "Run the post reel in a v-reel setup:
$ {command_name} vrecord ./test_data/post.vr.json"
)]
pub struct Command {
#[argh(switch, short = 'v')]
verbose: bool,
#[argh(positional)]
address: Option<String>,
#[argh(option, short = 'H')]
header: Option<String>,
#[argh(option, arg_name = "file")]
cut_out: Option<PathBuf>,
#[argh(switch, short = 'i')]
interactive: bool,
#[argh(switch)]
tls: bool,
#[argh(option, arg_name = "dir")]
proto_dir: Vec<PathBuf>,
#[argh(option, short = 'p', arg_name = "file")]
proto: Vec<PathBuf>,
#[argh(subcommand)]
pub nested: SubCommand,
}
impl Command {
pub fn base_params(&self) -> BaseParams {
BaseParams {
timeout: 30,
timestamp: false,
tls: self.tls,
header: self.header.clone(),
address: self.address.clone(),
proto_path: self.proto_dir.clone(),
proto: self.proto.clone(),
cut_out: self.cut_out.clone(),
interactive: self.interactive,
verbose: self.verbose,
}
}
pub fn get_nested(self) -> SubCommand {
self.nested
}
}
pub struct Opts {
pub verbose: bool,
}
impl Opts {
pub fn new(cmd: &Command) -> Self {
Self {
verbose: cmd.verbose,
}
}
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
#[argh(subcommand)]
pub enum SubCommand {
Version(Version),
Take(Take),
Record(Record),
#[cfg(feature = "man")]
Man(Man),
VirtualRecord(VirtualRecord),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
#[argh(subcommand, name = "version")]
pub struct Version {
#[argh(switch)]
version: bool,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
#[argh(subcommand, name = "take")]
#[argh(
example = "Echo the origin `${{IP}}` that gets written to the cut register from the httpbin.org POST request:
$ dark --cut-out >(jq .IP) take ./test_data/post.01s.body.fr.json"
)]
pub struct Take {
#[argh(positional)]
frame: PathBuf,
#[argh(option, short = 'c')]
cut: Option<PathBuf>,
#[argh(switch, short = 'n')]
no_cut: bool,
#[argh(option, short = 'o', arg_name = "file")]
take_out: Option<PathBuf>,
#[argh(positional)]
merge_cuts: Vec<String>,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
#[argh(subcommand, name = "record")]
#[argh(
example = "Step through the httpbin test in [-i]nteractive mode:
$ dark -i record ./test_data post",
example = "Echo the origin `${{IP}}` that gets written to the cut register from the httpbin.org POST request:
$ dark --cut-out >(jq .IP) record ./test_data post"
)]
pub struct Record {
#[argh(positional)]
reel_path: PathBuf,
#[argh(positional)]
reel_name: String,
#[argh(option, short = 'c')]
cut: Option<PathBuf>,
#[argh(option, short = 'b')]
component: Vec<String>,
#[argh(positional)]
merge_cuts: Vec<String>,
#[argh(option, short = 'o')]
take_out: Option<PathBuf>,
#[argh(option, short = 'r')]
range: Option<String>,
#[argh(option, short = 't', default = "30")]
timeout: u64,
#[argh(switch, short = 's')]
timestamp: bool,
#[argh(switch, short = 'd')]
duration: bool,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
#[argh(subcommand, name = "vrecord")]
#[argh(example = "Run the post reel in a v-reel setup:
$ {command_name} ./test_data/post.vr.json
$ {command_name} ./test_data/alt_post.vr.json")]
pub struct VirtualRecord {
#[argh(positional)]
vreel: String,
#[argh(option, short = 'o')]
take_out: Option<PathBuf>,
#[argh(option, short = 't', default = "30")]
timeout: u64,
#[argh(switch, short = 's')]
timestamp: bool,
#[argh(switch, short = 'd')]
duration: bool,
}
impl Take {
pub fn validate(&self) -> Result<(), Error> {
if !self.frame.is_file() {
return Err(anyhow!("<frame> must be a valid file"));
}
if !self.merge_cuts.is_empty() || self.no_cut {
return Ok(());
}
let cut_file = self.get_cut_file()?;
if !cut_file.is_file() {
return Err(anyhow!(
"{} must be a valid file",
cut_file.to_string_lossy()
));
}
Ok(())
}
pub fn get_cut_file(&self) -> Result<PathBuf, Error> {
if let Some(cut) = &self.cut {
return Ok(cut.clone());
}
let metaframe = filmreel::reel::MetaFrame::try_from(&self.frame)?;
let dir = fs::canonicalize(&self.frame)?;
Ok(metaframe.get_cut_file(dir.parent().unwrap()))
}
}
impl Record {
pub fn validate(&self) -> Result<(), Error> {
if !self.reel_path.is_dir() {
return Err(anyhow!("<path> must be a valid directory"));
}
if let Some(cut) = &self.cut {
if !cut.is_file() {
return Err(anyhow!("<cut> must be a valid file"));
}
} else {
if !self.get_cut_file().is_file() && self.merge_cuts.is_empty() {
return Err(anyhow!(
"unable to find a matching cut file in the given directory"
));
}
}
if let Some(output) = &self.take_out {
if !output.is_dir() {
return Err(anyhow!("<output> must be a valid directory"));
}
}
Ok(())
}
pub fn get_cut_file(&self) -> PathBuf {
if let Some(cut) = &self.cut {
return cut.clone();
}
self.reel_path.join(format!("{}.cut.json", self.reel_name))
}
pub fn get_cut_copy(&self) -> PathBuf {
self.reel_path.join(format!(".{}.cut.json", self.reel_name))
}
}
impl VirtualRecord {
pub fn init(&self) -> Result<VirtualReel, Error> {
let mut vreel = if guess_json_obj(&self.vreel) {
serde_json::from_str(&self.vreel)?
} else {
let vreel_path = PathBuf::from(&self.vreel);
let mut vreel_file = VirtualReel::try_from(vreel_path.clone())?;
if vreel_file.path.is_none() {
let parent_dir = fs::canonicalize(vreel_path.parent().unwrap())?;
vreel_file.path = Some(parent_dir);
}
vreel_file
};
vreel.join_path();
Ok(vreel)
}
}
fn get_styler() -> Styler {
Styler {
bool_value: Style::new(Colour::Magenta),
float_value: Style::new(Colour::RGB(255, 123, 0)),
integer_value: Style::new(Colour::RGB(255, 123, 0)),
nil_value: Style::new(Colour::Cyan),
string_include_quotation: false,
..Default::default()
}
}
trait ToTakeColouredJson {
fn to_coloured_tk_json(&self) -> Result<String, FrError>;
}
impl<T> ToTakeColouredJson for T
where
T: ?Sized + Serialize,
{
fn to_coloured_tk_json(&self) -> Result<String, FrError> {
Ok(self
.to_string_pretty()?
.to_colored_json_with_styler(ColorMode::default().eval(), get_styler())?)
}
}
trait ToTakeHiddenColouredJson: ToTakeColouredJson {
fn to_hidden_tk_json(&self) -> Result<String, FrError>;
}
impl<T> ToTakeHiddenColouredJson for T
where
T: ?Sized + Serialize,
{
fn to_hidden_tk_json(&self) -> Result<String, FrError> {
Ok(self
.to_string_hidden()?
.to_colored_json_with_styler(ColorMode::default().eval(), get_styler())?)
}
}
pub fn guess_json_obj<T: AsRef<str>>(input: T) -> bool {
let obj = input
.as_ref()
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>();
obj.starts_with("{\"") && obj[2..].contains("\":") && obj.ends_with('}')
}