superconductor 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }