tps_reporter 0.0.2 → 0.2.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.
- data/.gitignore +2 -0
- data/GUIDE.md +54 -0
- data/HISTORY.md +24 -0
- data/README.md +5 -3
- data/bin/tps +42 -11
- data/data/index.haml +317 -39
- data/lib/tps/cli_reporter.rb +11 -3
- data/lib/tps/importer.rb +67 -0
- data/lib/tps/sprint.rb +48 -0
- data/lib/tps/task.rb +132 -4
- data/lib/tps/task_list.rb +22 -2
- data/lib/tps/version.rb +1 -1
- data/lib/tps.rb +1 -0
- data/test/hello.yml +7 -0
- data/test/sprint_test.rb +64 -0
- data/test/sprints.yml +18 -0
- data/test/tps_test.rb +27 -0
- metadata +23 -7
data/.gitignore
ADDED
data/GUIDE.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
Guide
|
2
|
+
=====
|
3
|
+
|
4
|
+
Advanced usage guide!
|
5
|
+
|
6
|
+
Sprint support
|
7
|
+
--------------
|
8
|
+
|
9
|
+
First, make a sprints block, preferrably at the top of your `tasks.yml` file.
|
10
|
+
List down the sprints.
|
11
|
+
|
12
|
+
Then simply add the sprint ID to your tasks.
|
13
|
+
|
14
|
+
# tasks.yml
|
15
|
+
Sprints:
|
16
|
+
s1: Sprint 1 (Nov 1-15)
|
17
|
+
s2: Sprint 2 (Nov 16-30)
|
18
|
+
s3: Sprint 3 (Dec 1-15)
|
19
|
+
|
20
|
+
Beta release:
|
21
|
+
Account:
|
22
|
+
Login: [s1]
|
23
|
+
Logout: [s1, done]
|
24
|
+
Signup: [s2]
|
25
|
+
|
26
|
+
It's also recursive--you can put sprints in your parent tasks under `_`:
|
27
|
+
|
28
|
+
Beta release:
|
29
|
+
Blog:
|
30
|
+
_: [s3]
|
31
|
+
Create posts:
|
32
|
+
Read posts:
|
33
|
+
Delete posts:
|
34
|
+
|
35
|
+
Trello card linking support
|
36
|
+
---------------------------
|
37
|
+
|
38
|
+
Simply link the card shortcut under each item using `tr/XXXX`, where *XXXX* is
|
39
|
+
the short URL ID for the Trello card. You can see the short ID in Trello by
|
40
|
+
clicking "more..." inside the card popup.
|
41
|
+
|
42
|
+
Beta release:
|
43
|
+
Blog: [tr/Xh3pAGp1]
|
44
|
+
Account management:
|
45
|
+
|
46
|
+
You can also link the card numbers, but you have to define the main Trello board
|
47
|
+
URL. Simply add `Trello URL: ____` to the top of the file.
|
48
|
+
|
49
|
+
|
50
|
+
Trello URL: https://trello.com/board/trello-resources/4f84a60f0cbdcb7e7d40e099
|
51
|
+
|
52
|
+
Beta release:
|
53
|
+
Blog: [tr/42]
|
54
|
+
Account management: [tr/12, done]
|
data/HISTORY.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
v0.2.0 - Nov 24, 2012
|
2
|
+
---------------------
|
3
|
+
|
4
|
+
### New features:
|
5
|
+
|
6
|
+
* Added support for sprints! See `GUIDE.md` for info.
|
7
|
+
* Trello card linking support! See `GUIDE.md` again.
|
8
|
+
* Redesigned HTML output.
|
9
|
+
* Added "Export mode" in the HTML output.
|
10
|
+
|
11
|
+
### Internals:
|
12
|
+
|
13
|
+
* Implement @task['name'] lookup.
|
14
|
+
* Implement Task#id.
|
15
|
+
* Sprints: add a sprint model.
|
16
|
+
* Task: implement #filter, #filter_by, #contains_sprint?
|
17
|
+
|
18
|
+
v0.0.3 - Nov 21, 2012
|
19
|
+
---------------------
|
20
|
+
|
21
|
+
* Added support for linking tasks to Trello cards.
|
22
|
+
* Fixed error when invoking 'tps' without arguments.
|
23
|
+
* Fixed typo in message when invoked without a tasks.yml file.
|
24
|
+
|
1
25
|
v0.0.2 - Feb 04, 2012
|
2
26
|
---------------------
|
3
27
|
|
data/README.md
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|

