1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
#![allow(unused_doc_comment)]

use dev_prefix::*;

pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
/// variable which can be used in settings path to mean the repo directory
pub const REPO_VAR: &'static str = "repo";
/// variable which can be used in settings paths to mean the dir of the settings file.
/// #TODO: remove this
pub const CWD_VAR: &'static str = "cwd";
/// base definition of a valid name. Some pieces may ignore case.
pub const NAME_VALID_STR: &'static str = "(?:REQ|SPC|TST)(?:-[A-Z0-9_-]*[A-Z0-9_])?";

lazy_static!{
    // must start with artifact type, followed by "-", followed by at least 1 valid character
    // cannot end with "-"
    pub static ref NAME_VALID: Regex = Regex::new(
        &format!("^{}$", NAME_VALID_STR)).unwrap();
    pub static ref REPO_DIR: PathBuf = PathBuf::from(".art");
    pub static ref SETTINGS_PATH: PathBuf = REPO_DIR.join("settings.toml");
}

error_chain! {
    types {
        Error, ErrorKind, ResultExt, Result;
    }

    links {
        // no external error chains (yet)
    }

    foreign_links {
        // stdlib
        Io(::std::io::Error);
        Fmt(::std::fmt::Error);

        // crates
        StrFmt(::strfmt::FmtError);
        TomlError(::toml::de::Error);
    }

    errors {
        // Loading errors
        Load(desc: String) {
            description("Misc error while loading artifacts")
            display("Error loading: {}", desc)
        }
        TomlParse(locs: String) {
            description("Error while parsing TOML file")
            display("Error parsing TOML: {}", locs)
        }
        MissingTable {
            description("Must contain a single table")
        }
        InvalidName(desc: String) {
            description("Invalid artifact name")
            display("Invalid artifact name: \"{}\"", desc)
        }
        InvalidAttr(name: String, attr: String) {
            description("Artifact has invalid attribute")
            display("Artifact {} has invalid attribute: {}", name, attr)
        }
        InvalidSettings(desc: String) {
            description("Invalid settings")
            display("Invalid settings: {}", desc)
        }
        InvalidArtifact(name: String, desc: String) {
            description("Invalid artifact")
            display("Artifact {} is invalid: {}", name, desc)
        }
        MissingParent(name: String, parent: String) {
            description("Missing parent artifact")
            display("Parent {} does not exist for {}", parent, name)
        }
        // Processing errors
        InvalidTextVariables {
            description("Couldn't resolve some text variables")
        }
        InvalidPartof {
            description("Some artifacts have invalid partof attributes")
        }
        InvalidDone {
            description("Some artifacts have invalid partof attributes")
        }
        NameNotFound(desc: String) {
            description("Searched for names were not found")
            display("The following artifacts do not exists: {}", desc)
        }
        LocNotFound {
            description("Errors while finding implementation locations")
        }
        DoneTwice(desc: String) {
            description("The artifact is done and implemented in code")
            display("Referenced in code and `done` is set: {}", desc)
        }
        InvalidUnicode(path: String) {
            description("We do not yet support non-unicode paths")
            display("Invalid unicode in path: {}", path)
        }

        // Cmd errors
        CmdError(desc: String) {
            description("Error while running a command")
            display("{}", desc)
        }

        // Misc errors
        PathNotFound(desc: String) {
            description("Invalid path")
            display("Path does not exist: {}", desc)
        }
        NotEqual(desc: String) {
            description("Values not equal")
            display("{}", desc)
        }
        Security(desc: String) {
            description("Security vulnerability detected")
            display("Security vulnerability: {}", desc)
        }
        Internal(desc: String) {
            description("Internal error")
            display("Internal error: {}", desc)
        }
        NothingDone {
            description("Internal control flow")
        }
    }
}

/// our `from_str` can throw errors
pub trait LoadFromStr: Sized {
    fn from_str(s: &str) -> Result<Self>;
}

/// Artifacts organized by name
pub type Artifacts = HashMap<NameRc, Artifact>;
/// Names in a `HashSet` for fast lookup
pub type Names = HashSet<NameRc>;
pub type NameRc = Arc<Name>;

