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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.scss-lint.yml +21 -0
  6. data/.travis.yml +5 -0
  7. data/.yardopts +1 -0
  8. data/Cargo.lock +679 -0
  9. data/Cargo.toml +25 -0
  10. data/Gemfile +4 -0
  11. data/Guardfile +70 -0
  12. data/LICENSE.txt +21 -0
  13. data/Makefile +4 -0
  14. data/README.md +58 -0
  15. data/Rakefile +9 -36
  16. data/assets/__pm.js +424 -0
  17. data/assets/__pm.scss +261 -0
  18. data/assets/_buttons.scss +50 -0
  19. data/assets/_checkbox.scss +59 -0
  20. data/assets/_colors.scss +15 -0
  21. data/assets/_details.scss +54 -0
  22. data/assets/_pm_commits.scss +171 -0
  23. data/assets/_pm_setup.scss +7 -0
  24. data/assets/_pm_tasks.scss +280 -0
  25. data/assets/_tokens.scss +56 -0
  26. data/bin/console +14 -0
  27. data/bin/make +2 -0
  28. data/bin/rake +17 -0
  29. data/bin/release +5 -0
  30. data/bin/setup +8 -0
  31. data/bin/start +5 -0
  32. data/config.ru +10 -0
  33. data/config/properties.yml +37 -0
  34. data/extconf.rb +2 -0
  35. data/lib/superconductor.rb +35 -1
  36. data/lib/superconductor/documentation.rb +34 -0
  37. data/lib/superconductor/middleware.rb +71 -0
  38. data/lib/superconductor/version.rb +1 -1
  39. data/src/lib.rs +101 -0
  40. data/src/server.rs +176 -0
  41. data/src/state/mod.rs +5 -0
  42. data/src/state/state.rs +233 -0
  43. data/src/state/xml.rs +380 -0
  44. data/src/task.rs +129 -0
  45. data/src/view.rs +396 -0
  46. data/superconductor.gemspec +41 -0
  47. metadata +173 -116
  48. data/MIT-LICENSE +0 -20
  49. data/README.rdoc +0 -3
  50. data/app/assets/javascripts/jquery-linedtextarea.js +0 -126
  51. data/app/assets/javascripts/superconductor.js +0 -2
  52. data/app/assets/javascripts/superconductor/panel.js.coffee +0 -42
  53. data/app/assets/stylesheets/jquery-linedtextarea.css +0 -68
  54. data/app/assets/stylesheets/scaffold.css +0 -56
  55. data/app/assets/stylesheets/superconductor.css +0 -4
  56. data/app/assets/stylesheets/superconductor/panel.css.scss +0 -142
  57. data/app/controllers/file_controller.rb +0 -24
  58. data/app/helpers/superconductor/panel_helper.rb +0 -2
  59. data/app/models/superconductor.rb +0 -5
  60. data/app/models/superconductor/exception.rb +0 -0
  61. data/app/views/superconductor/_panel.html.erb +0 -130
  62. data/app/views/superconductor/_panel.js.erb +0 -0
  63. data/config/routes.rb +0 -6
  64. data/lib/superconductor/engine.rb +0 -24
  65. data/lib/tasks/superconductor_tasks.rake +0 -4
  66. data/test/dummy/README.rdoc +0 -261
  67. data/test/dummy/Rakefile +0 -7
  68. data/test/dummy/app/assets/javascripts/application.js +0 -15
  69. data/test/dummy/app/assets/stylesheets/application.css +0 -13
  70. data/test/dummy/app/controllers/application_controller.rb +0 -3
  71. data/test/dummy/app/helpers/application_helper.rb +0 -2
  72. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  73. data/test/dummy/config.ru +0 -4
  74. data/test/dummy/config/application.rb +0 -56
  75. data/test/dummy/config/boot.rb +0 -10
  76. data/test/dummy/config/database.yml +0 -25
  77. data/test/dummy/config/environment.rb +0 -5
  78. data/test/dummy/config/environments/development.rb +0 -37
  79. data/test/dummy/config/environments/production.rb +0 -67
  80. data/test/dummy/config/environments/test.rb +0 -37
  81. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  82. data/test/dummy/config/initializers/inflections.rb +0 -15
  83. data/test/dummy/config/initializers/mime_types.rb +0 -5
  84. data/test/dummy/config/initializers/secret_token.rb +0 -7
  85. data/test/dummy/config/initializers/session_store.rb +0 -8
  86. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  87. data/test/dummy/config/locales/en.yml +0 -5
  88. data/test/dummy/config/routes.rb +0 -58
  89. data/test/dummy/public/404.html +0 -26
  90. data/test/dummy/public/422.html +0 -26
  91. data/test/dummy/public/500.html +0 -25
  92. data/test/dummy/public/favicon.ico +0 -0
  93. data/test/dummy/script/rails +0 -6
  94. data/test/integration/navigation_test.rb +0 -10
  95. data/test/superconductor_test.rb +0 -7
  96. data/test/test_helper.rb +0 -10
  97. 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
+ }