cassiopeia: init project

This is the beginning of a Rust re-implementation of the original ruby
scripts.  cassiopeia is a simple time tracking tool, that integrates
into the larger ecosystem of project management tools that I use to
organise my business.
wip/yesman
Katharina Fey 4 years ago
parent 13cb8de4b6
commit 5d9cb68ba2
  1. 8
      apps/cassiopeia/Cargo.toml
  2. 145
      apps/cassiopeia/src/file.rs
  3. 3
      apps/cassiopeia/src/main.rs

@ -0,0 +1,8 @@
[package]
name = "cassiopeia"
version = "0.1.0"
authors = ["Mx Kookie <kookie@spacekookie.de>"]
edition = "2018"
[dependencies]
chrono = "*"

@ -0,0 +1,145 @@
//! Parse the cassiopeia file format
//!
//! Each file is associated with a single project. This way there is
//! no need to associate session enries with multiple customers and
//! projcets. Currently there's also no way to cross-relate sessions
//! between projects or clients, although the metadata in the header
//! is available to do so in the future
//!
//! ## Structure
//!
//! `cassiopeia` files should use the `.cass` extension, although this
//! implementation is not opinionated on that.
//!
//! A line starting with `;` is a comment and can be ignored. A line
//! can have a comment anywhere, which means that everything after it
//! gets ignored. There are no block comments.
//!
//! A regular statements has two parts: a key, and a value. Available
//! keys are:
//!
//! - HEADER
//! - START
//! - STOP
//! - FINISH
//!
//! A file has to have at least one `HEADER` key, containing a certain
//! number of fields to be considered valid. The required number of
//! fields may vary between versions.
//!
//! ### HEADER
//!
//! `cassiopeia` in princpile only needs a single value to parse a
//! file, which is `version`. It is however recommended to add
//! additional metadata to allow future processing into clients and
//! cross-referencing projects. Importantly: header keys that are not
//! expected will be ignored.
//!
//! The general header format is a comma-separated list with a key
//! value pair, separated by an equals sign. You can use spaces in
//! both keys and values without having to escape them or use special
//! quotes. Leading and trailing spaces will be removed.
//!
//! ```
//! HEADER version=0.0.0,location=Berlin
//! HEADER work schedule=mon tue wed
//! ```
//!
//! When re-writing the file format, known/ accepted keys should go
//! first. All other unknown keys will be printed alphabetically at
//! the end. This way it's possible for an outdated implementation to
//! pass through unknown keys, or users to add their own keys.
use chrono::{DateTime, Utc};
use std::{fs::File, io::Read, path::Path};
/// A cassiopeia file that has been successfully parsed
pub struct TimeFile {
path: PathBuf,
content: Vec<Statement>,
}
impl TimeFile {
/// Open an existing `.cass` file on disk. Panics!
pub fn open(p: impl Into<Path>) -> Self {
let mut f = File::open(p).unwrap();
let mut cont = String::new();
f.read_to_string(&mut cont).unwrap();
}
}
/// A statement in a `.cass` line
///
/// While the whole file get's re-written on every run to update
/// version numbers and header values, the structure of the file is
/// preserved.
pub enum Statement {
/// A blank line
Blank,
/// A comment line that is echo-ed back out
Comment(String),
/// Header value
Header(Vec<HeaderVal>),
/// A session start value
Start(DateTime<Utc>),
/// A session stop value
Stop(DateTime<Utc>),
/// A project finish value
Finish(DateTime<Utc>),
}
/// A set of header value
pub struct HeaderVal {
/// Header key
key: String,
/// Header value
val: String,
}
impl HeaderVal {
fn new<S: Into<String>>(key: S, val: S) -> Self {
Self {
key: key.into(),
val: val.into(),
}
}
/// Test if a header value is known to this implementation
fn known(&self) -> bool {
match self.key {
"version" => true,
_ => false,
}
}
}
/// A builder for cass files
#[cfg(tests)]
struct FileBuilder {
acc: Vec<Statement>,
}
impl FileBuilder {
fn new() -> Self {
Self { acc: vec![] }
}
fn header(mut self, data: Vec<(&str, &str)>) -> Self {
self.acc.push(Statement::Header(
data.into_iter()
.map(|(key, val)| HeaderVal::new(key, val))
.collect(),
));
self
}
fn build(self) -> String {
format!(";; This file was generated by cassiopeia (reference)\n{}", self.acc.into_iter().map(|s| s.render()).collect::<Vec<_>().join("\n"))
}
}
#[test]
fn empty_file() {
let fb = FileBuilder::new().header(vec![("version", "0.3.0"), ("project", "testing")]);
}

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}
Loading…
Cancel
Save