superconductor 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.scss-lint.yml +21 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/Cargo.lock +679 -0
- data/Cargo.toml +25 -0
- data/Gemfile +4 -0
- data/Guardfile +70 -0
- data/LICENSE.txt +21 -0
- data/Makefile +4 -0
- data/README.md +58 -0
- data/Rakefile +9 -36
- data/assets/__pm.js +424 -0
- data/assets/__pm.scss +261 -0
- data/assets/_buttons.scss +50 -0
- data/assets/_checkbox.scss +59 -0
- data/assets/_colors.scss +15 -0
- data/assets/_details.scss +54 -0
- data/assets/_pm_commits.scss +171 -0
- data/assets/_pm_setup.scss +7 -0
- data/assets/_pm_tasks.scss +280 -0
- data/assets/_tokens.scss +56 -0
- data/bin/console +14 -0
- data/bin/make +2 -0
- data/bin/rake +17 -0
- data/bin/release +5 -0
- data/bin/setup +8 -0
- data/bin/start +5 -0
- data/config.ru +10 -0
- data/config/properties.yml +37 -0
- data/extconf.rb +2 -0
- data/lib/superconductor.rb +35 -1
- data/lib/superconductor/documentation.rb +34 -0
- data/lib/superconductor/middleware.rb +71 -0
- data/lib/superconductor/version.rb +1 -1
- data/src/lib.rs +101 -0
- data/src/server.rs +176 -0
- data/src/state/mod.rs +5 -0
- data/src/state/state.rs +233 -0
- data/src/state/xml.rs +380 -0
- data/src/task.rs +129 -0
- data/src/view.rs +396 -0
- data/superconductor.gemspec +41 -0
- metadata +173 -116
- data/MIT-LICENSE +0 -20
- data/README.rdoc +0 -3
- data/app/assets/javascripts/jquery-linedtextarea.js +0 -126
- data/app/assets/javascripts/superconductor.js +0 -2
- data/app/assets/javascripts/superconductor/panel.js.coffee +0 -42
- data/app/assets/stylesheets/jquery-linedtextarea.css +0 -68
- data/app/assets/stylesheets/scaffold.css +0 -56
- data/app/assets/stylesheets/superconductor.css +0 -4
- data/app/assets/stylesheets/superconductor/panel.css.scss +0 -142
- data/app/controllers/file_controller.rb +0 -24
- data/app/helpers/superconductor/panel_helper.rb +0 -2
- data/app/models/superconductor.rb +0 -5
- data/app/models/superconductor/exception.rb +0 -0
- data/app/views/superconductor/_panel.html.erb +0 -130
- data/app/views/superconductor/_panel.js.erb +0 -0
- data/config/routes.rb +0 -6
- data/lib/superconductor/engine.rb +0 -24
- data/lib/tasks/superconductor_tasks.rake +0 -4
- data/test/dummy/README.rdoc +0 -261
- data/test/dummy/Rakefile +0 -7
- data/test/dummy/app/assets/javascripts/application.js +0 -15
- data/test/dummy/app/assets/stylesheets/application.css +0 -13
- data/test/dummy/app/controllers/application_controller.rb +0 -3
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -56
- data/test/dummy/config/boot.rb +0 -10
- data/test/dummy/config/database.yml +0 -25
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -37
- data/test/dummy/config/environments/production.rb +0 -67
- data/test/dummy/config/environments/test.rb +0 -37
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/inflections.rb +0 -15
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/secret_token.rb +0 -7
- data/test/dummy/config/initializers/session_store.rb +0 -8
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -5
- data/test/dummy/config/routes.rb +0 -58
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -25
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +0 -6
- data/test/integration/navigation_test.rb +0 -10
- data/test/superconductor_test.rb +0 -7
- data/test/test_helper.rb +0 -10
- data/test/unit/helpers/superconductor/panel_helper_test.rb +0 -4
data/src/state/xml.rs
ADDED
@@ -0,0 +1,380 @@
|
|
1
|
+
use state::State;
|
2
|
+
use task::Task;
|
3
|
+
|
4
|
+
use std::cell::RefCell;
|
5
|
+
use std::fs::File;
|
6
|
+
use std::io::prelude::*;
|
7
|
+
use std::env;
|
8
|
+
use termion::color;
|
9
|
+
|
10
|
+
extern crate git2;
|
11
|
+
use self::git2::Repository;
|
12
|
+
use self::git2::StatusOptions;
|
13
|
+
use self::git2::Delta;
|
14
|
+
use self::git2::BranchType;
|
15
|
+
use self::git2::{Diff, DiffDelta, DiffHunk, DiffLine, DiffBinary};
|
16
|
+
use git2::ObjectType;
|
17
|
+
use state::Filter;
|
18
|
+
|
19
|
+
extern crate md5;
|
20
|
+
extern crate chrono;
|
21
|
+
use maud::PreEscaped;
|
22
|
+
use self::chrono::{TimeZone, FixedOffset};
|
23
|
+
|
24
|
+
use yaml_rust::Yaml;
|
25
|
+
|
26
|
+
extern crate base64;
|
27
|
+
|
28
|
+
use self::base64::encode;
|
29
|
+
|
30
|
+
pub fn generate(state: Option<State>) -> String {
|
31
|
+
let repo = match Repository::open_from_env() {
|
32
|
+
Ok(repo) => repo,
|
33
|
+
Err(_) => Repository::init(env::var("GIT_DIR").unwrap_or(String::from("."))).unwrap()
|
34
|
+
};
|
35
|
+
println!("Generating from repo: {:?}", repo.path());
|
36
|
+
let config = repo.config().unwrap();
|
37
|
+
|
38
|
+
// If there is no master branch, start the setup
|
39
|
+
if repo.find_branch("master", BranchType::Local).is_err() {
|
40
|
+
return html! {
|
41
|
+
state {
|
42
|
+
setup "1"
|
43
|
+
@if let Some(state) = state {
|
44
|
+
message (state.message)
|
45
|
+
task {
|
46
|
+
name "master"
|
47
|
+
@for property in state.property {
|
48
|
+
property {
|
49
|
+
name (property.name)
|
50
|
+
value (property.value)
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
} @else {
|
55
|
+
task {
|
56
|
+
name "master"
|
57
|
+
}
|
58
|
+
}
|
59
|
+
(project())
|
60
|
+
}
|
61
|
+
}.into_string()
|
62
|
+
}
|
63
|
+
|
64
|
+
let head = repo.head().unwrap();
|
65
|
+
let head_tree_obj = head.peel(ObjectType::Tree).unwrap();
|
66
|
+
let head_tree = head_tree_obj.as_tree().unwrap();
|
67
|
+
let changes = repo.diff_tree_to_index(Some(&head_tree), None, None).unwrap();
|
68
|
+
|
69
|
+
let branches = repo.branches(Some(BranchType::Local)).unwrap().filter_map(|b|b.ok());
|
70
|
+
let all_tasks: Vec<Task> = branches.filter_map(|(branch, _)| {
|
71
|
+
let task = Task::from_ref(branch.get());
|
72
|
+
if let Some(ref state) = state {
|
73
|
+
if task.name != state.task { Some(task) } else { None }
|
74
|
+
} else {
|
75
|
+
if !branch.is_head() { Some(task) } else { None }
|
76
|
+
}
|
77
|
+
}).collect();
|
78
|
+
|
79
|
+
// Set Filters
|
80
|
+
let filter: Option<Filter> = match all_tasks.is_empty() {
|
81
|
+
true => Some(Filter {
|
82
|
+
name: String::from("Status"),
|
83
|
+
value: String::from("Sprint")
|
84
|
+
}),
|
85
|
+
false => match state {
|
86
|
+
Some(ref state) => match state.filter {
|
87
|
+
Some(ref filter) if filter.name != "" => state.filter.clone(),
|
88
|
+
_ => None,
|
89
|
+
},
|
90
|
+
None => None
|
91
|
+
}
|
92
|
+
};
|
93
|
+
|
94
|
+
// Apply Filters
|
95
|
+
let mut tasks: Vec<&Task> = match filter {
|
96
|
+
Some(ref filter) => {
|
97
|
+
let filter_name = Yaml::String(filter.name.clone());
|
98
|
+
let filter_by_value = Yaml::String(filter.value.clone());
|
99
|
+
all_tasks.iter().filter(|task| {
|
100
|
+
match task.get(&repo, &filter_name) {
|
101
|
+
Some(ref value) if *value == filter_by_value => true,
|
102
|
+
Some(ref value) if *value == Yaml::Null => true,
|
103
|
+
None => true,
|
104
|
+
_ => false
|
105
|
+
}
|
106
|
+
}).collect()
|
107
|
+
},
|
108
|
+
None => all_tasks.iter().map(|t|t).collect()
|
109
|
+
};
|
110
|
+
|
111
|
+
tasks.sort_by(|a, b| {
|
112
|
+
let ordinal = Yaml::from_str("Ordinal");
|
113
|
+
let ord_a = a.get(&repo, &ordinal).unwrap_or(Yaml::Real(String::from("1.0"))).as_f64().unwrap_or(1.0);
|
114
|
+
let ord_b = b.get(&repo, &ordinal).unwrap_or(Yaml::Real(String::from("1.0"))).as_f64().unwrap_or(1.0);
|
115
|
+
ord_a.partial_cmp(&ord_b).unwrap()
|
116
|
+
});
|
117
|
+
|
118
|
+
let branch = match state.clone() {
|
119
|
+
Some(state) => match repo.find_branch(&state.task, BranchType::Local) {
|
120
|
+
Ok(branch) => branch.into_reference(),
|
121
|
+
Err(_) => head
|
122
|
+
},
|
123
|
+
None => head
|
124
|
+
};
|
125
|
+
|
126
|
+
let task = Task::from_ref(&branch);
|
127
|
+
|
128
|
+
let mut revwalk = repo.revwalk().unwrap();
|
129
|
+
revwalk.set_sorting(git2::SORT_REVERSE);
|
130
|
+
revwalk.push(branch.target().unwrap()).unwrap();
|
131
|
+
if branch.shorthand().unwrap() != "master" {
|
132
|
+
revwalk.hide_ref("refs/heads/master").unwrap();
|
133
|
+
}
|
134
|
+
|
135
|
+
let mut status_opts = StatusOptions::new();
|
136
|
+
status_opts.include_untracked(true);
|
137
|
+
|
138
|
+
let payload = html! {
|
139
|
+
state {
|
140
|
+
@if let Some(commit) = state.clone() {
|
141
|
+
focus (commit.focus)
|
142
|
+
}
|
143
|
+
user {
|
144
|
+
name (config.get_string("user.name" ).unwrap_or(String::from("Unknown")))
|
145
|
+
email (config.get_string("user.email").unwrap_or(String::from("root@localhost")))
|
146
|
+
}
|
147
|
+
@if let Some(state) = state.clone() {
|
148
|
+
message (state.message)
|
149
|
+
@if state.property.len() == 0 {
|
150
|
+
@let task = Task::from_ref(&branch) {
|
151
|
+
(render_task(&repo, &task, task.properties(&repo)))
|
152
|
+
}
|
153
|
+
} @else {
|
154
|
+
task {
|
155
|
+
name (state.task)
|
156
|
+
@for property in state.property {
|
157
|
+
property {
|
158
|
+
name (property.name)
|
159
|
+
value (property.value)
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}
|
164
|
+
} @else {
|
165
|
+
@let task = Task::from_ref(&branch) {
|
166
|
+
(render_task(&repo, &task, task.properties(&repo)))
|
167
|
+
}
|
168
|
+
}
|
169
|
+
tasks {
|
170
|
+
@if let Some(filter) = filter {
|
171
|
+
@if filter.name != "" {
|
172
|
+
filter {
|
173
|
+
name (filter.name)
|
174
|
+
value (filter.value)
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
@for task in tasks {
|
179
|
+
(render_task(&repo, &task, task.properties(&repo)))
|
180
|
+
}
|
181
|
+
}
|
182
|
+
log {
|
183
|
+
@for (_, rev) in revwalk.enumerate() {
|
184
|
+
@let commit = repo.find_commit(rev.unwrap()).unwrap() {
|
185
|
+
commit {
|
186
|
+
id (commit.id())
|
187
|
+
@let time = commit.time() {
|
188
|
+
timestamp (time.seconds())
|
189
|
+
localtime (FixedOffset::east(time.offset_minutes()*60).timestamp(time.seconds(), 0).to_rfc3339())
|
190
|
+
}
|
191
|
+
user {
|
192
|
+
@let author = commit.author() {
|
193
|
+
name (author.name().unwrap())
|
194
|
+
@let email = author.email().unwrap().trim() {
|
195
|
+
email (email)
|
196
|
+
image (format!("https://www.gravatar.com/avatar/{:x}?s=64", md5::compute(email.to_lowercase())))
|
197
|
+
}
|
198
|
+
}
|
199
|
+
}
|
200
|
+
@let mut message = commit.message().unwrap().split("---\n") {
|
201
|
+
message (message.next().unwrap())
|
202
|
+
@let task = Task::from_commit(&branch.shorthand().unwrap(), &commit) {
|
203
|
+
(render_task(&repo, &task, task.changes(&repo)))
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
207
|
+
}
|
208
|
+
}
|
209
|
+
}
|
210
|
+
@if task.name == "master" {
|
211
|
+
(project())
|
212
|
+
} @else {
|
213
|
+
(properties())
|
214
|
+
}
|
215
|
+
changes {
|
216
|
+
@if let Ok(delta) = changes.stats() {
|
217
|
+
@if delta.files_changed() + delta.insertions() + delta.deletions() > 0 {
|
218
|
+
statistics {
|
219
|
+
files (delta.files_changed())
|
220
|
+
insertions (delta.insertions())
|
221
|
+
deletions (delta.deletions())
|
222
|
+
}
|
223
|
+
}
|
224
|
+
}
|
225
|
+
@for change in repo.statuses(Some(&mut status_opts)).unwrap().iter() {
|
226
|
+
@let path = change.path().unwrap() {
|
227
|
+
change id=(path.replace("/", "_").replace(".", "_").replace(" ", "_")) {
|
228
|
+
path (path)
|
229
|
+
insertions {}
|
230
|
+
deletions {}
|
231
|
+
included @match change.head_to_index().map(|d| d.status()).unwrap_or(Delta::Unreadable) {
|
232
|
+
Delta::Modified | Delta::Added | Delta::Deleted => "true",
|
233
|
+
_ => "false"
|
234
|
+
}
|
235
|
+
removal @match change.head_to_index().map(|d| d.status()).unwrap_or(Delta::Unreadable) {
|
236
|
+
Delta::Deleted => "true",
|
237
|
+
_ => "false"
|
238
|
+
}
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}
|
242
|
+
}
|
243
|
+
diffs {
|
244
|
+
@for change in diff(changes) {
|
245
|
+
(change)
|
246
|
+
}
|
247
|
+
}
|
248
|
+
}
|
249
|
+
}.into_string();
|
250
|
+
println!(" {}Sent payload of size: {}{}", color::Fg(color::LightGreen), payload.len(), color::Fg(color::Reset));
|
251
|
+
payload
|
252
|
+
}
|
253
|
+
|
254
|
+
fn diff(changes: Diff) -> Vec<PreEscaped<String>> {
|
255
|
+
let result = RefCell::new(vec![]);
|
256
|
+
changes.foreach(&mut |delta: DiffDelta, _: f32| {
|
257
|
+
result.borrow_mut().push(html!(
|
258
|
+
@if let Some(path) = delta.new_file().path() {
|
259
|
+
label (path.to_str().unwrap_or("[invalid]"))
|
260
|
+
}
|
261
|
+
));
|
262
|
+
true
|
263
|
+
}, Some(&mut |delta: DiffDelta, binary: DiffBinary| {
|
264
|
+
if binary.contains_data() {
|
265
|
+
result.borrow_mut().push(html!(
|
266
|
+
img src=(format!("data:image/png,{}", (String::from_utf8_lossy(&binary.new_file().data())))) {}
|
267
|
+
));
|
268
|
+
} else {
|
269
|
+
let path = delta.new_file().path().unwrap();
|
270
|
+
let mut file = File::open(path).unwrap();
|
271
|
+
let mut contents = vec![];
|
272
|
+
file.read_to_end(&mut contents).unwrap();
|
273
|
+
result.borrow_mut().push(html!(
|
274
|
+
img src=(format!("data:image/jpeg;base64,{}", encode(&contents))) alt=(format!("{}", path.to_str().unwrap())) {}
|
275
|
+
));
|
276
|
+
}
|
277
|
+
true
|
278
|
+
}), None, Some(&mut |_: DiffDelta, _: Option<DiffHunk>, line: DiffLine| {
|
279
|
+
let class = match line.origin() {
|
280
|
+
'+' | '>' => "add",
|
281
|
+
'-' | '<' => "sub",
|
282
|
+
'H' | 'F' => "meta",
|
283
|
+
_ => ""
|
284
|
+
};
|
285
|
+
result.borrow_mut().push(html!(
|
286
|
+
span class=(class) (String::from_utf8_lossy(&line.content()))
|
287
|
+
));
|
288
|
+
true
|
289
|
+
})).unwrap();
|
290
|
+
result.into_inner()
|
291
|
+
}
|
292
|
+
|
293
|
+
fn render_task(repo: &Repository, task: &Task, changes: Vec<(String, Option<String>, String)>) -> PreEscaped<String> {
|
294
|
+
html!(task {
|
295
|
+
name (task.name)
|
296
|
+
@for (name, before, value) in changes {
|
297
|
+
property {
|
298
|
+
name (name)
|
299
|
+
@if let Some(before) = before {
|
300
|
+
before (before)
|
301
|
+
}
|
302
|
+
value (value)
|
303
|
+
}
|
304
|
+
}
|
305
|
+
})
|
306
|
+
}
|
307
|
+
|
308
|
+
fn properties() -> PreEscaped<String> {
|
309
|
+
html! {
|
310
|
+
properties {
|
311
|
+
property {
|
312
|
+
name "Status"
|
313
|
+
options {
|
314
|
+
option "Sprint"
|
315
|
+
option "In Progress"
|
316
|
+
option "In Review"
|
317
|
+
option "Blocked"
|
318
|
+
option "Done"
|
319
|
+
}
|
320
|
+
}
|
321
|
+
property {
|
322
|
+
name "Ordinal"
|
323
|
+
value "1.0"
|
324
|
+
}
|
325
|
+
property {
|
326
|
+
name "Estimate"
|
327
|
+
}
|
328
|
+
property {
|
329
|
+
name "Developer"
|
330
|
+
value "Jaden Carver <jaden.carver@gmail.com>"
|
331
|
+
options {
|
332
|
+
option value="Jaden Carver <jaden.carver@gmail.com>" "Jaden Carver"
|
333
|
+
option value="Bob Dole <bdole69@gmail.com>" "Bob Dole"
|
334
|
+
}
|
335
|
+
}
|
336
|
+
property {
|
337
|
+
name "Manager"
|
338
|
+
value "Jaden Carver <jaden.carver@gmail.com>"
|
339
|
+
options {
|
340
|
+
option value="Jaden Carver <jaden.carver@gmail.com>" "Jaden Carver"
|
341
|
+
option value="Bob Dole <bdole69@gmail.com>" "Bob Dole"
|
342
|
+
}
|
343
|
+
}
|
344
|
+
property {
|
345
|
+
name "Description"
|
346
|
+
}
|
347
|
+
}
|
348
|
+
}
|
349
|
+
}
|
350
|
+
|
351
|
+
fn project() -> PreEscaped<String> {
|
352
|
+
html! {
|
353
|
+
properties {
|
354
|
+
property {
|
355
|
+
name "Project"
|
356
|
+
}
|
357
|
+
property {
|
358
|
+
name "Status"
|
359
|
+
options {
|
360
|
+
option "Sprint"
|
361
|
+
option "In Progress"
|
362
|
+
option "In Review"
|
363
|
+
option "Blocked"
|
364
|
+
option "Done"
|
365
|
+
}
|
366
|
+
}
|
367
|
+
property {
|
368
|
+
name "Manager"
|
369
|
+
value "Jaden Carver <jaden.carver@gmail.com>"
|
370
|
+
options {
|
371
|
+
option value="Jaden Carver <jaden.carver@gmail.com>" "Jaden Carver"
|
372
|
+
option value="Bob Dole <bdole69@gmail.com>" "Bob Dole"
|
373
|
+
}
|
374
|
+
}
|
375
|
+
property {
|
376
|
+
name "Description"
|
377
|
+
}
|
378
|
+
}
|
379
|
+
}
|
380
|
+
}
|
data/src/task.rs
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
use yaml_rust::{Yaml, YamlLoader};
|
2
|
+
use yaml_rust::yaml::Hash;
|
3
|
+
|
4
|
+
extern crate git2;
|
5
|
+
use self::git2::{Repository, ObjectType};
|
6
|
+
use self::git2::{Commit, Reference, Oid};
|
7
|
+
|
8
|
+
#[derive(Debug)]
|
9
|
+
pub struct Task {
|
10
|
+
pub name: String,
|
11
|
+
commit: Option<Oid>,
|
12
|
+
changes: Hash
|
13
|
+
}
|
14
|
+
|
15
|
+
impl Task {
|
16
|
+
pub fn from_ref(reference: &Reference) -> Task {
|
17
|
+
let commit_obj = reference.peel(ObjectType::Commit).unwrap();
|
18
|
+
let commit = commit_obj.as_commit().unwrap();
|
19
|
+
let name = reference.shorthand().unwrap_or("master");
|
20
|
+
Task::from_commit(&name, &commit)
|
21
|
+
}
|
22
|
+
|
23
|
+
pub fn from_commit(name: &str, commit: &Commit) -> Task {
|
24
|
+
let mut messages = commit.message().unwrap().split("---\n");
|
25
|
+
let _message = messages.next().unwrap();
|
26
|
+
let mut tasks = vec![];
|
27
|
+
if let Some(yaml) = messages.next() {
|
28
|
+
if let Ok(loader) = YamlLoader::load_from_str(yaml) {
|
29
|
+
for yaml in loader.iter() {
|
30
|
+
if let Some(hash) = yaml.as_hash() {
|
31
|
+
for (key, values) in hash {
|
32
|
+
if let Some(key) = key.as_str() {
|
33
|
+
if let Some(values) = values.as_hash() {
|
34
|
+
tasks.push(Task {
|
35
|
+
name: String::from(key),
|
36
|
+
changes: values.clone(),
|
37
|
+
commit: Some(commit.id())
|
38
|
+
});
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
};
|
46
|
+
tasks.retain(|c| c.name == name);
|
47
|
+
tasks.pop().unwrap_or(Task {
|
48
|
+
name: String::from(name),
|
49
|
+
commit: Some(commit.id()),
|
50
|
+
changes: Hash::new()
|
51
|
+
})
|
52
|
+
}
|
53
|
+
|
54
|
+
pub fn get(&self, repo: &Repository, property: &Yaml) -> Option<Yaml> {
|
55
|
+
if let Some(value) = self.changes.get(&property) {
|
56
|
+
Some(value.clone())
|
57
|
+
} else if let Some(commit) = self.commit {
|
58
|
+
let commit = repo.find_commit(commit).expect("Unable to find commit!");
|
59
|
+
let parents = commit.parents().map(|parent| Task::from_commit(&self.name, &parent));
|
60
|
+
let mut candidates: Vec<Yaml> = parents.filter_map(|task| task.get(&repo, property)).collect();
|
61
|
+
candidates.pop()
|
62
|
+
} else {
|
63
|
+
None
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
pub fn parent(&self, repo: &Repository) -> Option<Task> {
|
68
|
+
if let Some(commit_oid) = self.commit {
|
69
|
+
let commit = repo.find_commit(commit_oid).unwrap();
|
70
|
+
if let Some(parent) = commit.parents().next() {
|
71
|
+
Some(Task::from_commit(&self.name, &parent))
|
72
|
+
} else { None }
|
73
|
+
} else { None }
|
74
|
+
}
|
75
|
+
|
76
|
+
pub fn changes(&self, repo: &Repository) -> Vec<(String, Option<String>, String)> {
|
77
|
+
let mut changes = vec![];
|
78
|
+
for (key, value) in self.changes.clone() {
|
79
|
+
let after = match value {
|
80
|
+
Yaml::String(ref s) => s.clone(),
|
81
|
+
Yaml::Integer(i) => format!("{}", i),
|
82
|
+
Yaml::Real(ref i) => format!("{}", i),
|
83
|
+
Yaml::Boolean(b) => format!("{}", b),
|
84
|
+
Yaml::Null => format!(""),
|
85
|
+
_ => String::from("[unknown]")
|
86
|
+
};
|
87
|
+
let before = if let Some(parent) = self.parent(&repo) {
|
88
|
+
match parent.get(&repo, &key) {
|
89
|
+
Some(before_value) => {
|
90
|
+
if before_value == value {
|
91
|
+
None
|
92
|
+
} else {
|
93
|
+
match before_value {
|
94
|
+
Yaml::String(ref s) => Some(s.clone()),
|
95
|
+
Yaml::Integer(i) => Some(format!("{}", i)),
|
96
|
+
Yaml::Real(i) => Some(format!("{}", i)),
|
97
|
+
Yaml::Boolean(b) => Some(format!("{}", b)),
|
98
|
+
Yaml::Null => Some(format!("")),
|
99
|
+
_ => None
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}, _ => None
|
103
|
+
}
|
104
|
+
} else { None };
|
105
|
+
changes.push((String::from(key.as_str().unwrap()), before, after));
|
106
|
+
};
|
107
|
+
changes
|
108
|
+
}
|
109
|
+
|
110
|
+
pub fn properties(&self, repo: &Repository) -> Vec<(String, Option<String>, String)> {
|
111
|
+
let mut changes = vec![];
|
112
|
+
let properties = ["Ordinal","Status","Project","Estimate","Developer","Manager","Description"];
|
113
|
+
for property in properties.iter() {
|
114
|
+
let prop = Yaml::String(String::from(*property));
|
115
|
+
if let Some(value) = self.get(&repo, &prop) {
|
116
|
+
let after = match value {
|
117
|
+
Yaml::String(ref s) => s.clone(),
|
118
|
+
Yaml::Integer(i) => format!("{}", i),
|
119
|
+
Yaml::Real(i) => format!("{}", i),
|
120
|
+
Yaml::Boolean(b) => format!("{}", b),
|
121
|
+
Yaml::Null => format!(""),
|
122
|
+
_ => String::from("[unknown]")
|
123
|
+
};
|
124
|
+
changes.push((String::from(*property), None, after));
|
125
|
+
}
|
126
|
+
};
|
127
|
+
changes
|
128
|
+
}
|
129
|
+
}
|