Auto merge of #34 - matthiasbeyer:doc, r=matthiasbeyer

Add some documentation

Still does not produce the documentation I want, though it basically works now.
This commit is contained in:
Homu 2015-12-30 17:54:41 +09:00
commit 25045b7345
19 changed files with 417 additions and 125 deletions

166
README.md
View file

@ -21,23 +21,11 @@ see [Linking](#Linking))
Some modules might not want to store content there, for example you don't want
to put your icalendar files in there. So the calendar module just crawls through
your ical files and puts the scanned meta-information into the store. Of course,
if the content of the ical file changes, the store entry does not. It still
points (via its JSON content for example) to the same file. So changes are not
tracked (We can argue here whether we want to copy the contents to the store for
ical and vcard files, but we cannot argue on for example music files).
your ical files and puts only a link and some hashsums (for refinding on content
move) to the store. Changes are not tracked via this model.
If a (for example ical-)file gets removed, the store entry gets invalid and has
to be garbage-collected.
> The current model is not fixed yet. I'm thinking about copying .ical and
> .vcard, basically all text files, to the store.
> This is not possible for media files like music or movies, though. Also this
> is not feasible for documents like .pdf or similar.
Each of the following modules has a short description including a table what
core features are required to get it working.
### Linking
The UUID/SHA hashes in the file names can be used to connect two store entries.
@ -89,40 +77,75 @@ should't touch the content at all.
Here is a short overview what are the modules like:
| Module | Indexer | Header | Hdr-Format | Content |
| :---------------- | ------- | ------- | ---------- | ------- |
| Bookmarks | | X | JSON | |
| Contacts | X | X | JSON | |
| Calendar | X | X | JSON | |
| Notes | | X | YAML | X |
| Mail | X | X | JSON | |
| Wiki | | X | YAML | X |
| Todo | | X | YAML | |
| Shoppinglist | | X | YAML | |
| Bibliography | X | X | JSON | |
| News | X | X | JSON | |
| Image | X | X | JSON | |
| Movie | X | X | JSON | |
| Music | X | X | JSON | |
| Module | Indexer | Header-Format | Content | Expl | Dep |
| :----------- | ------- | ------------- | ------- | ------- | ----------- |
| Bibliography | X | JSON | X | .bib | Notes |
| Bookmarks | | JSON | | | |
| Calendar | X | JSON | | | |
| Contacts | X | JSON | | | |
| Image | X | JSON | | | Notes |
| Mail | X | JSON | | | |
| Movie | X | JSON | | | Notes |
| Music | X | JSON | | | Notes |
| News | X | JSON | | | |
| Notes | | YAML | X | Content | |
| Shoppinglist | | YAML | | | Notes, Todo |
| Todo | | YAML | | | Notes |
| Wiki | | YAML | X | Content | |
Explanation:
- An "Indexer" Module does only index some data and store the indexed meta
information in the store
- "Header" means that the header part of a store entry is used
- "Hdr-Format" Which format is chosen for the header. Basically: YAML if the
- An "Indexer" Module does only index some data and stores only some information
on how to content
- "Header-Format": Which format is chosen for the header. Basically: YAML if the
user might want to edit the header, otherwise JSON (pretty).
- "Content" means that the content part of a store entry is used
- "Expl": What the content part of a file is used for. "Content" means simply
user content.
- "Dep" means that the module uses this other module as a dependency for
extending its own functionality
### External Dependencies
| Library | Optional | Module |
| :------------ | :------: | :------- |
| vcard | | Contacts |
| icalendar | | Calendar |
| XDG | X | Contacts |
| | X | Calendar |
| | X | Notes |
| | X | Mail |
| | X | Wiki |
| | X | Todo |
| | X | Shopping List |
| | X | BibMan |
| | X | Music |
| | X | Movie |
| | X | Image |
| Markdown | | Notes |
| | | Wiki |
| Maildir | | Mail |
| BibTex parsing | X | BibMan |
| git-annex | X | BibMan |
| | X | Music |
| | X | Movie |
| | X | Image |
| Exif | X | Image |
| id3 | X | Music |
| RSS/Atom | | News |
(Optional means that these things are optional for the main functionality, but
should be implemented at some point to offer powerful functionality)
### Bookmarks
Bookmarks should be stored in a simple format:
```json
{ "URL": "https://github.com", "tags": ["ducks", "r", "great"]}
{ "URL": "https://github.com", "TAGS": ["ducks", "r", "great"]}
```
| Required core feature | Purpose |
| Required util feature | Purpose |
| :------------------------------------ | :------------ |
| XDG-open (Browser) | External program |
@ -131,29 +154,15 @@ Bookmarks should be stored in a simple format:
Contacts are just read and indexed by `imag`, to create an internal index of
them and to be able to refer to.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| vcard file format parsing | Data access |
| XDG-open (Mail program) | External Program |
### Calendar
Calendar are just read and indexed by `imag`, to create an internal index of
them and to be able to refer to.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| ical file format parsing | Data access |
### Notes
Just plain text notes.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Linking to other store entries | Data Link |
| XDG-open (Editor) | External Program |
### Mail
`imag` should be able to index all your mail and make them accessible through
@ -163,26 +172,12 @@ notes, etc etc. to your mail.
Some of these things (like linking contacts, calendar entries) should be
linked automatically.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Parser: Maildir | Data access |
| Parser: mbox | Data access |
| XDG-open (Mail program) | External Program |
| XDG-open (Editor) | External Program |
### Personal Wiki
`imag` should contain a complete personal wiki software. It should contain of
simple markdown files which have a YAML header, nothing too special for the
first step.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Parser: Markdown | Data parsing |
| XDG-open (Editor) | External Program |
| XDG-open (Mail program) | External Program |
| XDG-open (Browser) | External Program |
Some more ideas:
- Extract URLs and put them into store as Bookmarks
@ -192,21 +187,9 @@ Some more ideas:
`imag` should also contain a full todo-tool. I'm thinking about integrating
taskwarrior through a wrapper here.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Library: taskwarrior | Implementation |
| XDG-open (Editor) | External Program |
### Shoppinglist
Simply dot-and-tick lists.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Module: Todo-List | Implementation |
| XDG-open (Editor) | External Program |
- Sub-form of the Todo-List module
Simply dot-and-tick lists, uses Todo and Notes in combination.
### Bibliography management
@ -214,53 +197,22 @@ BibTex would be the first step, maybe we should be able to add the actual PDFs
here as well... didn't waste too much thoughts on this by now. If we have the
PDF data, we need git-annex.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| BibTex parsing | Data parsing |
| Backend: git-annex | Data parsing |
| XDG-open (Editor) | External Program |
| XDG-open (PDF viewer) | External Program |
| XDG-open (Office suite) | External Program |
### News (RSS)
Just indexing, reading news is not task of `imag` and so isn't syncing.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Parser: RSS | Data parsing |
| Parser: Atom | Data parsing |
### Image
Just indexing photos.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Backend: git-annex | Data parsing |
| Image metadata reading | Data parsing |
| XDG-open (Image viewer) | External Program |
### Video
Just indexing movies.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Backend: git-annex | Data parsing |
| Movie metadata reading | Data parsing |
| XDG-open (Movie viewer) | External Program |
### Music
Just indexing music.
| Required core feature | Purpose |
| :------------------------------------ | :------------ |
| Backend: git-annex | Data parsing |
| Music metadata reading | Data parsing |
| XDG-open (Music player) | External Program |
# License
This code is released under terms of GNU GPLv2.

View file

@ -28,18 +28,32 @@ impl<'a> CliConfig<'a> {
}
}
/**
* Check whether the CLI says we should run verbose
*/
pub fn is_verbose(&self) -> bool {
self.cli_matches.is_present("verbose") || self.is_debugging()
}
/**
* Check whether the CLI says we should run in debugging
*/
pub fn is_debugging(&self) -> bool {
self.cli_matches.is_present("debug")
}
/**
* Get the runtime path the CLI configured
*/
pub fn get_rtp(&self) -> Option<String> {
self.cli_matches.value_of("rtp").and_then(|s| Some(String::from(s)))
}
/**
* Get the store path the CLI configured
*
* TODO: Implement properly. Not working by now.
*/
pub fn store_path(&self) -> Option<String> {
self.get_rtp().and_then(|rtp| {
self.cli_matches

View file

@ -5,6 +5,15 @@ use config::reader::from_file;
use config::types::Config as Cfg;
use cli::CliConfig;
/**
* Configuration object which represents the configuration file.
*
* It gets passed a CliConfig object on ::new(), retreives some data from this one which is then
* provided as default value to the callee if there is no value for it in the configuration.
*
* TODO: Setup is kinda ugly, as we re-use data from the CLI, which is the job of the Runtime
* object later.
*/
pub struct Configuration {
pub rtp : String,
pub store_sub : String,
@ -63,18 +72,30 @@ impl Configuration {
}
}
/**
* Check whether the configuration says we should run verbose
*/
pub fn is_verbose(&self) -> bool {
self.verbose
}
/**
* Check whether the configuration says we should run in debugging
*/
pub fn is_debugging(&self) -> bool {
self.debugging
}
/**
* Get the store path the configuration configured
*/
pub fn store_path(&self) -> String {
format!("{}{}", self.rtp, self.store_sub)
}
/**
* Get the runtime path the configuration configured
*/
pub fn get_rtp(&self) -> String {
self.rtp.clone()
}
@ -89,6 +110,9 @@ impl Configuration {
}
/**
* Helper to get the runtimepath from the CLI
*/
fn rtp_path(config: &CliConfig) -> Option<String> {
config.cli_matches.value_of("rtp")
.and_then(|s| Some(String::from(s)))
@ -98,6 +122,9 @@ fn fetch_config(rtp: Option<String>) -> Option<Cfg> {
rtp.and_then(|r| from_file(Path::new(&(r.clone() + "/config"))).ok())
}
/**
* Default runtime path, if available.
*/
fn default_path() -> Option<String> {
use std::env::home_dir;

View file

@ -11,21 +11,21 @@ extern crate config;
use std::process::exit;
use cli::CliConfig;
use configuration::Configuration;
use runtime::{ImagLogger, Runtime};
use clap::App;
use module::Module;
pub use cli::CliConfig;
pub use configuration::Configuration;
pub use runtime::{ImagLogger, Runtime};
pub use clap::App;
pub use module::Module;
mod cli;
mod configuration;
mod runtime;
mod module;
mod storage;
mod ui;
mod util;
pub mod cli;
pub mod configuration;
pub mod runtime;
pub mod module;
pub mod storage;
pub mod ui;
pub mod util;
use module::bm::BM;
pub use module::bm::BM;
fn main() {
let yaml = load_yaml!("../etc/cli.yml");

View file

@ -40,6 +40,9 @@ impl<'a> BM<'a> {
&self.rt
}
/**
* Subcommand: add
*/
fn command_add(&self, matches: &ArgMatches) -> bool {
use std::process::exit;
use self::header::build_header;
@ -100,6 +103,9 @@ impl<'a> BM<'a> {
return true;
}
/**
* Subcommand: list
*/
fn command_list(&self, matches: &ArgMatches) -> bool {
use ui::file::{FilePrinter, TablePrinter};
use std::ops::Deref;
@ -124,6 +130,9 @@ impl<'a> BM<'a> {
true
}
/**
* Subcommand: remove
*/
fn command_remove(&self, matches: &ArgMatches) -> bool {
use std::process::exit;
@ -152,6 +161,9 @@ impl<'a> BM<'a> {
return result;
}
/**
* Subcommand: add_tags
*/
fn command_add_tags(&self, matches: &ArgMatches) -> bool {
self.alter_tags_in_files(matches, |old_tags, cli_tags| {
let mut new_tags = old_tags.clone();
@ -160,6 +172,9 @@ impl<'a> BM<'a> {
})
}
/**
* Subcommand: rm_tags
*/
fn command_rm_tags(&self, matches: &ArgMatches) -> bool {
self.alter_tags_in_files(matches, |old_tags, cli_tags| {
old_tags.clone()
@ -169,12 +184,18 @@ impl<'a> BM<'a> {
})
}
/**
* Subcommand: set_tags
*/
fn command_set_tags(&self, matches: &ArgMatches) -> bool {
self.alter_tags_in_files(matches, |old_tags, cli_tags| {
cli_tags.clone()
})
}
/**
* Helper function to alter the tags in a file
*/
fn alter_tags_in_files<F>(&self, matches: &ArgMatches, generate_new_tags: F) -> bool
where F: Fn(Vec<String>, &Vec<String>) -> Vec<String>
{
@ -229,7 +250,10 @@ impl<'a> BM<'a> {
.all(|x| x)
}
/**
* Helper function to get files from the store filtered by the constraints passed via the
* CLI
*/
fn get_files(&self,
matches: &ArgMatches,
id_key: &'static str,
@ -257,6 +281,9 @@ impl<'a> BM<'a> {
}
}
/**
* Get files from the store, filtere by ID
*/
fn get_files_by_id(&self, hash: FileHash) -> Vec<Rc<RefCell<File>>> {
let parser = Parser::new(JsonHeaderParser::new(None));
self.rt
@ -266,6 +293,9 @@ impl<'a> BM<'a> {
.unwrap_or(vec![])
}
/**
* Get files from the store, filtere by Regex
*/
fn get_files_by_match(&self, matcher: String) -> Vec<Rc<RefCell<File>>> {
let parser = Parser::new(JsonHeaderParser::new(None));
let re = Regex::new(&matcher[..]).unwrap_or_else(|e| {
@ -292,6 +322,9 @@ impl<'a> BM<'a> {
.collect()
}
/**
* Get files from the store, filtere by tags
*/
fn get_files_by_tags(&self, tags: Vec<String>) -> Vec<Rc<RefCell<File>>> {
let parser = Parser::new(JsonHeaderParser::new(None));
self.rt
@ -309,6 +342,9 @@ impl<'a> BM<'a> {
}
/**
* Trait implementation for BM module
*/
impl<'a> Module<'a> for BM<'a> {
fn exec(&self, matches: &ArgMatches) -> bool {

View file

@ -1,13 +1,23 @@
/*
/*!
* Helpers for headers
*/
pub mod tags;
/**
* Utility helpers for header data
*/
pub mod data {
use std::ops::Deref;
use storage::file::header::data::FileHeaderData as FHD;
/**
* Get an URL from a header, whereas the header has to have the following format:
*
* { ..., "URL": "<URL>", ... }
*
* Does no spec verification.
*/
pub fn get_url_from_header(header: &FHD) -> Option<String> {
match header {
&FHD::Map{keys: ref ks} => {

View file

@ -1,30 +1,61 @@
/*
/*!
* Helpers for headers - Tags
*/
/**
* Spec helpers for header-tags
*/
pub mod spec {
use storage::file::header::spec::FileHeaderSpec as FHS;
use module::helpers::spec::{named_text, named_text_array};
/**
* helper for a Header spec for
*
* { "URL": "<Text>" }
*/
pub fn url_key() -> FHS {
named_text("URL")
}
/**
* helper for a Header spec for
*
* { "TAGS": [ "<Text>", ... ] }
*/
pub fn tags_key() -> FHS {
named_text_array("TAGS")
}
}
/**
* Data helpers for header-tags
*/
pub mod data {
use std::ops::Deref;
use storage::file::header::data::FileHeaderData as FHD;
/**
* Use a Vec<String> to build a Tag-Array:
*
* [ "<Text>", ... ]
*/
pub fn build_tag_array(tags: Vec<String>) -> FHD {
let texttags = tags.into_iter().map(|t| FHD::Text(t.clone())).collect();
FHD::Array { values: Box::new(texttags) }
}
/**
* Fetch tags from a header, whereas the header looks like this:
*
* { ...,
* "TAGS": [ "<Text>", ... ],
* ...
* }
*
* Does no spec verification.
*/
pub fn get_tags_from_header(header: &FHD) -> Vec<String> {
let mut tags : Vec<String> = vec![];

View file

@ -1,17 +1,39 @@
/*!
* Utility helpers for modules
*/
pub mod header;
pub mod utils;
/**
* Helpers for header specs
*/
pub mod spec {
use storage::file::header::spec::FileHeaderSpec as FHS;
/**
* Helper to get a spec for a Key-Value for a named text:
*
* { '<name>': "<Text>" }
*/
pub fn named_text(name: &str) -> FHS {
FHS::Key { name: String::from(name), value_type: Box::new(FHS::Text) }
}
/**
* Helper to get a spec for a Key-Value for a named array:
*
* { '<name>': [ "<Text>", ...] }
*/
pub fn named_text_array(name: &str) -> FHS {
FHS::Key { name: String::from(name), value_type: Box::new(text_array()) }
}
/**
* Helper to get a spec for Array<Text>:
*
* [ "<Text>", ...]
*/
pub fn text_array() -> FHS {
FHS::Array { allowed_types: vec![FHS::Text] }
}

View file

@ -1,9 +1,16 @@
/**
* Utility helpers for CLI
*/
pub mod cli {
use clap::ArgMatches;
use regex::Regex;
use runtime::Runtime;
/**
* Get a commandline option "tags" and split the argument by "," to be able to provide a
* Vec<String> with the argument as array.
*/
pub fn get_tags<'a>(rt: &Runtime, sub: &ArgMatches<'a, 'a>) -> Vec<String> {
fn reject_if_with_spaces(e: &String) -> bool {

View file

@ -11,6 +11,9 @@ use runtime::Runtime;
pub mod bm;
pub mod helpers;
/**
* Module interface, each module has to implement this.
*/
pub trait Module<'a> : Debug {
fn exec(&self, matches: &ArgMatches) -> bool;
fn name(&self) -> &'static str;

View file

@ -51,6 +51,11 @@ impl log::Log for ImagLogger {
}
}
/**
* Runtime object, represents a single interface to both the CLI configuration and the
* configuration file. Also carries the store object around and is basically an object which
* contains everything which is required to run a command/module.
*/
pub struct Runtime<'a> {
pub config : CliConfig<'a>,
pub configuration : Cfg,
@ -68,22 +73,37 @@ impl<'a> Runtime<'a> {
}
}
/**
* Check whether we run verbose
*/
pub fn is_verbose(&self) -> bool {
self.config.is_verbose() || self.configuration.is_verbose()
}
/**
* Check whether we run in debugging
*/
pub fn is_debugging(&self) -> bool {
self.config.is_debugging() || self.configuration.is_verbose()
}
/**
* Get the store path we are currently using
*/
pub fn store_path(&self) -> String {
self.config.store_path().unwrap_or(self.configuration.store_path())
}
/**
* Get the store object
*/
pub fn store(&self) -> &Store {
&self.store
}
/**
* Get the runtime path we are currently using
*/
pub fn get_rtp(&self) -> String {
if let Some(rtp) = self.config.get_rtp() {
rtp

View file

@ -10,6 +10,11 @@ use uuid::Uuid;
#[derive(PartialEq)]
#[derive(Eq)]
#[derive(Hash)]
/**
* FileHash type
*
* Simple abstraction over String by now.
*/
pub struct FileHash {
hash: String,
}

View file

@ -16,6 +16,15 @@ use storage::file::hash::FileHash;
#[derive(Hash)]
#[derive(Eq)]
#[derive(PartialEq)]
/**
* FileID
*
* A FileID contains of two parts: The ID type and the Hash. For example the FileID
*
* UUID-235-1215-1212
*
* has a type ("UUID") and a Hash ("235-1215-1212").
*/
pub struct FileID {
id: FileHash,
id_type: FileIDType,
@ -30,14 +39,23 @@ impl FileID {
}
}
/**
* Get the type of the FileID
*/
pub fn get_type(&self) -> FileIDType {
self.id_type.clone()
}
/**
* Get the Hash of the FileID
*/
pub fn get_id(&self) -> FileHash {
self.id.clone()
}
/**
* Parse a String into a FileID, if possible
*/
pub fn parse(string: &String) -> Option<FileID> {
// we assume that it is an path
let regex = Regex::new(r"([:alnum:]*)-([:upper:]*)-([A-Za-z0-9-_]*)\.(.*)").unwrap();

View file

@ -9,6 +9,11 @@ use std::hash::Hash;
#[derive(Eq)]
// #[derive(Display)]
#[derive(Hash)]
/**
* File ID type
*
* Currently only UUID is available. Maybe this will be the only type available at all.
*/
pub enum FileIDType {
UUID,
}

View file

@ -19,43 +19,71 @@ use super::parser::{FileHeaderParser, Parser, ParserError};
use self::header::spec::*;
use self::header::data::*;
/*
* Internal abstract view on a file. Does not exist on the FS and is just kept
/**
* Internal abstract view on a file. Does not neccessarily exist on the FS and is just kept
* internally until it is written to disk.
*/
pub struct File {
/// The name of the module which owns this file
pub owning_module_name : &'static str,
/// The header of the file
pub header : FileHeaderData,
/// The content part of the file
pub data : String,
/// The ID of the file
pub id : FileID,
}
impl File {
/**
* Get the owner module name of the file
*/
pub fn owner_name(&self) -> &'static str {
self.owning_module_name
}
/**
* Get the header of the file
*/
pub fn header(&self) -> &FileHeaderData {
&self.header
}
/**
* Set the header of the file
*/
pub fn set_header(&mut self, new_header: FileHeaderData) {
self.header = new_header;
}
/**
* Set the data of the file
*/
pub fn data(&self) -> &String {
&self.data
}
/**
* Set the (header, data) of the file
*/
pub fn contents(&self) -> (&FileHeaderData, &String) {
(self.header(), self.data())
}
/**
* Set the id of the file
*/
pub fn id(&self) -> &FileID {
&self.id
}
/**
* Check whether the header or the data of the file match some regex
*/
pub fn matches_with(&self, r: &Regex) -> bool {
r.is_match(&self.data[..]) || self.header.matches_with(r)
}

View file

@ -27,6 +27,12 @@ pub struct Store {
cache : RefCell<Cache>,
}
/**
* Store object
*
* This object is an abstraction layer over FS and an interface to the object store of this
* software.
*/
impl Store {
pub fn new(storepath: String) -> Store {
@ -36,12 +42,20 @@ impl Store {
}
}
/**
* Put a file into the cache
*/
fn put_in_cache(&self, f: File) -> FileID {
let res = f.id().clone();
self.cache.borrow_mut().insert(f.id().clone(), Rc::new(RefCell::new(f)));
res
}
/**
* Load a file by ID into the cache and return it afterwards
*
* Returns None if the file could be loaded from the Filesystem
*/
pub fn load_in_cache<HP>(&self, m: &Module, parser: &Parser<HP>, id: FileID)
-> Option<Rc<RefCell<File>>>
where HP: FileHeaderParser
@ -63,6 +77,11 @@ impl Store {
self.load(&id)
}
/**
* Generate a new file for a module.
*
* Returns the new FileID object then
*/
pub fn new_file(&self, module: &Module)
-> FileID
{
@ -77,6 +96,11 @@ impl Store {
self.put_in_cache(f)
}
/**
* Generate a new file from a parser result.
*
* @deprecated This function shouldn't be needed anymore
*/
pub fn new_file_from_parser_result(&self,
module: &Module,
id: FileID,
@ -94,6 +118,11 @@ impl Store {
self.put_in_cache(f)
}
/**
* Generate a new file for a module, providing some header data
*
* Returns the new FileID object then
*/
pub fn new_file_with_header(&self,
module: &Module,
h: FileHeaderData)
@ -109,6 +138,11 @@ impl Store {
self.put_in_cache(f)
}
/**
* Generate a new file for a module, providing some initial data
*
* Returns the new FileID object then
*/
pub fn new_file_with_data(&self, module: &Module, d: String)
-> FileID
{
@ -122,6 +156,12 @@ impl Store {
self.put_in_cache(f)
}
/**
* Generate a new file for a module, providing some initial data and some header
*
* Returns the new FileID object then
*/
pub fn new_file_with_content(&self,
module: &Module,
h: FileHeaderData,
@ -138,6 +178,11 @@ impl Store {
self.put_in_cache(f)
}
/**
* Persist a File on the filesystem
*
* Returns true if this worked
*/
pub fn persist<HP>(&self,
p: &Parser<HP>,
f: Rc<RefCell<File>>) -> bool
@ -162,8 +207,15 @@ impl Store {
}).map_err(|writeerr| {
debug!("Could not create file at '{}'", path);
}).and(Ok(true)).unwrap()
// TODO: Is this unwrap() save?
}
/**
* Helper to generate the store path
*
* Kills the program if it fails
*/
fn ensure_store_path_exists(&self) {
use std::fs::create_dir_all;
use std::process::exit;
@ -176,11 +228,20 @@ impl Store {
})
}
/**
* Load a file from the cache by FileID
*
* TODO: Semantics: This function should load from FS if the file is not in the cache yet or
* fail if the file is not available.
*/
pub fn load(&self, id: &FileID) -> Option<Rc<RefCell<File>>> {
debug!("Loading '{:?}'", id);
self.cache.borrow().get(id).cloned()
}
/**
* Load a file from the filesystem/cache by a FileHash
*/
pub fn load_by_hash<HP>(&self,
m: &Module,
parser: &Parser<HP>,
@ -232,6 +293,11 @@ impl Store {
}).unwrap_or(None)
}
/**
* Remove a file from the filesystem by FileID
*
* Returns true if this works.
*/
pub fn remove(&self, id: FileID) -> bool {
use std::fs::remove_file;
@ -250,6 +316,9 @@ impl Store {
.unwrap_or(false)
}
/**
* Load all files for a module
*/
pub fn load_for_module<HP>(&self, m: &Module, parser: &Parser<HP>)
-> Vec<Rc<RefCell<File>>>
where HP: FileHeaderParser
@ -276,6 +345,9 @@ impl Store {
res
}
/**
* Helper to generate a new FileID object
*/
fn get_new_file_id(&self) -> FileID {
use uuid::Uuid;
let hash = FileHash::from(Uuid::new_v4().to_hyphenated_string());

View file

@ -83,11 +83,26 @@ impl Display for ParserError {
}
/**
* Trait for a header parser.
*
* This parser type has to provide two functions:
* - read(), which reads an String into a FileHeaderData structure
* - write(), which parses a FileHeaderData structure into a String
*
* TODO: Use Write/Read traits?
*/
pub trait FileHeaderParser : Sized + Debug + Display {
fn read(&self, string: Option<String>) -> Result<FileHeaderData, ParserError>;
fn write(&self, data: &FileHeaderData) -> Result<String, ParserError>;
}
/**
* Parser
*
* This Parser object is an abstraction which uses the FileHeaderParser to parse the whole contents
* of a file into a header (FileHeaderData) structure and the content (String).
*/
pub struct Parser<HP> {
headerp : HP,
}
@ -100,6 +115,10 @@ impl<HP: FileHeaderParser> Parser<HP> {
}
}
/**
* Read the String which is the contents of a file into a (FileHeaderData, String) tuple, which
* is the header and the content of the file.
*/
pub fn read(&self, s: String) -> Result<(FileHeaderData, String), ParserError> {
debug!("Reading into internal datastructure: '{}'", s);
let divided = self.divide_text(&s);
@ -122,6 +141,10 @@ impl<HP: FileHeaderParser> Parser<HP> {
Ok((h_parseres, data.unwrap_or(String::new())))
}
/**
* Write the FileHeaderData and String (header and content) of the tuple into a String, which
* can then simply be written into the store as a file.
*/
pub fn write(&self, tpl : (&FileHeaderData, &String)) -> Result<String, ParserError> {
debug!("Parsing internal datastructure to String");
let (header, data) = tpl;
@ -132,6 +155,10 @@ impl<HP: FileHeaderParser> Parser<HP> {
Ok(text)
}
/**
* Helper to parse the full-text of a file into a header part (String) and a content part
* (String)
*/
fn divide_text(&self, text: &String) -> Result<(Option<String>, Option<String>), ParserError> {
let re = Regex::new(r"(?sm)^---$(.*)^---$(.*)").unwrap();

View file

@ -5,6 +5,9 @@ use std::ops::Deref;
use storage::file::File;
/**
* Trait for a printer which can be used to print data from files
*/
pub trait FilePrinter {
fn new(verbose: bool, debug: bool) -> Self;
@ -40,6 +43,9 @@ pub trait FilePrinter {
}
/**
* Printer which prints in debug mode if enabled
*/
struct DebugPrinter {
debug: bool,
}
@ -68,6 +74,9 @@ impl FilePrinter for DebugPrinter {
}
/**
* Simple printer, which just uses the info!() macro or debug!() macro if in debug mode.
*/
struct SimplePrinter {
verbose: bool,
debug: bool,
@ -107,6 +116,9 @@ impl FilePrinter for SimplePrinter {
}
/**
* Table printer to print file information in a nice ASCII-table
*/
pub struct TablePrinter {
verbose: bool,
debug: bool,

View file

@ -1,5 +1,8 @@
use url::Url;
/**
* Util: Check wether a String can be parsed as an URL
*/
pub fn is_url(url: &String) -> bool {
Url::parse(&url[..]).is_ok()
}