artifact-app 0.6.3

Artifact is a design doc tool made for developers. It allows anyone to easily write and link their design docs both to each other and to source code, making it easy to track how complete their project is. Documents are revision controllable, can be rendered as a static web page and have a full suite of command line tools for searching, formatting and displaying them.
Documentation

/*  artifact: the requirements tracking tool made for developers
 * Copyright (C) 2017  Garrett Berg <@vitiral, vitiral@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the Lesser GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * */
//! methods to format the `FmtArtifact` object and write it to a stream

use dev_prefix::*;
use super::types::*;

impl FmtArtifact {
    /// write the formatted version of the artifact to the
    /// cmdline writter
    ///
    /// #SPC-cmd-ls-color
    #[allow(cyclomatic_complexity)] // TODO: break this up
    pub fn write<W: io::Write>(&self,
                               w: &mut W,
                               cwd: &Path,
                               artifacts: &Artifacts,
                               color: bool,
                               indent: u8)
                               -> io::Result<()> {
        let nfno = indent > 0 && self.name_only(); // not-first-name-only
        if !self.name_only() {
            for _ in 0..(indent * 2) {
                try!(w.write_all(" ".as_ref()));
            }
        }
        trace!("formatting artifact: {}", self.name);
        let artifact = match artifacts.get(&self.name) {
            Some(a) => a,
            None => {
                // invalid partof value
                if color {
                    write!(w, "{}", Red.bold().blink().paint(self.name.raw.as_str())).unwrap();
                } else {
                    write!(w, "{}", self.name.raw).unwrap();
                }
                return Ok(());
            }
        };

        // format the completeness and name
        let completed_str = if artifact.completed < 0. {
            "-1".to_string()
        } else {
            ((artifact.completed * 100.) as u8).to_string()
        };
        let tested_str = if artifact.tested < 0. {
            "-1".to_string()
        } else {
            ((artifact.tested * 100.) as u8).to_string()
        };
        let completed_len = completed_str.len();
        let tested_len = tested_str.len();
        if color {
            let (d_sym, d_perc, t_sym, t_perc, name) = if artifact.completed >= 1. &&
                                                          artifact.tested >= 1. {
                let name = if nfno {
                    Green.paint(self.name.raw.as_str())
                } else {
                    Green.bold().underline().paint(self.name.raw.as_str())
                };
                (Green.bold().paint("D"),
                 Green.bold().paint(completed_str),
                 Green.bold().paint("T"),
                 Green.bold().paint(tested_str),
                 name)
            } else {
                let mut score = 0;
                let (d_sym, d_perc) = if artifact.completed >= 1. {
                    score += 3;
                    (Blue.bold().paint("D"), Blue.bold().paint(completed_str))
                } else if artifact.completed >= 0.7 {
                    score += 2;
                    (Yellow.bold().paint("-"), Yellow.bold().paint(completed_str))
                } else if artifact.completed >= 0.4 {
                    score += 1;
                    (Yellow.bold().paint("-"), Yellow.bold().paint(completed_str))
                } else if artifact.completed < 0. {
                    (Red.bold().blink().paint("!"), Red.bold().blink().paint(completed_str))
                } else {
                    (Red.bold().paint("-"), Red.bold().paint(completed_str))
                };
                let (t_sym, t_perc) = if artifact.tested >= 1. {
                    score += 2;
                    (Blue.bold().paint("T"), Blue.bold().paint(tested_str))
                } else if artifact.tested >= 0.5 {
                    score += 1;
                    (Yellow.bold().paint("-"), Yellow.bold().paint(tested_str))
                } else if artifact.tested < 0. {
                    (Red.bold().blink().paint("!"), Red.bold().blink().paint(tested_str))
                } else {
                    (Red.bold().paint("-"), Red.bold().paint(tested_str))
                };
                let name = match score {
                    3...4 => Blue,
                    1...2 => Yellow,
                    0 => Red,
                    _ => unreachable!(),
                };
                let sname = self.name.raw.as_str();
                let name = if nfno {
                    name.paint(sname)
                } else {
                    name.bold().underline().paint(sname)
                };
                (d_sym, d_perc, t_sym, t_perc, name)
            };
            if nfno {
                try!(write!(w, "{}", name));
            } else {
                try!(write!(w, "|{}{}| ", d_sym, t_sym));
                // format completed %
                for _ in 0..(3 - completed_len) {
                    try!(w.write_all(" ".as_ref()));
                }
                try!(write!(w, "{}% ", d_perc));
                // format tested %
                for _ in 0..(3 - tested_len) {
                    try!(w.write_all(" ".as_ref()));
                }
                try!(write!(w, "{}% ", t_perc));
                try!(write!(w, "| {} ", name));
            }
        } else if nfno {
            try!(write!(w, "{}", &self.name.raw));
        } else {
            let d_sym = if artifact.completed >= 1. { "D" } else { "-" };
            let t_sym = if artifact.tested >= 1. { "T" } else { "-" };
            try!(write!(w,
                        "|{}{}| {:>3}% {:>3}% | {}",
                        d_sym,
                        t_sym,
                        completed_str,
                        tested_str,
                        &self.name.raw));
        }

        if nfno {
            return Ok(());
        }

        // format the parts
        if let Some(ref parts) = self.parts {
            self.write_start(w, "\n * parts: ", color);
            for (n, p) in parts.iter().enumerate() {
                if self.long {
                    w.write_all("\n    ".as_ref()).unwrap();
                }
                try!(p.write(w, cwd, artifacts, color, indent + 1));
                if !self.long && n + 1 < parts.len() {
                    w.write_all(", ".as_ref()).unwrap();
                }
            }
        }

        // format the artifacts that are a partof this artifact
        if let Some(ref partof) = self.partof {
            self.write_start(w, "\n * partof: ", color);
            let mut first = true;
            for p in partof {
                if !first && p.name_only() {
                    try!(w.write_all(", ".as_ref()));
                }
                first = false;
                try!(p.write(w, cwd, artifacts, color, indent + 1));
            }
        }

        // format the location that where the implementation of this artifact can be found
        if let Some(ref done) = self.done {
            self.write_start(w, "\n * done: ", color);
            if color {
                try!(write!(w, "{}", Green.paint(done.as_ref())));
            } else {
                try!(w.write_all(done.as_ref()));
            }
            try!(w.write_all(" ".as_ref()));
        }

        // format where the artifact is defined
        if let Some(ref path) = self.path {
            self.write_start(w, "\n * defined-at: ", color);
            let path = utils::relative_path(path.as_path(), cwd);
            try!(write!(w, "{}", path.display()));
        }

        // format the text
        // TODO: use markdown to apply styles to the text
        if let Some(ref text) = self.text {
            self.write_start(w, "\n * text:\n    ", color);
            let lines: Vec<_> = text.split('\n').collect();
            let text = lines.join("\n    ");
            w.write_all(text.as_ref()).unwrap();
        }

        try!(w.write_all("\n".as_ref()));
        Ok(())
    }