|
4
4
|
|
5
5
|
We often need to make regular reports of things done for our projects at work. I
|
6
|
-
hate doing these by hand. This tool lets us build these reports from YAML files
|
6
|
+
hate doing these by hand. This tool lets us build these reports from YAML files
|
7
|
+
[such as this][s].
|
7
8
|
|
8
9
|
Get started
|
9
10
|
-----------
|
@@ -12,8 +13,8 @@ Install TPS (Ruby):
|
|
12
13
|
|
13
14
|
$ gem install tps_reporter
|
14
15
|
|
15
|
-
...then generate a sample file. (or create `tasks.yml` based on [this
|
16
|
-
|
16
|
+
...then generate a sample file. (or create `tasks.yml` based on [this sample
|
17
|
+
file.][s])
|
17
18
|
|
18
19
|
$ tps sample
|
19
20
|
|
@@ -58,6 +59,7 @@ The metadata is just a simple YAML array that you can conveniently define using
|
|
58
59
|
- `done`
|
59
60
|
- `in progress`
|
60
61
|
- `pt/2839478` *(Pivotal tracker ID. Links to a Pivotal tracker story.)*
|
62
|
+
- `tr/LabxGP3` *(Trello card short name. Links to a Trello card.)*
|
61
63
|
- `0pt` *(points; influences percentage. needs to end in __pt__ or __pts__.)*
|
62
64
|
- `10%` *(task progress. implies __in progress__.)*
|
63
65
|
|
data/bin/tps
CHANGED
@@ -3,8 +3,17 @@ $:.unshift File.expand_path('../../lib', __FILE__)
|
|
3
3
|
require 'tps'
|
4
4
|
|
5
5
|
module Params
|
6
|
-
def extract(what)
|
7
|
-
|
6
|
+
def extract(what)
|
7
|
+
i = index(what) and slice!(i, 2)[1]
|
8
|
+
end
|
9
|
+
|
10
|
+
def first_is(what)
|
11
|
+
shift if first == what
|
12
|
+
end
|
13
|
+
|
14
|
+
def first_matches(what)
|
15
|
+
shift if first.downcase == what.downcase[0...first.size]
|
16
|
+
end
|
8
17
|
end
|
9
18
|
|
10
19
|
ARGV.extend Params
|
@@ -17,9 +26,12 @@ module TPS::Command
|
|
17
26
|
puts ""
|
18
27
|
puts "Commands:"
|
19
28
|
puts " html Builds HTML"
|
29
|
+
puts " html --stdout Builds HTML in stdout"
|
20
30
|
puts " open Builds HTML and opens it in the browser"
|
21
31
|
puts " paparazzi Builds HTML and opens it in Paparazzi (Mac)"
|
22
32
|
puts " print Prints the report to the console."
|
33
|
+
puts " import FILE FORMAT Import from a file of a given format."
|
34
|
+
puts " (Supported formats: trello)"
|
23
35
|
puts ""
|
24
36
|
puts "Options (optional):"
|
25
37
|
puts " -f FILE Specifies the input file. Defaults to tasks.yml."
|
@@ -29,8 +41,14 @@ module TPS::Command
|
|
29
41
|
|
30
42
|
def html
|
31
43
|
t = get_tasks
|
32
|
-
|
33
|
-
|
44
|
+
stdout = ARGV.delete('--stdout')
|
45
|
+
|
46
|
+
if stdout
|
47
|
+
puts t.to_html
|
48
|
+
else
|
49
|
+
path = output { |file| file.write t.to_html }
|
50
|
+
info "Wrote to '#{path}'."
|
51
|
+
end
|
34
52
|
|
35
53
|
path
|
36
54
|
end
|
@@ -65,6 +83,13 @@ module TPS::Command
|
|
65
83
|
reporter.print
|
66
84
|
end
|
67
85
|
|
86
|
+
def import(file, format=nil)
|
87
|
+
require 'tps/importer'
|
88
|
+
import = TPS::Importer.create(file, format)
|
89
|
+
File.open('tasks.yml', 'w') { |f| f.write import.to_yaml }
|
90
|
+
puts "Wrote to tasks.yml."
|
91
|
+
end
|
92
|
+
|
68
93
|
private
|
69
94
|
def err(str)
|
70
95
|
$stdout << "#{str}\n"
|
@@ -92,13 +117,13 @@ private
|
|
92
117
|
fn = tasks_filename
|
93
118
|
if !File.exists?(fn)
|
94
119
|
err "No tasks file found."
|
95
|
-
err "Create a sample using `
|
120
|
+
err "Create a sample using `tps sample`."
|
96
121
|
exit 256
|
97
122
|
end
|
98
123
|
|
99
124
|
begin
|
100
125
|
TPS::TaskList.new yaml: fn
|
101
|
-
rescue => e
|
126
|
+
rescue Psych::SyntaxError => e
|
102
127
|
err "Parse error: #{e.message}"
|
103
128
|
exit 256
|
104
129
|
end
|
@@ -125,19 +150,25 @@ private
|
|
125
150
|
end
|
126
151
|
end
|
127
152
|
|
128
|
-
if ARGV.
|
153
|
+
if ARGV.empty?
|
154
|
+
TPS::Command.help
|
155
|
+
|
156
|
+
elsif ARGV.first_matches('html')
|
129
157
|
TPS::Command.html
|
130
158
|
|
131
|
-
elsif ARGV.
|
159
|
+
elsif ARGV.first_matches('open')
|
132
160
|
TPS::Command.open
|
133
161
|
|
134
|
-
elsif ARGV.
|
162
|
+
elsif ARGV.first_matches('sample')
|
135
163
|
TPS::Command.sample
|
136
164
|
|
137
|
-
elsif ARGV.
|
165
|
+
elsif ARGV.first_matches('print')
|
138
166
|
TPS::Command.print
|
139
167
|
|
140
|
-
elsif ARGV.
|
168
|
+
elsif ARGV.first_matches('import')
|
169
|
+
TPS::Command.import *ARGV
|
170
|
+
|
171
|
+
elsif ARGV.first_matches('paparazzi')
|
141
172
|
TPS::Command.paparazzi
|
142
173
|
|
143
174
|
else
|
data/data/index.haml
CHANGED
@@ -4,13 +4,45 @@
|
|
4
4
|
%script{src: "http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7/jquery.min.js"}
|
5
5
|
|
6
6
|
:javascript
|
7
|
-
$("td").live('
|
7
|
+
$("td").live('dblclick', function() {
|
8
8
|
$(this).closest('tr').toggleClass('highlight');
|
9
9
|
});
|
10
10
|
|
11
|
+
// Folding
|
12
|
+
$(".task .status").live('click', function(e) {
|
13
|
+
e.preventDefault();
|
14
|
+
e.stopPropagation();
|
15
|
+
var id = $(this).closest('tr').attr('id');
|
16
|
+
$(".in_"+id).toggle();
|
17
|
+
});
|
18
|
+
|
19
|
+
// Filter by sprints
|
20
|
+
$('.sprint-nav a').live('click', function(e) {
|
21
|
+
e.preventDefault();
|
22
|
+
e.stopPropagation();
|
23
|
+
|
24
|
+
var sprint = $(this).attr('data-sprint');
|
25
|
+
|
26
|
+
$(".tasks tr")
|
27
|
+
.hide()
|
28
|
+
.filter('.sprint-'+sprint+', .has_sprint-'+sprint).show();
|
29
|
+
});
|
30
|
+
|
31
|
+
$('[href="#all"]').live('click', function(e) {
|
32
|
+
e.preventDefault();
|
33
|
+
$('.tasks tr').show();
|
34
|
+
});
|
35
|
+
|
36
|
+
$('[href="#print"]').live('click', function(e) {
|
37
|
+
e.preventDefault();
|
38
|
+
$('body').addClass('print');
|
39
|
+
});
|
40
|
+
|
41
|
+
|
11
42
|
%style
|
12
43
|
:plain
|
13
44
|
body {
|
45
|
+
background: #eee;
|
14
46
|
padding: 0;
|
15
47
|
margin: 0; }
|
16
48
|
|
@@ -20,13 +52,23 @@
|
|
20
52
|
line-height: 13.5pt;
|
21
53
|
font-size: 9pt; }
|
22
54
|
|
55
|
+
h2.table-header {
|
56
|
+
padding: 0 0 20px 0;
|
57
|
+
margin: 20px auto;
|
58
|
+
text-align: center;
|
59
|
+
font-family: helvetica neue, sans-serif;
|
60
|
+
font-size: 28pt;
|
61
|
+
font-weight: 100;
|
62
|
+
color: #888;
|
63
|
+
text-shadow: 0 1px 0 rgba(250, 250, 250, 0.7); }
|
64
|
+
|
23
65
|
table {
|
24
|
-
border: solid
|
66
|
+
border: solid 10px #fff;
|
67
|
+
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.15);
|
25
68
|
padding: 2px;
|
26
69
|
|
27
70
|
background: #fafafa;
|
28
|
-
margin: 0;
|
29
|
-
width: 600px;
|
71
|
+
margin: 0 auto 20px auto;
|
30
72
|
border-collapse: collapse; }
|
31
73
|
|
32
74
|
table td, table th {
|
@@ -34,9 +76,10 @@
|
|
34
76
|
border-top: solid 1px #eee; }
|
35
77
|
|
36
78
|
/* Columns */
|
37
|
-
tr>.task { width:
|
38
|
-
tr>.points { width:
|
39
|
-
tr>.progress { width:
|
79
|
+
tr>.task { width: auto; text-align: left; padding-right: 50px; }
|
80
|
+
tr>.points { width: 70px; }
|
81
|
+
tr>.progress { width: 100px; }
|
82
|
+
tr>.sprints { padding-right: 50px; }
|
40
83
|
tr>.owner { display: none; }
|
41
84
|
|
42
85
|
/* Indentation */
|
@@ -46,28 +89,30 @@
|
|
46
89
|
.level-4 .task { padding-left: 100px; }
|
47
90
|
|
48
91
|
/* Overrides for parents */
|
49
|
-
.milestone td.task,
|
50
|
-
.feature td.task {
|
92
|
+
tr.milestone td.task,
|
93
|
+
tr.feature td.task {
|
51
94
|
font-weight: bold;
|
52
95
|
font-size: 1.1em; }
|
53
96
|
|
54
97
|
tr.milestone td,
|
55
98
|
tr.feature td {
|
56
|
-
border-top: solid 1px #
|
99
|
+
border-top: solid 1px #ccc; }
|
57
100
|
|
58
|
-
tr .progress .bar {
|
101
|
+
tr.subtask .progress .bar {
|
59
102
|
display: none; }
|
60
103
|
|
61
|
-
tr.
|
62
|
-
tr.feature .progress .bar {
|
63
|
-
display: block; }
|
64
|
-
|
65
|
-
tr td.points>* {
|
104
|
+
tr.subtask td.points>* {
|
66
105
|
display: none; }
|
67
106
|
|
68
|
-
|
69
|
-
tr.
|
70
|
-
|
107
|
+
/* Milestone */
|
108
|
+
tr.milestone td {
|
109
|
+
line-height: 18pt; }
|
110
|
+
|
111
|
+
tr.milestone:not(:first-child) td {
|
112
|
+
border-top: solid 3px #777; }
|
113
|
+
|
114
|
+
tr.milestone td.task {
|
115
|
+
font-size: 1.2em; }
|
71
116
|
|
72
117
|
/* Header */
|
73
118
|
thead {
|
@@ -90,23 +135,24 @@
|
|
90
135
|
background: #fafae0; }
|
91
136
|
|
92
137
|
/* Status box */
|
93
|
-
|
138
|
+
.status {
|
94
139
|
display: inline-block;
|
95
140
|
width: 12px;
|
96
141
|
height: 12px;
|
97
142
|
margin: 0 5px 0 0;
|
98
143
|
|
99
|
-
border-radius:
|
144
|
+
border-radius: 6px;
|
145
|
+
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1), inset 1px 1px 1px rgba(0, 0, 0, 0.1);
|
100
146
|
|
101
147
|
position: relative;
|
102
148
|
top: 2px;
|
103
149
|
background: #ddd; }
|
104
150
|
|
105
|
-
|
106
|
-
|
151
|
+
.parent .status {
|
152
|
+
cursor: pointer; }
|
107
153
|
|
108
|
-
|
109
|
-
|
154
|
+
.parent .status:hover {
|
155
|
+
box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1); }
|
110
156
|
|
111
157
|
/* Progress */
|
112
158
|
.progress .number {
|
@@ -115,25 +161,43 @@
|
|
115
161
|
.progress .bar {
|
116
162
|
height: 10px;
|
117
163
|
border-radius: 5px;
|
118
|
-
|
164
|
+
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05), inset 1px 1px 2px rgba(0, 0, 0, 0.1);
|
165
|
+
background: #eee; }
|
119
166
|
|
120
167
|
.progress .bar span {
|
168
|
+
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05), inset -1px -1px 7px rgba(0, 0, 0, 0.1);
|
169
|
+
min-width: 10px;
|
121
170
|
display: block;
|
122
171
|
height: 10px;
|
123
172
|
border-radius: 5px;
|
124
173
|
background: #888; }
|
125
174
|
|
126
|
-
|
127
|
-
|
175
|
+
.meta {
|
176
|
+
white-space: nowrap;
|
177
|
+
font-size: 8pt;
|
178
|
+
font-weight: normal;
|
128
179
|
text-decoration: none;
|
129
180
|
|
130
|
-
background: #
|
131
|
-
padding: 1px
|
181
|
+
background: #eee;
|
182
|
+
padding: 1px 5px;
|
132
183
|
border-radius: 2px;
|
133
|
-
border-bottom: solid 1px #
|
184
|
+
border-bottom: solid 1px #ddd;
|
185
|
+
border-right: solid 1px #ddd;
|
186
|
+
|
187
|
+
margin: 0 10px;
|
188
|
+
color: #999; }
|
189
|
+
|
190
|
+
a.meta:hover {
|
191
|
+
background: #ddd; }
|
134
192
|
|
135
|
-
|
136
|
-
|
193
|
+
a.meta:active {
|
194
|
+
background: #bbb; }
|
195
|
+
|
196
|
+
span.meta {
|
197
|
+
background: #ddd;
|
198
|
+
border-bottom: solid 1px #ccc;
|
199
|
+
border-right: solid 1px #ccc;
|
200
|
+
color: #666; }
|
137
201
|
|
138
202
|
/* Progress */
|
139
203
|
td.points {
|
@@ -147,31 +211,244 @@
|
|
147
211
|
td.points .points.total {
|
148
212
|
color: #888; }
|
149
213
|
|
150
|
-
|
214
|
+
/* Sprints */
|
215
|
+
.sprint-display {
|
216
|
+
background: #eee;
|
217
|
+
border-radius: 6px;
|
218
|
+
height: 12px;
|
219
|
+
position: relative; }
|
220
|
+
|
221
|
+
.sprint-display {
|
222
|
+
overflow: hidden; }
|
223
|
+
|
224
|
+
.sprint-progress {
|
225
|
+
display: block;
|
226
|
+
float: left;
|
227
|
+
|
228
|
+
background-color: #eee;
|
229
|
+
background: -webkit-linear-gradient(left, #eee, #ddd);
|
230
|
+
height: 2px;
|
231
|
+
border-top-left-radius: 1px;
|
232
|
+
border-bottom-left-radius: 1px;
|
233
|
+
margin-top: 8px; }
|
234
|
+
|
235
|
+
.sprint-marker {
|
236
|
+
display: block;
|
237
|
+
float: left;
|
238
|
+
|
239
|
+
background: #ccc;
|
240
|
+
|
241
|
+
width: 6px;
|
242
|
+
height: 6px;
|
243
|
+
border-radius: 3px;
|
244
|
+
float: left;
|
151
245
|
|
152
|
-
|
246
|
+
margin-top: 6px; }
|
247
|
+
|
248
|
+
.sprint-label {
|
249
|
+
display: block;
|
250
|
+
float: left;
|
251
|
+
|
252
|
+
font-size: 7pt;
|
253
|
+
color: #888;
|
254
|
+
padding-left: 5px; }
|
255
|
+
|
256
|
+
.status-bg-in_progress {
|
257
|
+
background: #ea3; }
|
258
|
+
|
259
|
+
.status-bg-done {
|
260
|
+
background: #393; }
|
261
|
+
|
262
|
+
/* Navigation */
|
263
|
+
body {
|
264
|
+
padding: 80px 0 50px 0; }
|
265
|
+
|
266
|
+
.nav {
|
267
|
+
position: fixed;
|
268
|
+
top: 0;
|
269
|
+
left: 0;
|
270
|
+
right: 0;
|
271
|
+
z-index: 5;
|
272
|
+
|
273
|
+
padding: 0 20px;
|
274
|
+
background: rgba(220, 220, 220, 0.95);
|
275
|
+
box-shadow: inset 0 -5px 3px -3px rgba(0, 0, 0, 0.06);
|
276
|
+
|
277
|
+
font-size: 8pt;
|
278
|
+
font-family: sans-serif; }
|
279
|
+
|
280
|
+
.nav:after {
|
281
|
+
content: '';
|
282
|
+
display: table;
|
283
|
+
clear: both; }
|
284
|
+
|
285
|
+
.nav ul, .nav li, .nav h4 {
|
286
|
+
margin: 0;
|
287
|
+
padding: 0;
|
288
|
+
list-style-type: none; }
|
289
|
+
|
290
|
+
.nav ul, .nav li, .nav h4, .nav a, .nav .segment {
|
291
|
+
float: left; }
|
292
|
+
|
293
|
+
.nav h4 {
|
294
|
+
padding: 1px;
|
295
|
+
color: #888;
|
296
|
+
text-shadow: 0 1px 0 rgba(250, 250, 250, 0.4); }
|
297
|
+
|
298
|
+
.nav ul {
|
299
|
+
margin-left: 10px; }
|
300
|
+
|
301
|
+
.nav .segment {
|
302
|
+
margin: 0;
|
303
|
+
padding: 10px 20px 10px 10px;
|
304
|
+
border-left: solid 1px #ccc;
|
305
|
+
box-shadow: inset 1px 0 0 #e0e0e0; }
|
306
|
+
|
307
|
+
.nav .segment:first-child {
|
308
|
+
box-shadow: none;
|
309
|
+
border-left: 0; }
|
310
|
+
|
311
|
+
.nav h4,
|
312
|
+
.nav a {
|
313
|
+
height: 24px;
|
314
|
+
line-height: 24px; }
|
315
|
+
|
316
|
+
.nav a {
|
317
|
+
color: #888;
|
318
|
+
text-shadow: 0 1px 0 rgba(250, 250, 250, 0.4);
|
319
|
+
text-decoration: none;
|
320
|
+
|
321
|
+
display: block;
|
322
|
+
padding: 0 10px;
|
323
|
+
border: solid 1px rgba(0, 0, 0, 0.1);
|
324
|
+
border-radius: 3px; }
|
325
|
+
|
326
|
+
.nav ul a {
|
327
|
+
border-radius: 0;
|
328
|
+
border-left-width: 0; }
|
329
|
+
|
330
|
+
.nav a:hover {
|
331
|
+
background: rgba(250, 250, 250, 0.2); }
|
332
|
+
|
333
|
+
.nav ul li:first-child a {
|
334
|
+
border-top-left-radius: 3px;
|
335
|
+
border-bottom-left-radius: 3px;
|
336
|
+
border-left-width: 1px; }
|
337
|
+
|
338
|
+
.nav ul li:last-child a {
|
339
|
+
border-top-right-radius: 3px;
|
340
|
+
border-bottom-right-radius: 3px; }
|
341
|
+
|
342
|
+
/* Print mode */
|
343
|
+
body.print {
|
344
|
+
padding: 0;
|
345
|
+
background: transparent; }
|
346
|
+
|
347
|
+
body.print .table-header,
|
348
|
+
body.print .nav {
|
349
|
+
display: none; }
|
350
|
+
|
351
|
+
body.print table {
|
352
|
+
box-shadow: none;
|
353
|
+
margin-left: 0;
|
354
|
+
margin-right: 0;
|
355
|
+
border: 0; }
|
356
|
+
|
357
|
+
/* Sprint table */
|
358
|
+
table.sprints td {
|
359
|
+
padding: 10px; }
|
360
|
+
|
361
|
+
table.sprints td.progress {
|
362
|
+
width: 200px; }
|
363
|
+
|
364
|
+
table.sprints td.progress .bar {
|
365
|
+
background: #ccc; }
|
366
|
+
|
367
|
+
%body
|
368
|
+
.nav
|
369
|
+
.segment
|
370
|
+
- if list.sprints?
|
371
|
+
%h4 Filter by:
|
372
|
+
%ul.sprint-nav
|
373
|
+
- list.sprints.values.each do |sprint|
|
374
|
+
%li
|
375
|
+
%a{href: "#sprint-#{sprint.slug}", data: { sprint: sprint.slug }}= sprint.id
|
376
|
+
|
377
|
+
%ul
|
378
|
+
%li
|
379
|
+
%a{href: '#all'} All
|
380
|
+
|
381
|
+
.segment
|
382
|
+
%ul
|
383
|
+
%li
|
384
|
+
%a{href: '#print'} Export mode
|
385
|
+
|
386
|
+
|
387
|
+
- if list.sprints?
|
388
|
+
%h2.table-header Schedule
|
389
|
+
|
390
|
+
%table.sprints
|
391
|
+
- max_points = list.sprints.values.map(&:points).max
|
392
|
+
- list.sprints.values.each do |sprint|
|
393
|
+
%tr{data: { sprint: sprint.slug }}
|
394
|
+
%td.name= sprint.name
|
395
|
+
%td.progress
|
396
|
+
.bar{style: "width: #{sprint.points*100/max_points}%"}
|
397
|
+
- if sprint.percent > 0
|
398
|
+
%span{style: "width: #{(sprint.percent*100).to_i}%"}
|
399
|
+
%span.number= "#{(sprint.percent*100).to_i}%"
|
400
|
+
%td.points
|
401
|
+
%span.points.done= sprint.points_done.round(1).to_s.gsub(/\.0+$/,'')
|
402
|
+
%span.of of
|
403
|
+
%span.points.total= sprint.points.round(1).to_s.gsub(/\.0+$/,'')
|
404
|
+
%br
|
405
|
+
|
406
|
+
%h2.table-header Tasks
|
407
|
+
|
408
|
+
%table.tasks
|
153
409
|
%thead
|
154
410
|
%tr
|
155
411
|
%th.task Task
|
156
412
|
%th.owner Owner
|
413
|
+
- if list.sprints?
|
414
|
+
%th.sprints Schedule
|
157
415
|
%th.progress Progress
|
158
416
|
%th.points Points
|
159
417
|
|
160
418
|
- list.walk do |task, recurse|
|
161
419
|
|
162
|
-
%tr{
|
420
|
+
%tr{id: "task_#{task.id}", class: "#{task.css_class}"}
|
163
421
|
%td.task
|
164
|
-
%span.status{class: "
|
422
|
+
%span.status{class: "status-bg-#{task.status}"}
|
165
423
|
= task
|
166
424
|
- if task.pivotal_id
|
167
|
-
%a.meta{href: task.pivotal_url}= "
|
425
|
+
%a.meta{href: task.pivotal_url, target: '_blank'}= "PT ##{task.pivotal_id}"
|
426
|
+
- if task.trello_id
|
427
|
+
%a.meta{href: task.trello_url, target: '_blank'}= "Trello"
|
428
|
+
|
429
|
+
- if task.tags.any?
|
430
|
+
- task.tags.each do |tag|
|
431
|
+
%span.meta= tag
|
432
|
+
|
433
|
+
- if task.leaf? && task.points != 1
|
434
|
+
%span.meta= ("%.2f" % task.points).gsub(/\.?0+$/,'') + "pts"
|
168
435
|
|
169
436
|
%td.owner
|
170
437
|
= task.owner
|
171
438
|
|
439
|
+
- if list.sprints?
|
440
|
+
%td.sprints
|
441
|
+
- if task.sprint?
|
442
|
+
%span.sprint-display
|
443
|
+
%span.sprint-progress{style: "width: #{45*task.sprint.index+6}px"}
|
444
|
+
%span.sprint-marker{class: "status-bg-#{task.status}"}
|
445
|
+
%span.sprint-label
|
446
|
+
= task.sprint.id
|
447
|
+
|
172
448
|
%td.progress
|
173
449
|
.bar
|
174
|
-
|
450
|
+
- if task.percent > 0
|
451
|
+
%span{style: "width: #{(task.percent*100).to_i}%"}
|
175
452
|
%span.number= "#{(task.percent*100).to_i}%"
|
176
453
|
|
177
454
|
%td.points
|
@@ -180,3 +457,4 @@
|
|
180
457
|
%span.points.total= task.points.round(1).to_s.gsub(/\.0+$/,'')
|
181
458
|
|
182
459
|
- recurse.call if recurse
|
460
|
+
|
data/lib/tps/cli_reporter.rb
CHANGED
@@ -25,15 +25,23 @@ module TPS
|
|
25
25
|
# Columns
|
26
26
|
c1 = "%s %s %s" % [ indent, status, task.name ]
|
27
27
|
c2 = if task.feature? || task.milestone?
|
28
|
-
progress
|
28
|
+
"%6s %s" % [ points, progress ]
|
29
29
|
else
|
30
|
-
' '*
|
30
|
+
' '*19
|
31
31
|
end
|
32
32
|
|
33
33
|
pref = c("-"*80, 30)+"\n" if task.feature?
|
34
34
|
|
35
35
|
# Put together
|
36
|
-
"#{pref}" + "%-
|
36
|
+
"#{pref}" + "%-88s%s\n" % [ c1, c2 ]
|
37
|
+
end
|
38
|
+
|
39
|
+
def points
|
40
|
+
[
|
41
|
+
"%3i" % [ task.points_done ],
|
42
|
+
c("/", 30),
|
43
|
+
c("%-2i" % [ task.points ], 32)
|
44
|
+
].join ''
|
37
45
|
end
|
38
46
|
|
39
47
|
def color
|
data/lib/tps/importer.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module TPS
|
5
|
+
module Importer
|
6
|
+
def self.create(file, format=nil)
|
7
|
+
TPS::Importer::Trello.new file
|
8
|
+
end
|
9
|
+
|
10
|
+
class Base
|
11
|
+
def initialize(file)
|
12
|
+
@file = file
|
13
|
+
@tree = Hash.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_yaml
|
17
|
+
YAML::dump @tree
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Trello < Base
|
22
|
+
def initialize(file)
|
23
|
+
super
|
24
|
+
@data = JSON.parse(File.read(@file))
|
25
|
+
@tree = Hash.new
|
26
|
+
work!
|
27
|
+
end
|
28
|
+
|
29
|
+
def milestone
|
30
|
+
name = @data['name'] + ' milestone'
|
31
|
+
@tree[name] ||= Hash.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def archived
|
35
|
+
name = @data['name'] + ' archived milestone'
|
36
|
+
@tree[name] ||= Hash.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def work!
|
40
|
+
milestone
|
41
|
+
archived
|
42
|
+
|
43
|
+
@lists ||= Hash.new
|
44
|
+
@data['lists'].each do |list|
|
45
|
+
|
46
|
+
parent = list['closed'] ? archived : milestone
|
47
|
+
parent[list['name']] ||= Hash.new
|
48
|
+
|
49
|
+
@lists[list['id']] = parent[list['name']]
|
50
|
+
end
|
51
|
+
|
52
|
+
@data['cards'].each do |card|
|
53
|
+
parent = @lists[card['idList']]
|
54
|
+
|
55
|
+
labels = card['labels'].map { |l| l['name'].downcase }
|
56
|
+
|
57
|
+
value = Array.new
|
58
|
+
value << 'done' if labels.include?("done")
|
59
|
+
value << 'in progress' if labels.include?("in progress")
|
60
|
+
|
61
|
+
parent[card['name']] = value.empty? ? nil : value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
data/lib/tps/sprint.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module TPS
|
2
|
+
class Sprint
|
3
|
+
attr_accessor :id
|
4
|
+
attr_accessor :name
|
5
|
+
attr_accessor :list
|
6
|
+
|
7
|
+
def initialize(id, name, list)
|
8
|
+
@id = id
|
9
|
+
@name = name
|
10
|
+
@list = list
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns the numeric index of the sprint.
|
14
|
+
#
|
15
|
+
# s1.index #=> 0
|
16
|
+
# s2.index #=> 1
|
17
|
+
#
|
18
|
+
def index
|
19
|
+
list.sprints.keys.index id
|
20
|
+
end
|
21
|
+
|
22
|
+
def points
|
23
|
+
sublist ? sublist.points : 0.0
|
24
|
+
end
|
25
|
+
|
26
|
+
def points_done
|
27
|
+
sublist ? sublist.points_done : 0.0
|
28
|
+
end
|
29
|
+
|
30
|
+
def percent
|
31
|
+
sublist ? sublist.percent : 0.0
|
32
|
+
end
|
33
|
+
|
34
|
+
def sublist
|
35
|
+
@sublist ||= list.filter_by_sprint(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def slug
|
39
|
+
slugify id
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def slugify(str)
|
45
|
+
str.scan(/[A-Za-z0-9]+/).join('_').downcase
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/tps/task.rb
CHANGED
@@ -7,12 +7,18 @@ module TPS
|
|
7
7
|
attr_reader :tasks # Array of Tasks
|
8
8
|
attr_reader :owner
|
9
9
|
attr_reader :pivotal_id
|
10
|
+
attr_reader :trello_id
|
10
11
|
attr_reader :parent
|
12
|
+
attr_reader :tags
|
13
|
+
attr_reader :list # the root TaskList
|
14
|
+
attr_reader :id
|
11
15
|
|
12
|
-
def initialize(parent, name, data=nil)
|
16
|
+
def initialize(parent, name, data=nil, list)
|
13
17
|
@name = name
|
14
18
|
@tasks = Array.new
|
19
|
+
@tags = Array.new
|
15
20
|
@parent = parent
|
21
|
+
@list = list
|
16
22
|
|
17
23
|
if data.is_a?(Array)
|
18
24
|
tags = data
|
@@ -38,6 +44,9 @@ module TPS
|
|
38
44
|
# [pt/28394] -- Pivotal tracker
|
39
45
|
elsif t =~ /^pt\/(.*)$/i
|
40
46
|
@pivotal_id = $1.strip
|
47
|
+
# [tl/28394] -- Trello
|
48
|
+
elsif t =~ /^tr\/(.*)$/i
|
49
|
+
@trello_id = $1.strip
|
41
50
|
# [50%] -- percentage
|
42
51
|
elsif t =~ /^([\d\.]+)%$/
|
43
52
|
@status = :in_progress
|
@@ -45,13 +54,20 @@ module TPS
|
|
45
54
|
# [0pt] -- points
|
46
55
|
elsif t =~ /^([\d\.]+)pts?/i
|
47
56
|
@points = $1.strip.to_f
|
57
|
+
# [-all] -- tags
|
58
|
+
elsif %w[- #].include?(t[0])
|
59
|
+
@tags.push t[1..-1]
|
60
|
+
# Sprint name
|
61
|
+
elsif list && list.sprints[t]
|
62
|
+
@sprint = list.sprints[t]
|
48
63
|
end
|
49
64
|
end
|
50
65
|
|
51
|
-
@tasks = tasks.map { |task, data| Task.new self, task, data } if tasks
|
66
|
+
@tasks = tasks.map { |task, data| Task.new self, task, data, self.list } if tasks
|
52
67
|
|
53
|
-
|
54
|
-
|
68
|
+
@milestone = root? && is_milestone?(@name)
|
69
|
+
|
70
|
+
@id = list.get_id if list
|
55
71
|
end
|
56
72
|
|
57
73
|
def status
|
@@ -109,10 +125,109 @@ module TPS
|
|
109
125
|
tasks.any?
|
110
126
|
end
|
111
127
|
|
128
|
+
def sprint?
|
129
|
+
!! sprint
|
130
|
+
end
|
131
|
+
|
132
|
+
def sprint
|
133
|
+
@sprint || (parent && parent.sprint)
|
134
|
+
end
|
135
|
+
|
136
|
+
def contains_sprint?(sprint)
|
137
|
+
contains? { |t| t.sprint == sprint }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Filters a task list by tasks that match a given block, preserving its
|
141
|
+
# ancestors even if they don't match.
|
142
|
+
#
|
143
|
+
# @list.contains? { |t| t.done? }
|
144
|
+
# @list.contains? { |t| t.sprint == sprint }
|
145
|
+
#
|
146
|
+
def contains?(&blk)
|
147
|
+
blk.call(self) || tasks.detect { |t| t.contains?(&blk) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def ancestor?(&blk)
|
151
|
+
blk.call(self) || parent && parent.ancestor?(&blk)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Filters a task tree to tasks of a given criteria, preserving its
|
155
|
+
# ancestors even if they don't match.
|
156
|
+
#
|
157
|
+
# @list.filter { |t| t.done? }
|
158
|
+
#
|
159
|
+
def filter(&blk)
|
160
|
+
filter_by { |t| t.contains?(&blk) || t.ancestor?(&blk) }
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns a list of a task's ancestors, excluding the list.
|
164
|
+
def breadcrumbs(include_self=true)
|
165
|
+
arr = []
|
166
|
+
arr += [self] if include_self
|
167
|
+
arr = parent.breadcrumbs + arr if parent
|
168
|
+
arr
|
169
|
+
end
|
170
|
+
|
171
|
+
def css_class
|
172
|
+
[
|
173
|
+
"level-#{level}",
|
174
|
+
"status-#{status}",
|
175
|
+
(tasks? ? 'parent' : 'leaf'),
|
176
|
+
('root' if root?),
|
177
|
+
('feature' if feature?),
|
178
|
+
('milestone' if milestone?),
|
179
|
+
('subtask' if subtask?),
|
180
|
+
("sprint-#{sprint.slug}" if sprint?),
|
181
|
+
sprint_css_classes,
|
182
|
+
breadcrumbs(false).map { |t| "in_task_#{t.id}" }
|
183
|
+
].flatten.compact.join(' ')
|
184
|
+
end
|
185
|
+
|
186
|
+
def sprint_css_classes
|
187
|
+
list.sprints.values.map { |sprint|
|
188
|
+
"has_sprint-#{sprint.slug}" if contains_sprint?(sprint)
|
189
|
+
}.compact
|
190
|
+
end
|
191
|
+
|
192
|
+
def subtask?
|
193
|
+
!feature? && !milestone?
|
194
|
+
end
|
195
|
+
|
196
|
+
def filter_by_sprint(sprint)
|
197
|
+
filter { |t| t.sprint == sprint }
|
198
|
+
end
|
199
|
+
|
200
|
+
# Works like #filter, but only preserves ancestors if they match.
|
201
|
+
def filter_by(&blk)
|
202
|
+
return nil unless blk.call(self)
|
203
|
+
task = self.dup
|
204
|
+
task.instance_variable_set :@tasks, tasks.map { |t| t.filter_by(&blk) }.compact
|
205
|
+
task
|
206
|
+
end
|
207
|
+
|
112
208
|
def pivotal_url
|
113
209
|
"https://www.pivotaltracker.com/story/show/#{pivotal_id}" if pivotal_id
|
114
210
|
end
|
115
211
|
|
212
|
+
# Looks up a task.
|
213
|
+
#
|
214
|
+
# @list['Login']
|
215
|
+
#
|
216
|
+
def [](name)
|
217
|
+
tasks.detect { |t| t.name == name }
|
218
|
+
end
|
219
|
+
|
220
|
+
def trello_url
|
221
|
+
if trello_id
|
222
|
+
id = trello_id.match(/([A-Za-z0-9]+)$/) && $1.strip
|
223
|
+
if list.trello_board_url && id.match(/^[0-9]+/)
|
224
|
+
"#{list.trello_board_url}/#{id}"
|
225
|
+
else
|
226
|
+
"https://trello.com/c/#{id}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
116
231
|
def percent
|
117
232
|
if done?
|
118
233
|
1.0
|
@@ -142,6 +257,10 @@ module TPS
|
|
142
257
|
!! @milestone
|
143
258
|
end
|
144
259
|
|
260
|
+
def leaf?
|
261
|
+
tasks.empty?
|
262
|
+
end
|
263
|
+
|
145
264
|
# - list.walk do |task, recurse|
|
146
265
|
# %ul
|
147
266
|
# %li
|
@@ -162,5 +281,14 @@ module TPS
|
|
162
281
|
tpl = Tilt.new(template)
|
163
282
|
tpl.evaluate({}, list: self)
|
164
283
|
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
def is_milestone?(str)
|
288
|
+
str = str.to_s.downcase
|
289
|
+
str.match(/^milestone|milestone$/i) ||
|
290
|
+
str.match(/^release|release$/i) ||
|
291
|
+
str.match(/^version|version$/i)
|
292
|
+
end
|
165
293
|
end
|
166
294
|
end
|
data/lib/tps/task_list.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
module TPS
|
2
|
+
# The root node.
|
2
3
|
class TaskList < Task
|
4
|
+
attr_accessor :tasks
|
5
|
+
attr_accessor :sprints
|
6
|
+
attr_accessor :trello_board_url
|
7
|
+
|
3
8
|
def initialize(options)
|
4
|
-
super nil, nil
|
9
|
+
super nil, nil, nil, nil
|
5
10
|
|
6
11
|
data = if options[:yaml]
|
7
12
|
YAML::load_file options[:yaml]
|
@@ -11,7 +16,22 @@ module TPS
|
|
11
16
|
options
|
12
17
|
end
|
13
18
|
|
14
|
-
|
19
|
+
sprint_data = data.delete('Sprints') || {}
|
20
|
+
@sprints = Hash[*sprint_data.map { |id, name| [id, Sprint.new(id, name, self)] }.flatten]
|
21
|
+
|
22
|
+
@trello_board_url = data.delete('Trello URL')
|
23
|
+
|
24
|
+
@tasks = data.map { |task, data| Task.new nil, task, data, self }
|
25
|
+
end
|
26
|
+
|
27
|
+
def sprints?
|
28
|
+
sprints.any?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a fresh ID. (internal)
|
32
|
+
def get_id
|
33
|
+
@task_count ||= 0
|
34
|
+
@task_count += 1
|
15
35
|
end
|
16
36
|
end
|
17
37
|
end
|
data/lib/tps/version.rb
CHANGED
data/lib/tps.rb
CHANGED
data/test/hello.yml
CHANGED
data/test/sprint_test.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class SprintTest < UnitTest
|
4
|
+
setup do
|
5
|
+
@list = TPS::TaskList.new yaml: f('sprints.yml')
|
6
|
+
@milestone = @list.tasks.first
|
7
|
+
@s1 = @list.sprints['s1']
|
8
|
+
@s2 = @list.sprints['s2']
|
9
|
+
end
|
10
|
+
|
11
|
+
test "Sprints" do
|
12
|
+
assert @list.sprints.size == 2
|
13
|
+
assert @s1.name == 'Sprint one'
|
14
|
+
assert @s2.name == 'Sprint two'
|
15
|
+
end
|
16
|
+
|
17
|
+
test "Sprint model attributes" do
|
18
|
+
sprint = @list.sprints['s1']
|
19
|
+
assert sprint.name == 'Sprint one'
|
20
|
+
assert sprint.list == @list
|
21
|
+
end
|
22
|
+
|
23
|
+
test "Tasks should be assigned to sprints" do
|
24
|
+
assert @list['Version 1']['Account']['Login'].sprint == @s1
|
25
|
+
end
|
26
|
+
|
27
|
+
test "Task#contains_sprint?" do
|
28
|
+
assert @list.contains_sprint?(@s1)
|
29
|
+
assert @list.contains_sprint?(@s2)
|
30
|
+
end
|
31
|
+
|
32
|
+
test "Task#contains_sprint? part 2" do
|
33
|
+
task = @list['Version 1']['Account']['Login']
|
34
|
+
assert task.contains_sprint?(@s1)
|
35
|
+
assert ! task.contains_sprint?(@s2)
|
36
|
+
end
|
37
|
+
|
38
|
+
test "Task#filter_by_sprint" do
|
39
|
+
list = @list.filter_by_sprint(@s1)
|
40
|
+
assert ! list['Version 1']['Account']['Login'].nil?
|
41
|
+
assert list['Version 1']['Account']['Signup'].nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
test "Sub-tasks of a sprint task" do
|
45
|
+
list = @list.filter_by_sprint(@s1)
|
46
|
+
task = list['Version 1']['Comments']['Creating']
|
47
|
+
assert ! task.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
test "Sprint#index" do
|
51
|
+
assert @list.sprints['s1'].index == 0
|
52
|
+
assert @list.sprints['s2'].index == 1
|
53
|
+
end
|
54
|
+
|
55
|
+
test "Sprint#points" do
|
56
|
+
assert @s1.points == 5
|
57
|
+
end
|
58
|
+
|
59
|
+
test "Sprint#points_done" do
|
60
|
+
assert @s1.points_done == 3
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
data/test/sprints.yml
ADDED
data/test/tps_test.rb
CHANGED
@@ -61,6 +61,12 @@ class MyTest < UnitTest
|
|
61
61
|
assert_equal 0.25, task.percent
|
62
62
|
end
|
63
63
|
|
64
|
+
test "Progress override" do
|
65
|
+
task = @milestone.tasks[7]
|
66
|
+
assert_equal 0.2, task.percent
|
67
|
+
assert_equal 0.4, task.points_done
|
68
|
+
end
|
69
|
+
|
64
70
|
test "Milestone" do
|
65
71
|
assert @milestone.milestone?
|
66
72
|
end
|
@@ -68,4 +74,25 @@ class MyTest < UnitTest
|
|
68
74
|
test "HTML works" do
|
69
75
|
assert @list.to_html
|
70
76
|
end
|
77
|
+
|
78
|
+
test "Lookup" do
|
79
|
+
assert @list['Milestone 1'] == @milestone
|
80
|
+
end
|
81
|
+
|
82
|
+
test "Task#breadcrumbs" do
|
83
|
+
crumbs = @list['Milestone 1']['User login']['Signup'].breadcrumbs
|
84
|
+
assert crumbs == [
|
85
|
+
@list['Milestone 1'],
|
86
|
+
@list['Milestone 1']['User login'],
|
87
|
+
@list['Milestone 1']['User login']['Signup']
|
88
|
+
]
|
89
|
+
end
|
90
|
+
|
91
|
+
test "Task#breadcrumbs(false)" do
|
92
|
+
crumbs = @list['Milestone 1']['User login']['Signup'].breadcrumbs(false)
|
93
|
+
assert crumbs == [
|
94
|
+
@list['Milestone 1'],
|
95
|
+
@list['Milestone 1']['User login']
|
96
|
+
]
|
97
|
+
end
|
71
98
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tps_reporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: tilt
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: contest
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,7 +37,12 @@ dependencies:
|
|
32
37
|
version: '0'
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
description: A YAML-powered, simple command-line task report builder.
|
37
47
|
email:
|
38
48
|
- rico@sinefunc.com
|
@@ -41,6 +51,8 @@ executables:
|
|
41
51
|
extensions: []
|
42
52
|
extra_rdoc_files: []
|
43
53
|
files:
|
54
|
+
- .gitignore
|
55
|
+
- GUIDE.md
|
44
56
|
- HISTORY.md
|
45
57
|
- README.md
|
46
58
|
- Rakefile
|
@@ -49,10 +61,14 @@ files:
|
|
49
61
|
- data/sample.yml
|
50
62
|
- lib/tps.rb
|
51
63
|
- lib/tps/cli_reporter.rb
|
64
|
+
- lib/tps/importer.rb
|
65
|
+
- lib/tps/sprint.rb
|
52
66
|
- lib/tps/task.rb
|
53
67
|
- lib/tps/task_list.rb
|
54
68
|
- lib/tps/version.rb
|
55
69
|
- test/hello.yml
|
70
|
+
- test/sprint_test.rb
|
71
|
+
- test/sprints.yml
|
56
72
|
- test/test_helper.rb
|
57
73
|
- test/tps_test.rb
|
58
74
|
- tps_reporter.gemspec
|
@@ -76,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
92
|
version: '0'
|
77
93
|
requirements: []
|
78
94
|
rubyforge_project:
|
79
|
-
rubygems_version: 1.8.
|
95
|
+
rubygems_version: 1.8.23
|
80
96
|
signing_key:
|
81
97
|
specification_version: 3
|
82
98
|
summary: Task progress sheet reporter.
|