/// represents the results and all the data necessary
/// to reconstruct a loaded project
#[derive(Debug, Clone)]
pub struct Project {
    pub artifacts: Artifacts,
    pub settings: Settings,
    pub files: HashSet<PathBuf>,
    pub dne_locs: HashMap<Name, Loc>,

    // preserved locations where each piece is from
    pub origin: PathBuf,
    pub repo_map: HashMap<PathBuf, PathBuf>,
}

impl Default for Project {
    fn default() -> Project {
        Project {
            artifacts: Artifacts::default(),
            settings: Settings::default(),
            files: HashSet::default(),
            dne_locs: HashMap::default(),
            origin: PathBuf::default(),
            repo_map: HashMap::default(),
        }
    }
}

/// Definition of an artifact name, with Traits for hashing,
/// displaying, etc
// note: methods are implemented in name.rs
#[derive(Clone)]
pub struct Name {
    /// user definition
    pub raw: String,
    /// standardized version
    pub value: Vec<String>,
    /// the inferred type of the artifact
    pub ty: Type,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
/// type of an `Artifact`
pub enum Type {
    REQ,
    SPC,
    TST,
}

/// location in a file
#[derive(Debug, Clone, PartialEq)]
pub struct Loc {
    pub path: PathBuf,
    pub line: usize,
}

#[cfg(test)]
impl Loc {
    pub fn fake() -> Loc {
        Loc {
            path: Path::new("fake").to_path_buf(),
            line: 42,
        }
    }
}

impl fmt::Display for Loc {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}[{}]", self.path.display(), self.line)
    }
}

/// Determines if the artifact is "done by definition"
///
/// It is done by definition if:
/// - it is found in source code
/// - it has it's `done` field set
#[derive(Debug, Clone, PartialEq)]
pub enum Done {
    /// Artifact is implemented in code
    Code(Loc),
    /// artifact has it's `done` field defined
    Defined(String),
    /// artifact is NOT "done by definition"
    NotDone,
}

impl Done {
    /// return true if Done == Code || Defined
    pub fn is_done(&self) -> bool {
        match *self {
            Done::Code(_) | Done::Defined(_) => true,
            Done::NotDone => false,
        }
    }
}

impl fmt::Display for Done {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Done::Code(ref c) => write!(f, "{}", c),
            Done::Defined(ref s) => write!(f, "{}", s),
            Done::NotDone => write!(f, "not done"),
        }
    }
}

/// The Artifact type. This encapsulates
/// REQ, SPC, and TST artifacts and
/// contains space to link them
/// #SPC-artifact
#[derive(Clone, Debug, PartialEq)]
pub struct Artifact {
    /// constant id for this instance
    pub id: u64,
    /// revision id for edit functionality
    pub revision: u64,
    /// path of definition (.toml file)
    pub def: PathBuf,
    /// `text` attr
    pub text: String,
    /// explicit and calculated `partof` attribute
    pub partof: Names,
    /// parts is inverse of partof (calculated)
    pub parts: Names,
    /// `done` attribute, allows user to "define as done"
    pub done: Done,
    /// completed ratio (calculated)
    pub completed: f32,
    /// tested ratio (calculated)
    pub tested: f32,
}

/// repo settings for loading artifacts
/// #SPC-project-settings
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Settings {
    pub artifact_paths: HashSet<PathBuf>,
    pub exclude_artifact_paths: HashSet<PathBuf>,
    pub code_paths: HashSet<PathBuf>,
    pub exclude_code_paths: HashSet<PathBuf>,
}

impl Settings {
    pub fn new() -> Settings {
        Settings {
            artifact_paths: HashSet::new(),
            exclude_artifact_paths: HashSet::new(),
            code_paths: HashSet::new(),
            exclude_code_paths: HashSet::new(),
        }
    }
}

#[derive(Debug, Default, Clone, Serialize)]
/// struct that is passed to the api server
pub struct ServeCmd {
    pub addr: String,
    pub readonly: bool,
    pub path_url: String,
}