tps_reporter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md ADDED
@@ -0,0 +1,4 @@
1
+ v0.0.2 - Feb 04, 2012
2
+ ---------------------
3
+
4
+ Initial.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Task progress sheet reporter
2
+
3
+ ![TPS report](https://img.skitch.com/20120203-nr24dn9u7euchmqa516718unpe.png)
4
+
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.
7
+
8
+ Get started
9
+ -----------
10
+
11
+ Install TPS (Ruby):
12
+
13
+ $ gem install tps_reporter
14
+
15
+ ...then generate a sample file. (or create `tasks.yml` based on [this][s]
16
+ sample.)
17
+
18
+ $ tps sample
19
+
20
+ Edit it, then generate the report:
21
+
22
+ $ tps open
23
+
24
+ [s]: https://github.com/rstacruz/tps_reporter/blob/master/data/sample.yml
25
+
26
+ Format
27
+ ------
28
+
29
+ The tasks file, usually `tasks.yml`, is in YAML format.
30
+
31
+ Tasks are always keys (ie, they all end in `:`). They can be nested as far
32
+ as you like.
33
+
34
+ ``` yaml
35
+ Edit users:
36
+ Register and signup:
37
+ Login and logout:
38
+ ```
39
+
40
+ To define task metadata for *leaf* tasks, add it as an array inside the task:
41
+
42
+ ``` yaml
43
+ Manage employees: [done]
44
+ ```
45
+
46
+ Or for *branch* tasks, add it under the `_` task:
47
+
48
+ ``` yaml
49
+ Manage employees:
50
+ _: [done]
51
+ Creating employees:
52
+ Editing employees:
53
+ ```
54
+
55
+ The metadata is just a simple YAML array that you can conveniently define using
56
+ `[tag1, tag2, etc]`. Allowed metadata are:
57
+
58
+ - `done`
59
+ - `in progress`
60
+ - `pt/2839478` *(Pivotal tracker ID. Links to a Pivotal tracker story.)*
61
+ - `0pt` *(points; influences percentage. needs to end in __pt__ or __pts__.)*
62
+ - `10%` *(task progress. implies __in progress__.)*
63
+
64
+ Example:
65
+
66
+ ``` yaml
67
+ Creating employees: [40%]
68
+ Editing employees: [done, 2pts]
69
+ ```
70
+
71
+ Exporting to PDF or image
72
+ -------------------------
73
+
74
+ If you're on a Mac, install [Paparazzi](http://derailer.org/paparazzi)
75
+ and use the `tps paparazzi` command. This will open the report in Paparazzi
76
+ where you can save or copy it as an image, or PDF.
77
+
78
+ Command line
79
+ ------------
80
+
81
+ There's also a command line reporter that you can access via `tps print`. It
82
+ looks like this:
83
+
84
+ ![Comamnd line reporter][cli]
85
+
86
+ [cli]: https://img.skitch.com/20120204-ccb2guerhrjmj3rht3e4ies4ur.png
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ desc "Invokes the test suite in multiple RVM environments"
2
+ task :'test!' do
3
+ # Override this by adding RVM_TEST_ENVS=".." in .rvmrc
4
+ envs = ENV['RVM_TEST_ENVS'] || '1.9.2@sinatra,1.8.7@sinatra'
5
+ puts "* Testing in the following RVM environments: #{envs.gsub(',', ', ')}"
6
+ system "rvm #{envs} rake test" or abort
7
+ end
8
+
9
+ desc "Runs tests"
10
+ task :test do
11
+ Dir['test/*_test.rb'].each { |f| load f }
12
+ end
13
+
14
+ task :default => :test
data/bin/tps ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'tps'
4
+
5
+ module Params
6
+ def extract(what) i = index(what) and slice!(i, 2)[1] end;
7
+ def first_is(what) shift if first == what; end
8
+ end
9
+
10
+ ARGV.extend Params
11
+
12
+ module TPS::Command
13
+ extend self
14
+
15
+ def help
16
+ puts "Usage: tps <command> [-f filename] [-o output]"
17
+ puts ""
18
+ puts "Commands:"
19
+ puts " html Builds HTML"
20
+ puts " open Builds HTML and opens it in the browser"
21
+ puts " paparazzi Builds HTML and opens it in Paparazzi (Mac)"
22
+ puts " print Prints the report to the console."
23
+ puts ""
24
+ puts "Options (optional):"
25
+ puts " -f FILE Specifies the input file. Defaults to tasks.yml."
26
+ puts " -o/--output OUTPUT Specifies the output HTML file."
27
+ puts ""
28
+ end
29
+
30
+ def html
31
+ t = get_tasks
32
+ path = output { |file| file.write t.to_html }
33
+ info "Wrote to '#{path}'."
34
+
35
+ path
36
+ end
37
+
38
+ def sample
39
+ require 'fileutils'
40
+ fn = tasks_filename
41
+
42
+ if File.exists?(fn)
43
+ err "Error: #{fn} already exists."
44
+ exit 130
45
+ end
46
+
47
+ FileUtils.cp TPS.root('data', 'sample.yml'), fn
48
+ info "Created '#{fn}'."
49
+ info "Edit it, then use `tps html` to generate HTML from it."
50
+ end
51
+
52
+ def open
53
+ fn = html
54
+ open_file fn
55
+ end
56
+
57
+ def paparazzi
58
+ fn = html
59
+ open_file "paparazzi:(minwidth=1,minheight=1)#{fn}"
60
+ end
61
+
62
+ def print
63
+ t = get_tasks
64
+ reporter = TPS::CliReporter.new(t)
65
+ reporter.print
66
+ end
67
+
68
+ private
69
+ def err(str)
70
+ $stdout << "#{str}\n"
71
+ end
72
+
73
+ def info(str)
74
+ puts str
75
+ end
76
+
77
+ def tasks_filename
78
+ ARGV.extract('-f') ||
79
+ ENV['TPS_FILE'] ||
80
+ Dir['./{Tasksfile,tasks.yml}'].first ||
81
+ "tasks.yml"
82
+ end
83
+
84
+ def output(&blk)
85
+ fn = ARGV.extract('--output') || ARGV.extract('-o') || get_temp_filename
86
+
87
+ File.open(fn, 'w', &blk)
88
+ fn
89
+ end
90
+
91
+ def get_tasks
92
+ fn = tasks_filename
93
+ if !File.exists?(fn)
94
+ err "No tasks file found."
95
+ err "Create a sample using `tsp sample`."
96
+ exit 256
97
+ end
98
+
99
+ begin
100
+ TPS::TaskList.new yaml: fn
101
+ rescue => e
102
+ err "Parse error: #{e.message}"
103
+ exit 256
104
+ end
105
+ end
106
+
107
+ def opener
108
+ program = %w[open xdg-open start].detect { |cmd| `which #{cmd}` }
109
+ unless program
110
+ err "No opener found."
111
+ exit 256
112
+ end
113
+
114
+ program
115
+ end
116
+
117
+ def open_file(file)
118
+ require 'shellwords'
119
+ system "#{opener} #{file.shellescape}"
120
+ end
121
+
122
+ def get_temp_filename
123
+ require 'tmpdir'
124
+ File.join Dir.tmpdir, "tasks-#{'%x' % [rand * 2**48]}.html"
125
+ end
126
+ end
127
+
128
+ if ARGV.first_is('html')
129
+ TPS::Command.html
130
+
131
+ elsif ARGV.first_is('open')
132
+ TPS::Command.open
133
+
134
+ elsif ARGV.first_is('sample')
135
+ TPS::Command.sample
136
+
137
+ elsif ARGV.first_is('print')
138
+ TPS::Command.print
139
+
140
+ elsif ARGV.first_is('paparazzi')
141
+ TPS::Command.paparazzi
142
+
143
+ else
144
+ TPS::Command.help
145
+ end
data/data/index.haml ADDED
@@ -0,0 +1,182 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %script{src: "http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7/jquery.min.js"}
5
+
6
+ :javascript
7
+ $("td").live('click', function() {
8
+ $(this).closest('tr').toggleClass('highlight');
9
+ });
10
+
11
+ %style
12
+ :plain
13
+ body {
14
+ padding: 0;
15
+ margin: 0; }
16
+
17
+ body, td {
18
+ font-family: pt sans, sans-serif;
19
+ color: #333;
20
+ line-height: 13.5pt;
21
+ font-size: 9pt; }
22
+
23
+ table {
24
+ border: solid 2px #aaa;
25
+ padding: 2px;
26
+
27
+ background: #fafafa;
28
+ margin: 0;
29
+ width: 600px;
30
+ border-collapse: collapse; }
31
+
32
+ table td, table th {
33
+ padding: 5px 10px;
34
+ border-top: solid 1px #eee; }
35
+
36
+ /* Columns */
37
+ tr>.task { width: 50%; text-align: left; }
38
+ tr>.points { width: 9%; }
39
+ tr>.progress { width: 18%; }
40
+ tr>.owner { display: none; }
41
+
42
+ /* Indentation */
43
+ .level-1 .task { padding-left: 25px; }
44
+ .level-2 .task { padding-left: 50px; }
45
+ .level-3 .task { padding-left: 75px; }
46
+ .level-4 .task { padding-left: 100px; }
47
+
48
+ /* Overrides for parents */
49
+ .milestone td.task,
50
+ .feature td.task {
51
+ font-weight: bold;
52
+ font-size: 1.1em; }
53
+
54
+ tr.milestone td,
55
+ tr.feature td {
56
+ border-top: solid 1px #888; }
57
+
58
+ tr .progress .bar {
59
+ display: none; }
60
+
61
+ tr.milestone .progress .bar,
62
+ tr.feature .progress .bar {
63
+ display: block; }
64
+
65
+ tr td.points>* {
66
+ display: none; }
67
+
68
+ tr.milestone td.points>*,
69
+ tr.feature td.points>* {
70
+ display: inline; }
71
+
72
+ /* Header */
73
+ thead {
74
+ display: none; }
75
+
76
+ th {
77
+ color: #888; }
78
+
79
+ /* Zebra */
80
+ tr td,
81
+ tr {
82
+ background: #ffffff; }
83
+
84
+ tr:nth-child(odd) td,
85
+ tr:nth-child(odd) {
86
+ background: #fafafa; }
87
+
88
+ tr.highlight,
89
+ tr.highlight td {
90
+ background: #fafae0; }
91
+
92
+ /* Status box */
93
+ span.status {
94
+ display: inline-block;
95
+ width: 12px;
96
+ height: 12px;
97
+ margin: 0 5px 0 0;
98
+
99
+ border-radius: 2px;
100
+
101
+ position: relative;
102
+ top: 2px;
103
+ background: #ddd; }
104
+
105
+ span.status.in_progress {
106
+ background: #ea3; }
107
+
108
+ span.status.done {
109
+ background: #393; }
110
+
111
+ /* Progress */
112
+ .progress .number {
113
+ display: none; }
114
+
115
+ .progress .bar {
116
+ height: 10px;
117
+ border-radius: 5px;
118
+ background: #ddd; }
119
+
120
+ .progress .bar span {
121
+ display: block;
122
+ height: 10px;
123
+ border-radius: 5px;
124
+ background: #888; }
125
+
126
+ a.meta {
127
+ font-size: 0.9em;
128
+ text-decoration: none;
129
+
130
+ background: #ddd;
131
+ padding: 1px 3px;
132
+ border-radius: 2px;
133
+ border-bottom: solid 1px #ccc;
134
+
135
+ margin: 0 5px;
136
+ color: #777; }
137
+
138
+ /* Progress */
139
+ td.points {
140
+ text-align: left;
141
+ font-size: 0.9em; }
142
+
143
+ td.points .points.done {
144
+ font-weight: bold; }
145
+
146
+ td.points .of,
147
+ td.points .points.total {
148
+ color: #888; }
149
+
150
+ %body
151
+
152
+ %table
153
+ %thead
154
+ %tr
155
+ %th.task Task
156
+ %th.owner Owner
157
+ %th.progress Progress
158
+ %th.points Points
159
+
160
+ - list.walk do |task, recurse|
161
+
162
+ %tr{class: "level-#{task.level} #{task.tasks? ? 'parent' : 'leaf'} #{'root' if task.root?} #{'feature' if task.feature?} #{'milestone' if task.milestone?}"}
163
+ %td.task
164
+ %span.status{class: "#{task.status}"}
165
+ = task
166
+ - if task.pivotal_id
167
+ %a.meta{href: task.pivotal_url}= "Pivotal: #{task.pivotal_id}"
168
+
169
+ %td.owner
170
+ = task.owner
171
+
172
+ %td.progress
173
+ .bar
174
+ %span{style: "width: #{(task.percent*95+5).to_i}%"}
175
+ %span.number= "#{(task.percent*100).to_i}%"
176
+
177
+ %td.points
178
+ %span.points.done= task.points_done.round(1).to_s.gsub(/\.0+$/,'')
179
+ %span.of of
180
+ %span.points.total= task.points.round(1).to_s.gsub(/\.0+$/,'')
181
+
182
+ - recurse.call if recurse
data/data/sample.yml ADDED
@@ -0,0 +1,18 @@
1
+ Version 1:
2
+
3
+ User signup:
4
+ Register for an account:
5
+ Log in: [done]
6
+ Forget password:
7
+
8
+ Manage users:
9
+ _: [in progress]
10
+ Create users: [in progress]
11
+ Delete users:
12
+ User profile page:
13
+
14
+ Blog:
15
+ Creating new posts: [done]
16
+ Comments: [done]
17
+ Moderating comments: [done]
18
+
@@ -0,0 +1,76 @@
1
+ module TPS
2
+ class CliReporter
3
+ attr_reader :task
4
+
5
+ def initialize(task)
6
+ @task = task
7
+ end
8
+
9
+ def print
10
+ puts report
11
+ end
12
+
13
+ def report
14
+ re = ""
15
+ task.walk do |t, recurse|
16
+ re += CliReporter.new(t).report_task
17
+ recurse.call if recurse
18
+ end
19
+ re
20
+ end
21
+
22
+ def report_task
23
+ indent = ' ' * (4 * task.level)
24
+
25
+ # Columns
26
+ c1 = "%s %s %s" % [ indent, status, task.name ]
27
+ c2 = if task.feature? || task.milestone?
28
+ progress
29
+ else
30
+ ' '*12
31
+ end
32
+
33
+ pref = c("-"*80, 30)+"\n" if task.feature?
34
+
35
+ # Put together
36
+ "#{pref}" + "%-95s%s\n" % [ c1, c2 ]
37
+ end
38
+
39
+ def color
40
+ if task.done?
41
+ 32
42
+ elsif task.in_progress?
43
+ 34
44
+ else
45
+ 30
46
+ end
47
+ end
48
+
49
+ def status
50
+ l = c("(", 30)
51
+ r = c(")", 30)
52
+ if task.done?
53
+ l + c('##', color) + r
54
+ elsif task.in_progress?
55
+ l + c('--', color) + r
56
+ else
57
+ l + c(' ', color) + r
58
+ end
59
+ end
60
+
61
+ def progress
62
+ max = 12
63
+ len = (task.percent * max).to_i
64
+
65
+ prog = ("%-#{max}s" % ["="*len])
66
+ prog = c("|"*len, color) + c("."*(max-len), 30)
67
+
68
+ prog
69
+ end
70
+
71
+ private
72
+ def c(str, c=nil)
73
+ c ? "\033[#{c}m#{str}\033[0m" : str
74
+ end
75
+ end
76
+ end
data/lib/tps/task.rb ADDED
@@ -0,0 +1,166 @@
1
+ require 'yaml'
2
+ require 'tilt'
3
+
4
+ module TPS
5
+ class Task
6
+ attr_reader :name # "Create metrics"
7
+ attr_reader :tasks # Array of Tasks
8
+ attr_reader :owner
9
+ attr_reader :pivotal_id
10
+ attr_reader :parent
11
+
12
+ def initialize(parent, name, data=nil)
13
+ @name = name
14
+ @tasks = Array.new
15
+ @parent = parent
16
+
17
+ if data.is_a?(Array)
18
+ tags = data
19
+ tasks = nil
20
+ elsif data.is_a?(Hash) && data['_']
21
+ tags = data.delete('_')
22
+ tasks = data
23
+ else
24
+ tags = Array.new
25
+ tasks = data
26
+ end
27
+
28
+ # Parse tags.
29
+ tags.each do |t|
30
+ # [done]
31
+ if ['done', 'ok'].include?(t)
32
+ @status = :done
33
+ elsif ['in progress', '...'].include?(t)
34
+ @status = :in_progress
35
+ # [@rstacruz]
36
+ elsif t =~ /^@/
37
+ @owner = t[1..-1]
38
+ # [pt/28394] -- Pivotal tracker
39
+ elsif t =~ /^pt\/(.*)$/i
40
+ @pivotal_id = $1.strip
41
+ # [50%] -- percentage
42
+ elsif t =~ /^([\d\.]+)%$/
43
+ @status = :in_progress
44
+ @percent = $1.strip.to_f / 100
45
+ # [0pt] -- points
46
+ elsif t =~ /^([\d\.]+)pts?/i
47
+ @points = $1.strip.to_f
48
+ end
49
+ end
50
+
51
+ @tasks = tasks.map { |task, data| Task.new self, task, data } if tasks
52
+
53
+ n = @name.to_s.downcase
54
+ @milestone = root? && (n.include?('milestone') || n.include?('version'))
55
+ end
56
+
57
+ def status
58
+ # If no status is given, infer the status based on tasks.
59
+ if !@status && tasks?
60
+ if all_tasks_done?
61
+ return :done
62
+ elsif has_started_tasks?
63
+ return :in_progress
64
+ end
65
+ end
66
+
67
+ @status or :unstarted
68
+ end
69
+
70
+ def points
71
+ if @points
72
+ @points
73
+ elsif tasks?
74
+ tasks.inject(0.0) { |pts, task| pts + task.points }
75
+ else
76
+ 1.0
77
+ end
78
+ end
79
+
80
+ def points_done
81
+ points * percent
82
+ end
83
+
84
+ def all_tasks_done?
85
+ tasks? and !tasks.any? { |t| ! t.done? }
86
+ end
87
+
88
+ def has_started_tasks?
89
+ tasks? and tasks.any? { |t| t.in_progress? or t.done? }
90
+ end
91
+
92
+ def to_s
93
+ name
94
+ end
95
+
96
+ def done?
97
+ status == :done
98
+ end
99
+
100
+ def in_progress?
101
+ status == :in_progress
102
+ end
103
+
104
+ def unstarted?
105
+ status == :unstarted
106
+ end
107
+
108
+ def tasks?
109
+ tasks.any?
110
+ end
111
+
112
+ def pivotal_url
113
+ "https://www.pivotaltracker.com/story/show/#{pivotal_id}" if pivotal_id
114
+ end
115
+
116
+ def percent
117
+ if done?
118
+ 1.0
119
+ elsif @percent
120
+ @percent
121
+ elsif tasks?
122
+ total = tasks.inject(0.0) { |pts, task| pts + task.points }
123
+ tasks.inject(0) { |i, task| i + task.points_done } / total
124
+ else
125
+ in_progress? ? 0.5 : 0
126
+ end
127
+ end
128
+
129
+ def level
130
+ parent ? parent.level + 1 : 0
131
+ end
132
+
133
+ def root?
134
+ ! parent
135
+ end
136
+
137
+ def feature?
138
+ root? or parent.milestone?
139
+ end
140
+
141
+ def milestone?
142
+ !! @milestone
143
+ end
144
+
145
+ # - list.walk do |task, recurse|
146
+ # %ul
147
+ # %li
148
+ # = task
149
+ # - recurse.call if recurse
150
+ def walk(&blk)
151
+ tasks.each do |task|
152
+ yield task, lambda {
153
+ task.walk { |t, recurse| blk.call t, recurse }
154
+ }
155
+ end
156
+ end
157
+
158
+ def to_html(template=nil)
159
+ require 'tilt'
160
+ template ||= TPS.root('data', 'index.haml')
161
+
162
+ tpl = Tilt.new(template)
163
+ tpl.evaluate({}, list: self)
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,17 @@
1
+ module TPS
2
+ class TaskList < Task
3
+ def initialize(options)
4
+ super nil, nil
5
+
6
+ data = if options[:yaml]
7
+ YAML::load_file options[:yaml]
8
+ elsif options[:data]
9
+ options[:data]
10
+ else
11
+ options
12
+ end
13
+
14
+ @tasks = data.map { |task, data| Task.new nil, task, data }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ module TPS
2
+ def self.version
3
+ "0.0.2"
4
+ end
5
+ end
data/lib/tps.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'yaml'
2
+ require 'tilt'
3
+
4
+ # Common usage:
5
+ #
6
+ # list = TPS::TaskList.new data: yaml_data
7
+ # list = TPS::TaskList.new yaml: filename
8
+ #
9
+ # # Returns an array of tasks.
10
+ # task.tasks
11
+ #
12
+ # # Metadata:
13
+ # task.name
14
+ # task.owner
15
+ # task.status
16
+ #
17
+ # task.done?
18
+ # task.in_progress?
19
+ #
20
+ # list.to_html
21
+ #
22
+ module TPS
23
+ ROOT = File.expand_path('../../', __FILE__)
24
+
25
+ autoload :Task, 'tps/task'
26
+ autoload :TaskList, 'tps/task_list'
27
+ autoload :CliReporter, 'tps/cli_reporter'
28
+
29
+ require 'tps/version'
30
+
31
+ def self.root(*a)
32
+ File.join ROOT, *a
33
+ end
34
+ end
data/test/hello.yml ADDED
@@ -0,0 +1,42 @@
1
+ Milestone 1:
2
+ First task: [25%] #0
3
+
4
+ User login: #1
5
+ Login:
6
+ Signup:
7
+
8
+ Overridden percent: #2
9
+ _: [50%]
10
+ one: [done]
11
+ two: [done]
12
+ three: [done]
13
+
14
+ Explicit points: #3
15
+ _: [15pt]
16
+ one: [done]
17
+ two: [done]
18
+ three: [done]
19
+ four:
20
+
21
+ Compound points: #4
22
+ one:
23
+ two:
24
+ three:
25
+ sub1: [3pts, done]
26
+ sub2:
27
+
28
+ Point rescaling: #5
29
+ # Has 6 sub points, but will be rescaled to 8.
30
+ # That is, the done "3" point task is actually worth 4 points.
31
+ one:
32
+ _: [8pts]
33
+ sub1: [3pts, done]
34
+ sub2: [1pt]
35
+ sub3: [1pt]
36
+ sub4: [1pt]
37
+
38
+ In progress: #6
39
+ one:
40
+ two: [in progress]
41
+
42
+ Milestone 2:
@@ -0,0 +1,13 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'contest'
3
+ require 'tps'
4
+
5
+ class UnitTest < Test::Unit::TestCase
6
+ def f(*a)
7
+ File.join fixture_root, *a
8
+ end
9
+
10
+ def fixture_root
11
+ File.expand_path('../', __FILE__)
12
+ end
13
+ end
data/test/tps_test.rb ADDED
@@ -0,0 +1,71 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class MyTest < UnitTest
4
+ setup do
5
+ @list = TPS::TaskList.new yaml: f('hello.yml')
6
+ @milestone = @list.tasks.first
7
+ end
8
+
9
+ test "Has tasks" do
10
+ assert @list.tasks?
11
+ assert @list.tasks.size == 2
12
+ assert @list.tasks.first.tasks.size >= 2
13
+ end
14
+
15
+ test "Explicit percent" do
16
+ task = @milestone.tasks[0]
17
+ assert task.in_progress?
18
+ assert task.status == :in_progress
19
+ assert task.percent == 0.25
20
+ end
21
+
22
+ test "Overriding percent" do
23
+ task = @milestone.tasks[2]
24
+ assert task.name == "Overridden percent"
25
+ assert task.in_progress?
26
+ assert task.status == :in_progress
27
+ assert task.percent == 0.5
28
+ end
29
+
30
+ test "Points" do
31
+ task = @milestone.tasks[1]
32
+ assert task.points == 2.0
33
+ end
34
+
35
+ test "Explicit points" do
36
+ task = @milestone.tasks[3]
37
+ assert task.points == 15
38
+ assert task.percent == 0.75
39
+ assert task.points_done == 11.25
40
+ end
41
+
42
+ test "Compound points" do
43
+ task = @milestone.tasks[4]
44
+ assert task.points == 6
45
+ assert task.percent == 0.50
46
+ end
47
+
48
+ test "Point rescaling" do
49
+ task = @milestone.tasks[5]
50
+ assert task.points == 8
51
+ assert task.points_done == 4.0
52
+ assert task.percent == 0.5
53
+ end
54
+
55
+ test "In progress" do
56
+ task = @milestone.tasks[6]
57
+ assert_equal 2, task.tasks.size
58
+ assert task.tasks[1].in_progress?
59
+ assert_equal 0.5, task.tasks[1].percent
60
+ assert_equal 0.5, task.tasks[1].points_done
61
+ assert_equal 0.25, task.percent
62
+ end
63
+
64
+ test "Milestone" do
65
+ assert @milestone.milestone?
66
+ end
67
+
68
+ test "HTML works" do
69
+ assert @list.to_html
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ require './lib/tps/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "tps_reporter"
5
+ s.version = TPS.version
6
+ s.summary = %{Task progress sheet reporter.}
7
+ s.description = %Q{A YAML-powered, simple command-line task report builder.}
8
+ s.authors = ["Rico Sta. Cruz"]
9
+ s.email = ["rico@sinefunc.com"]
10
+ s.homepage = "http://github.com/rstacruz/tps_reporter"
11
+ s.files = `git ls-files`.strip.split("\n")
12
+ s.executables = Dir["bin/*"].map { |f| File.basename(f) }
13
+
14
+ s.add_dependency "tilt"
15
+ s.add_development_dependency "contest"
16
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tps_reporter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rico Sta. Cruz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-04 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: tilt
16
+ requirement: &2152658640 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2152658640
25
+ - !ruby/object:Gem::Dependency
26
+ name: contest
27
+ requirement: &2152658200 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2152658200
36
+ description: A YAML-powered, simple command-line task report builder.
37
+ email:
38
+ - rico@sinefunc.com
39
+ executables:
40
+ - tps
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - HISTORY.md
45
+ - README.md
46
+ - Rakefile
47
+ - bin/tps
48
+ - data/index.haml
49
+ - data/sample.yml
50
+ - lib/tps.rb
51
+ - lib/tps/cli_reporter.rb
52
+ - lib/tps/task.rb
53
+ - lib/tps/task_list.rb
54
+ - lib/tps/version.rb
55
+ - test/hello.yml
56
+ - test/test_helper.rb
57
+ - test/tps_test.rb
58
+ - tps_reporter.gemspec
59
+ homepage: http://github.com/rstacruz/tps_reporter
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.10
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Task progress sheet reporter.
83
+ test_files: []