Compare commits
8 Commits
arg-parsin
...
master
Author | SHA1 | Date |
---|---|---|
Avril | c9d71b0bec | 4 years ago |
Avril | 784f3f2c10 | 4 years ago |
Avril | 5169227499 | 4 years ago |
Avril | b7d6bb0095 | 4 years ago |
Avril | e8d215a65f | 4 years ago |
Avril | 1698eaa37d | 4 years ago |
Avril | c253b593ca | 4 years ago |
Avril | b78475b91a | 4 years ago |
@ -0,0 +1,124 @@
|
|||||||
|
use super::*;
|
||||||
|
use treemap::{
|
||||||
|
Rect,
|
||||||
|
Mappable,
|
||||||
|
TreemapLayout
|
||||||
|
};
|
||||||
|
use data::{FsInfo, INodeInfoGraph, INodeInfoGraphEntry};
|
||||||
|
|
||||||
|
/// A treemap of all **files** in the graph.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Treemap
|
||||||
|
{
|
||||||
|
//layout: TreemapLayout,
|
||||||
|
nodes: Vec<MapNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Treemap
|
||||||
|
{
|
||||||
|
/// All nodes of the map.
|
||||||
|
#[inline] pub fn nodes(&self) -> &[MapNode]
|
||||||
|
{
|
||||||
|
&self.nodes[..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct MapNode
|
||||||
|
{
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
vw_size: f64, // Should be halved each iteration
|
||||||
|
vw_bounds: Rect, // should be Rect::new() before aligntment
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MapNode
|
||||||
|
{
|
||||||
|
/// The calculated bounds of the node
|
||||||
|
#[inline] pub fn bounds(&self) -> &Rect
|
||||||
|
{
|
||||||
|
&self.vw_bounds
|
||||||
|
}
|
||||||
|
/// The name of the node
|
||||||
|
#[inline] pub fn name(&self) -> &str
|
||||||
|
{
|
||||||
|
&self.name[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline] fn size(&self) -> f64 //Is this useful for consumer?
|
||||||
|
{
|
||||||
|
self.vw_size
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline] fn new(name: String) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
vw_size: 1.0,
|
||||||
|
vw_bounds: Rect::new(),
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a treemap from this graph.
|
||||||
|
pub fn treemap(_cfg: &Config, graph: &INodeInfoGraph, (w, h): (f64, f64)) -> Treemap
|
||||||
|
{
|
||||||
|
let layout = TreemapLayout::new();
|
||||||
|
let mut nodes = Vec::with_capacity(graph.len());
|
||||||
|
//TODO: Recursively walk the graph, halving size with each iteration. (Maybe we need `INodeInfoGraph` here, not `Hierarchicalinodegraph`?)
|
||||||
|
let total_size = graph.total_size();
|
||||||
|
|
||||||
|
let size = 1.0;
|
||||||
|
|
||||||
|
fn calc_path<'a, I: IntoIterator<Item = INodeInfoGraphEntry<'a>>>(insert: &'a mut Vec<MapNode>, from: I, total_size: u64, size: f64, scale: f64)
|
||||||
|
{
|
||||||
|
for top in from {
|
||||||
|
let path = top.path();
|
||||||
|
match top.info() {
|
||||||
|
FsInfo::Directory(_) => {
|
||||||
|
//TODO: Do we add dir itself? I think not?
|
||||||
|
// Add children
|
||||||
|
let size = size * 0.5;
|
||||||
|
calc_path(insert, top.level().unwrap(), total_size, size, scale);
|
||||||
|
},
|
||||||
|
&FsInfo::File(sz, _) => {
|
||||||
|
let fract = (sz as f64) / (total_size as f64);
|
||||||
|
insert.push(MapNode {
|
||||||
|
name: path.to_string_lossy().into_owned(),
|
||||||
|
vw_size: fract * scale,
|
||||||
|
vw_bounds: Rect::new(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calc_path(&mut nodes, graph.top_level(), total_size, size, 1.0);
|
||||||
|
|
||||||
|
layout.layout_items(&mut nodes[..], Rect {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
w, h,
|
||||||
|
});
|
||||||
|
|
||||||
|
Treemap {
|
||||||
|
//layout,
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mappable for MapNode
|
||||||
|
{
|
||||||
|
fn size(&self) -> f64
|
||||||
|
{
|
||||||
|
self.vw_size
|
||||||
|
}
|
||||||
|
fn bounds(&self) -> &Rect
|
||||||
|
{
|
||||||
|
&self.vw_bounds
|
||||||
|
}
|
||||||
|
fn set_bounds(&mut self, bounds: Rect)
|
||||||
|
{
|
||||||
|
self.vw_bounds = bounds;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
//! Graph inspection REPL
|
||||||
|
use super::*;
|
||||||
|
use std::{fmt, error};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use rustyline::error::ReadlineError;
|
||||||
|
use rustyline::Editor;
|
||||||
|
|
||||||
|
mod env;
|
||||||
|
mod command;
|
||||||
|
mod opcodes;
|
||||||
|
|
||||||
|
/// Default history file name
|
||||||
|
///
|
||||||
|
/// # Path lookup
|
||||||
|
/// * To make this an absolute path, start it with `/`
|
||||||
|
/// * To make this path relative to the user's home directory, start it with `~/` (Note: If we are unable to find the user's home directory, it is considered a lookup **failure** (*not* a **disable**) and `calculate_history_path()` will return `Err`.)
|
||||||
|
/// * Otherwise, the path is taken relative to the current working directory
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// This is only used when the `save-history` feature is enabled.
|
||||||
|
const DEFAULT_HISTORY_FILE: &'static str = "~/.dirstat_history";
|
||||||
|
|
||||||
|
/// Get the path to the history file.
|
||||||
|
///
|
||||||
|
/// # Lookup
|
||||||
|
/// * If the `DIRSTAT_HISTORY` envvar is set and not empty, use this file path.
|
||||||
|
/// * If the `DIRSTAT_HISTORY` envvar is set and empty, saving history is considered **disabled**, we return `Ok(None)`.
|
||||||
|
/// * Otherwise, refer to lookup rules for `DEFAULT_HISTORY_FILE`.
|
||||||
|
pub fn calculate_history_path() -> io::Result<Option<PathBuf>>
|
||||||
|
{
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature="save-history")] {
|
||||||
|
todo!()
|
||||||
|
} else {
|
||||||
|
unreachable!("Tried to calculate repl history path when binary was compiled with history saving perma-disabled.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inspect the graph with commands
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// This function synchronously blocks the current thread.
|
||||||
|
pub fn inspect(cfg: &Config, graph: &HierarchicalINodeGraph) -> Result<(), ReplExitError>
|
||||||
|
{
|
||||||
|
let mut repl = Editor::<()>::new(); //TODO: Change `()` to our completer, when we have a decent idea of how they'll work.
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature="save-history")] {
|
||||||
|
let history_path = match calculate_history_path() {
|
||||||
|
Ok(Some(path)) => {
|
||||||
|
if let Err(err) = repl.load_history(&path)
|
||||||
|
{
|
||||||
|
cfg_eprintln!(cfg, "Failed to load repl history from {:?}: {}", path, err);
|
||||||
|
}
|
||||||
|
Some(path)
|
||||||
|
},
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(err)
|
||||||
|
{
|
||||||
|
cfg_eprintln!(cfg, "Failed to find repl history: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: Result<(), ReplExitError> = try {
|
||||||
|
loop {
|
||||||
|
let line = repl.readline("> ")?;
|
||||||
|
repl.add_history_entry(&line);
|
||||||
|
|
||||||
|
//TODO: How to interpret commands?
|
||||||
|
todo!("Interpret commands from `line`.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature="save-history")] {
|
||||||
|
if let Some(path) = history_path {
|
||||||
|
if let Err(err) = repl.save_history(&path)
|
||||||
|
{
|
||||||
|
cfg_eprintln!(cfg, "Failed to save repl history to {:?}: {}", path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When the inspection repl exists abnormally.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ReplExitError
|
||||||
|
{
|
||||||
|
ReadLine(ReadlineError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ReadlineError> for ReplExitError
|
||||||
|
{
|
||||||
|
#[inline] fn from(from: ReadlineError) -> Self
|
||||||
|
{
|
||||||
|
Self::ReadLine(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for ReplExitError
|
||||||
|
{
|
||||||
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
|
Some(match &self
|
||||||
|
{
|
||||||
|
Self::ReadLine(rl) => rl
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Display for ReplExitError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
match self
|
||||||
|
{
|
||||||
|
Self::ReadLine(ReadlineError::Eof) |
|
||||||
|
Self::ReadLine(ReadlineError::Interrupted) => write!(f, "exit"),
|
||||||
|
Self::ReadLine(_) => write!(f, "readline error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
|||||||
|
//! Repl commands
|
||||||
|
use super::*;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::env::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Context<'a>
|
||||||
|
{
|
||||||
|
/// Environment containing variable name mappings.
|
||||||
|
env: &'a mut Lexenv,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for commands.
|
||||||
|
///
|
||||||
|
/// # Defining commands
|
||||||
|
/// A command object should be created once only, and then referenced and executed using `params` and through mutating `cx`.
|
||||||
|
pub trait Command: fmt::Debug
|
||||||
|
{
|
||||||
|
fn execute(&self, cx: &mut Context<'_>, params: Vec<Value>) -> eyre::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command structurally parsed.
|
||||||
|
///
|
||||||
|
/// Can be converted into `Command` with the `TryInto` trait.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct IR
|
||||||
|
{
|
||||||
|
op: String,
|
||||||
|
params: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for IR
|
||||||
|
{
|
||||||
|
type Err = CommandParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error when parsing a command into `IR`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandParseError(String);
|
||||||
|
|
||||||
|
impl error::Error for CommandParseError{}
|
||||||
|
|
||||||
|
impl fmt::Display for CommandParseError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "failed to parse command from {:?}", self.0)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,242 @@
|
|||||||
|
//! Execution environment for repl
|
||||||
|
use super::*;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Lexenv
|
||||||
|
{
|
||||||
|
/// Maps symbol name to value in generations.
|
||||||
|
kvstack: BTreeMap<usize, HashMap<String, Value>>,
|
||||||
|
/// Current generation of the satck
|
||||||
|
current_generation: usize,
|
||||||
|
}
|
||||||
|
impl Lexenv
|
||||||
|
{
|
||||||
|
/// The generation number of the current level.
|
||||||
|
pub fn depth(&self) -> usize
|
||||||
|
{
|
||||||
|
self.current_generation
|
||||||
|
}
|
||||||
|
/// Create a new empty lexenv
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
kvstack: BTreeMap::new(),
|
||||||
|
current_generation: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// All valid symbols at this level.
|
||||||
|
///
|
||||||
|
/// # Ordering
|
||||||
|
/// Each symbol's level will appear in the order from level 0 to the current level, however the order of intra-level symbols is undefined.
|
||||||
|
pub fn symbols(&self) -> impl Iterator<Item = &'_ Value> + '_
|
||||||
|
{
|
||||||
|
self.kvstack.range(0..=self.current_generation).flat_map(|(_, v)| v.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All valid symbols **in** this level.
|
||||||
|
pub fn symbols_local(&self) -> impl Iterator<Item = &'_ Value> + '_
|
||||||
|
{
|
||||||
|
OptionIterator::from(self.kvstack.get(&self.current_generation).map(|x| x.values()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the current level, but leave its memory allocated for further use.
|
||||||
|
pub fn pop(&mut self)
|
||||||
|
{
|
||||||
|
self.kvstack.entry(self.current_generation).or_insert_with(|| HashMap::new()).clear();
|
||||||
|
if self.current_generation > 0 {
|
||||||
|
self.current_generation-=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a symbol from the **current** level.
|
||||||
|
pub fn remove(&mut self, name: &str) -> Option<Value>
|
||||||
|
{
|
||||||
|
self.kvstack.entry(self.current_generation).or_insert_with(|| HashMap::new()).remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a new value mapping into the current level.
|
||||||
|
pub fn insert(&mut self, name: String, value: Value)
|
||||||
|
{
|
||||||
|
self.kvstack.entry(self.current_generation).or_insert_with(|| HashMap::new()).insert(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up a symbol in this or any of the above levels.
|
||||||
|
pub fn lookup(&self, name: &str) -> Option<&Value>
|
||||||
|
{
|
||||||
|
for (_, lvmap) in self.kvstack.range(0..=self.current_generation).rev()
|
||||||
|
{
|
||||||
|
let m = lvmap.get(name);
|
||||||
|
if m.is_some() {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up a symbol in this level.
|
||||||
|
pub fn lookup_local(&self, name: &str) -> Option<&Value>
|
||||||
|
{
|
||||||
|
self.kvstack.get(&self.current_generation).map(|map| map.get(name)).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new, empty level.
|
||||||
|
pub fn push(&mut self)
|
||||||
|
{
|
||||||
|
self.current_generation+=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the current level, deallocating any memory it was using.
|
||||||
|
pub fn pop_clear(&mut self)
|
||||||
|
{
|
||||||
|
if self.current_generation > 0 {
|
||||||
|
self.kvstack.remove(&self.current_generation);
|
||||||
|
self.current_generation -=1;
|
||||||
|
} else {
|
||||||
|
self.kvstack.entry(0).or_insert_with(|| HashMap::new()).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value type
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum Value
|
||||||
|
{
|
||||||
|
String(String),
|
||||||
|
Symbol(String),
|
||||||
|
List(Vec<Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value
|
||||||
|
{
|
||||||
|
/// Parse from an iterator of `char`s.
|
||||||
|
pub fn parse_chars<T>(ch: &mut T) -> Result<Self, ValueParseError>
|
||||||
|
where T: Iterator<Item = char>
|
||||||
|
{
|
||||||
|
|
||||||
|
match ch.next()
|
||||||
|
{
|
||||||
|
Some('(') => {
|
||||||
|
todo!("list");
|
||||||
|
},
|
||||||
|
Some('"') => {
|
||||||
|
todo!("string");
|
||||||
|
},
|
||||||
|
Some(first_chr) => {
|
||||||
|
todo!("symbol");
|
||||||
|
},
|
||||||
|
_ => Err(ValueParseError(String::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Parse a `Value` from this string and then return the rest of the string.
|
||||||
|
#[deprecated]
|
||||||
|
pub fn parse_running(s: &str) -> Result<(Self, &'_ str), ValueParseError>
|
||||||
|
{
|
||||||
|
match s.trim().as_bytes()
|
||||||
|
{
|
||||||
|
& [b'(', ..] => {
|
||||||
|
todo!("list");
|
||||||
|
},
|
||||||
|
& [b'"', ..] => {
|
||||||
|
todo!("string");
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
todo!("shmbol");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Value
|
||||||
|
{
|
||||||
|
type Err = ValueParseError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Self::parse_running(s).map(|(x, _)| x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value
|
||||||
|
{
|
||||||
|
pub fn try_as_symbol(&self) -> Result<&str, ValueTypeError>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Symbol(s) => Ok(&s[..]),
|
||||||
|
_ => Err(ValueTypeError::Symbol),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_as_string(&self) -> Result<&str, ValueTypeError>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Symbol(s) |
|
||||||
|
Self::String(s) => Ok(&s[..]),
|
||||||
|
_ => Err(ValueTypeError::String),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_as_list(&self) -> Result<&[Value], ValueTypeError>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::List(l) => Ok(&l[..]),
|
||||||
|
_ => Err(ValueTypeError::List),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_symbol(&self) -> Option<&str>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Symbol(s) => Some(&s[..]),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_string(&self) -> Option<&str>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Symbol(s) |
|
||||||
|
Self::String(s) => Some(&s[..]),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_list(&self) -> Option<&[Value]>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::List(l) => Some(&l[..]),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error when using `try_as_*` functions on `Value`.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
|
||||||
|
pub enum ValueTypeError
|
||||||
|
{
|
||||||
|
Symbol,
|
||||||
|
String,
|
||||||
|
List,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error when parsing a `Value` from a stirng.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ValueParseError(String);
|
||||||
|
|
||||||
|
impl error::Error for ValueParseError{}
|
||||||
|
impl fmt::Display for ValueParseError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "cannot parse {:?}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for ValueTypeError{}
|
||||||
|
impl fmt::Display for ValueTypeError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "type error: expected ")?;
|
||||||
|
match self {
|
||||||
|
Self::Symbol => write!(f, "symbol"),
|
||||||
|
Self::String => write!(f, "string"),
|
||||||
|
Self::List => write!(f, "list"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
//! Defined commands
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use env::*;
|
||||||
|
use command::*;
|
||||||
|
|
||||||
|
/// Contains all operations
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Operations
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in new issue