    fn write_start<W: io::Write>(&self, w: &mut W, msg: &str, color: bool) {
        if self.long {
            if color {
                write!(w, "{}", Green.paint(msg)).unwrap();
            } else {
                w.write_all(msg.as_ref()).unwrap();
            }
        } else {
            w.write_all("\t| ".as_ref()).unwrap();
        }
    }

    /// return whether this object is only the name
    /// if it is, it is formatted differently
    fn name_only(&self) -> bool {
        match (&self.path, &self.parts, &self.partof, &self.done, &self.text) {
            (&None, &None, &None, &None, &None) => true,
            _ => false,
        }
    }
}

pub fn write_table_header<W: io::Write>(w: &mut W, fmt_set: &FmtSettings) {
    let mut header = String::new();
    header.write_str("|  | DONE TEST | NAME").unwrap();
    if fmt_set.parts {
        header.write_str("\t| PARTS   ").unwrap();
    }
    if fmt_set.partof {
        header.write_str("\t| PARTOF   ").unwrap();
    }
    if fmt_set.loc_path {
        header.write_str("\t| IMPLEMENTED   ").unwrap();
    }
    if fmt_set.path {
        header.write_str("\t| DEFINED   ").unwrap();
    }
    if fmt_set.text {
        header.write_str("\t| TEXT").unwrap();
    }
    header.push('\n');
    if fmt_set.color {
        write!(w, "{}", Style::new().bold().paint(header)).unwrap();
    } else {
        write!(w, "{}", header).unwrap();
    }
}