saga 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: beaae8ecc51e2ef96e5343ffc064ab3290f490f722e44c68fa3a766986161371
4
- data.tar.gz: 9ccabb4c4e6ea3563d893bfe04054aebfd954fd26e825415bb56735003d64f08
3
+ metadata.gz: c5c5f033278bff0ecbe1f20538b7bd941c332e656a3c6ae64f88622f3efe2510
4
+ data.tar.gz: 4f8d452bb4d7ddf1acbe244ca6c91ff8d02803134476386f2fdabcd94daeeebe
5
5
  SHA512:
6
- metadata.gz: add20ee8c3436a11c6141ec3e9e9ed82c9a16f4965894bb044633b5846cc8df4fe047f6b81fe12aad6ef167a65426b1a8f98c643e0a23a8110adb5bbbcf6bd9a
7
- data.tar.gz: 0772c7c89c5cd0ae6f1f6b1bc1aad3e3a48e44163fed93d6104c5e1ecb5e3d42ea052674bcec8c5ecf87e034217aad2e72f5a4afc301d1f38ef0ae66cd7cf23a
6
+ metadata.gz: 4eedabed8c5770263a74a8578bdf5f4cb82f19834704780174da9e739c6e6812e1e829c84f69e7812d962dab2c3b017ccf9b2c1342ce5c5b385bec8f53e44cd3
7
+ data.tar.gz: 5f0472e1e7f857c3f637882a29c9170e554f21b1c8f1a372b3e98017c613f9823e65b5f38be2e66c70827307c0f10037473b90f212063a3c78c91bd29b5e83dc
data/lib/saga/planning.rb CHANGED
@@ -48,6 +48,16 @@ module Saga
48
48
  range_estimated
49
49
  end
50
50
 
51
+ def relative_estimated
52
+ relative_estimated = 0
53
+ @document.stories_as_flat_list.each do |story|
54
+ if story[:estimate] && story[:estimate][1] == :relative
55
+ relative_estimated += 1
56
+ end
57
+ end
58
+ relative_estimated
59
+ end
60
+
51
61
  def statusses
52
62
  statusses = {}
53
63
  @document.stories_as_flat_list.each do |story|
@@ -75,6 +85,9 @@ module Saga
75
85
  parts << ''
76
86
  parts << self.class.format_unestimated(unestimated) if unestimated > 0
77
87
  parts << self.class.format_range_estimated(range_estimated) if range_estimated > 0
88
+ if relative_estimated > 0
89
+ parts << self.class.format_relative_estimated(relative_estimated)
90
+ end
78
91
  parts << self.class.format_statusses(statusses) unless statusses.empty?
79
92
  end
80
93
  parts.shift if parts[0] == ''
@@ -92,6 +105,8 @@ module Saga
92
105
  estimate[0] * 40
93
106
  when :range
94
107
  0
108
+ when :relative
109
+ 0
95
110
  else
96
111
  estimate[0]
97
112
  end
@@ -115,6 +130,10 @@ module Saga
115
130
  "Range-estimate: #{format_stories_count(range_estimated)}"
116
131
  end
117
132
 
133
+ def self.format_relative_estimated(relative_estimated)
134
+ "Relative : #{format_stories_count(relative_estimated)}"
135
+ end
136
+
118
137
  def self.format_stories_count(count)
119
138
  count > 1 ? "#{count} stories" : 'one story'
120
139
  end
@@ -54,6 +54,7 @@ module Saga
54
54
 
55
55
  RE_STORY_NUMBER = /\#(\d+)/.freeze
56
56
  RE_STORY_ITERATION = /i(\d+)/.freeze
57
+ RE_STORY_RELATIVE_ESTIMATE = /(trivial|easy|simple|straightforward|hard|epic)/.freeze
57
58
  RE_STORY_ESTIMATE_PART = /(\d+)(d|w|h|)/.freeze
58
59
 
59
60
  def self.tokenize_story_attributes(input)
@@ -70,6 +71,8 @@ module Saga
70
71
  attributes[:id] = match[1].to_i
71
72
  elsif match = RE_STORY_ITERATION.match(part)
72
73
  attributes[:iteration] = match[1].to_i
74
+ elsif match = RE_STORY_RELATIVE_ESTIMATE.match(part)
75
+ attributes[:estimate] = [match[1], :relative]
73
76
  elsif match = /#{RE_STORY_ESTIMATE_PART}-#{RE_STORY_ESTIMATE_PART}/.match(part)
74
77
  estimate = "#{match[1, 2].join}-#{match[3, 2].join}"
75
78
  attributes[:estimate] = [estimate, :range]
data/lib/saga/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Saga
4
- VERSION = '0.14.0'.freeze
4
+ VERSION = '0.15.1'.freeze
5
5
  end
@@ -4,250 +4,223 @@
4
4
  <meta charset="utf-8">
5
5
  <title>Requirements <%= title %></title>
6
6
  <style>
7
- a
7
+ :root
8
8
  {
9
- text-decoration: none;
9
+ color-scheme: light dark;
10
+
11
+ --primary: hsl(232 0% 0%);
12
+ --background: white;
13
+ --done-background: hsl(232 90% 95%);
14
+ --dropped: hsl(232 0% 60%);
15
+ --target-background: hsl(40 90% 95%);
16
+ --border: hsl(232 0% 90%);
17
+ --link: hsl(232 80% 60%);
18
+ --link-active: hsl(232 80% 40%);
19
+
20
+ --inset-left: max(env(safe-area-inset-left), 1rem, 2.5vw);
21
+ --inset-right: max(env(safe-area-inset-right), 1rem, 2.5vw);
22
+ --inset-top: max(env(safe-area-inset-top), 1rem);
23
+ --inset-bottom: max(env(safe-area-inset-bottom), 1rem);
10
24
  }
11
- a:hover,
12
- a:active
25
+ @media (prefers-color-scheme: dark)
13
26
  {
14
- text-decoration: underline;
27
+ :root
28
+ {
29
+ --primary: hsl(232 0% 90%);
30
+ --background: hsl(232 0% 10%);
31
+ --done-background: hsl(232 90% 10%);
32
+ --dropped: hsl(232 0% 40%);
33
+ --target-background: hsl(40 90% 10%);
34
+ --border: hsl(232 0% 20%);
35
+ --link: hsl(232 80% 70%);
36
+ --link-active: hsl(232 80% 60%);
37
+ }
15
38
  }
16
39
  body
17
40
  {
18
- margin: 1.5em 1em;
19
41
  font-family: palatino, georgia, serif;
20
42
  line-height: 1.25;
43
+ -webkit-text-size-adjust: none;
44
+ text-size-adjust: none;
45
+ margin: var(--inset-top) var(--inset-right) var(--inset-bottom) var(--inset-left);
46
+ background: var(--background);
47
+ color: var(--primary);
21
48
  }
49
+ p,
22
50
  h1,
23
51
  h2,
24
- h3
52
+ h3,
53
+ ul,
54
+ li,
55
+ dl,
56
+ dt,
57
+ dd
25
58
  {
26
59
  margin: 0;
27
- font-size: 150%;
28
- line-height: 1;
60
+ padding: 0
61
+ }
62
+ h1,
63
+ h2,
64
+ h3
65
+ {
29
66
  font-weight: normal;
30
67
  }
68
+ h1
69
+ {
70
+ font-size: 162.5%;
71
+ }
31
72
  h2
32
73
  {
33
- margin: 1em 0;
74
+ font-size: 137.5%;
34
75
  }
35
76
  h3
36
77
  {
37
- margin: 0.75em 0 0.25em 0;
38
- font-size: 125%;
78
+ font-size: 118.75%;
39
79
  }
40
- table + h2,
41
- dl + h2,
42
- table + h3
80
+ h2,
81
+ h3
43
82
  {
44
- margin-top: 2em;
83
+ margin: 2rem 0 1.25rem 0;
45
84
  }
46
- p,
47
- ul
85
+ body > p + p
48
86
  {
49
- margin: 1em 0;
87
+ margin-top: 1rem;
50
88
  }
51
- ul.authors
89
+ ul
52
90
  {
53
- padding: 0;
54
- font-style: italic;
91
+ margin: 1rem 0;
55
92
  list-style-type: none;
56
- color: #444;
93
+ font-style: italic;
57
94
  }
58
- table
95
+ dl
59
96
  {
60
- margin: 1em -1em;
61
- border-collapse: collapse;
62
- empty-cells: show;
63
- border-bottom: 1px solid #ddd;
64
- margin-top: -1em;
97
+ display: grid;
98
+ grid-template-columns: auto 1fr;
99
+ gap: 0.5rem 1rem;
100
+ margin: 1rem 0;
65
101
  }
66
- tbody tr:not(.notes)
102
+ dt:after
67
103
  {
68
- border-top: 1px solid #ddd;
104
+ content: ':';
69
105
  }
70
- tbody tr.nested:not(.notes)
106
+ a[href]
71
107
  {
72
- border-top: 1px dashed #ddd;
108
+ color: var(--link);
73
109
  }
74
- tr.notes
110
+ a[href]:active
75
111
  {
76
- color: #444;
77
- font-style: italic;
112
+ color: var(--link-active);
78
113
  }
79
- th,
80
- td
114
+ div.stories
81
115
  {
82
- padding: .5em 0.25em;
83
- vertical-align: top;
84
- text-align: left;
116
+ display: grid;
117
+ grid-template-columns: fit-content(0) 1fr repeat(4, fit-content(0));
118
+ margin: 1rem calc(-1 * var(--inset-right)) 1rem calc(-1 * var(--inset-left));
85
119
  }
86
- th:first-child,
87
- td:first-child
120
+ h3,
121
+ div.stories > div.story
88
122
  {
89
- padding-left: 1em;
123
+ grid-column-start: 1;
124
+ grid-column-end: 6;
90
125
  }
91
- tr.nested th:first-child,
92
- tr.nested td:first-child
126
+ div.stories > h3
93
127
  {
94
- padding-left: 3em;
128
+ margin: 1.5rem var(--inset-right) 0.75rem var(--inset-left);
95
129
  }
96
- th:last-child,
97
- td:last-child
130
+ div.stories > h3:first-child
98
131
  {
99
- padding-right: 1em;
132
+ margin-top: 0;
100
133
  }
101
- th
134
+ div.stories > div.story
102
135
  {
103
- text-align: right;
136
+ display: grid;
137
+ grid-template-columns: subgrid;
138
+ border-top: 1px solid var(--border);
139
+ padding: 0.5rem;
104
140
  }
105
- tr.notes td
141
+ div.stories > div.story:not(:has(+ div.story))
106
142
  {
107
- padding-top: 0;
143
+ border-bottom: 1px solid var(--border);
108
144
  }
109
- td.story
145
+ div.story.done
110
146
  {
111
- width: 100%;
147
+ background: var(--done-background);
112
148
  }
113
- th.meta
149
+ div.story.dropped
114
150
  {
115
- cursor: help;
151
+ color: var(--dropped);
116
152
  }
117
- td.meta
153
+ div.story.dropped > div.content
118
154
  {
119
- text-align: right !important;
120
- white-space: nowrap;
155
+ text-decoration: line-through;
121
156
  }
122
- .dropped
157
+ div.story:target
123
158
  {
124
- color: #888;
125
- text-decoration: line-through;
159
+ background: var(--target-background);
126
160
  }
127
- .done
161
+ div.story > *
128
162
  {
129
- background-color: #f0f8ff;
163
+ padding: 0 0.5rem;
130
164
  }
131
- td.id
165
+ div.story > *:first-child
132
166
  {
133
- text-align: right !important;
167
+ padding-left: calc(var(--inset-right) - 0.5rem);
134
168
  }
135
- td.id a
169
+ div.story > *:last-child
136
170
  {
137
- color: #000;
171
+ padding-right: calc(var(--inset-left) - 0.5rem);
138
172
  }
139
- dl
173
+ div.story > a.id
140
174
  {
141
- margin: 1em -1em;
175
+ grid-column: 1;
176
+ color: inherit;
177
+ text-decoration: none;
142
178
  }
143
- dt,
144
- dd
179
+ div.story > div.content
145
180
  {
146
- display: block;
181
+ grid-column: 2;
147
182
  }
148
- dt
183
+ div.story > div.estimate
149
184
  {
150
- float: left;
151
- clear: both;
152
- margin: 0;
153
- padding: 0 0 0 1em;
154
- font-weight: bold;
185
+ grid-column: 3;
155
186
  }
156
- dt:after
187
+ div.story > div.iteration
157
188
  {
158
- content: ": ";
189
+ grid-column: 4;
190
+ font-variant-numeric: tabular-nums;
159
191
  }
160
- dd
192
+ div.story > div.status
161
193
  {
162
- margin: 0 0 1em 0;
163
- padding: 0 1em;
164
- display: block;
194
+ grid-column: 5;
165
195
  }
166
- @media (min-width: 40em) {
167
- body
168
- {
169
- margin: 2em;
170
- }
171
- table,
172
- dl
196
+ @media (max-width: 25rem) {
197
+ div.story > div.content
173
198
  {
174
- margin-left: -2em;
175
- margin-right: -2em;
199
+ grid-column: 2 / span 4;
176
200
  }
177
- th:first-child,
178
- td:first-child
201
+ div.story > div.estimate,
202
+ div.story > div.iteration,
203
+ div.story > div.status
179
204
  {
180
- padding-left: 2em;
181
- }
182
- th:last-child,
183
- td:last-child
184
- {
185
- padding-right: 2em;
186
- }
187
- tr.nested th:first-child,
188
- tr.nested td:first-child
189
- {
190
- padding-left: 4em;
191
- }
192
- dl
193
- {
194
- border-bottom: 1px solid #ddd;
195
- }
196
- dt
197
- {
198
- padding: .5em 0 0 2em;
199
- border-top: none;
200
- }
201
- dd
202
- {
203
- margin: 0;
204
- padding: .5em 2em .5em 20em;
205
- border-top: 1px solid #ddd;
205
+ padding-top: 0.25rem;
206
206
  }
207
207
  }
208
- @media print
208
+ div.content p:not(:first-of-type)
209
209
  {
210
- body
211
- {
212
- margin: 0;
213
- font-size: 10pt;
214
- }
215
- table,
216
- dl
217
- {
218
- margin-left: 0;
219
- margin-right: 0;
220
- }
221
- th:first-child,
222
- td:first-child
223
- {
224
- padding-left: 0;
225
- }
226
- tr.nested th:first-child,
227
- tr.nested td:first-child
228
- {
229
- padding-left: 2em;
230
- }
231
- th:last-child,
232
- td:last-child
233
- {
234
- padding-right: 0;
235
- }
236
- dl
237
- {
238
- border-bottom: 1px solid #ddd;
239
- }
240
- dt
241
- {
242
- padding: .5em 0 0 0;
243
- border-top: none;
244
- }
245
- dd
246
- {
247
- margin: 0;
248
- padding: .5em 0 .5em 40%;
249
- border-top: 1px solid #ddd;
250
- }
210
+ font-style: italic;
211
+ margin-top: 0.25rem;
212
+ }
213
+ div.story.nested div.content
214
+ {
215
+ padding-left: 2rem;
216
+ }
217
+ a.id,
218
+ div.estimate:not(.relative),
219
+ div.iteration
220
+ {
221
+ text-align: right;
222
+ white-space: nowrap;
223
+ font-variant-numeric: tabular-nums;
251
224
  }
252
225
  </style>
253
226
  </head>
@@ -256,67 +229,58 @@ dd
256
229
  <h1>Requirements <%= title %></h1>
257
230
 
258
231
  <% unless authors.empty? %>
259
- <ul class="authors">
232
+ <ul>
260
233
  <% authors.each do |author| %>
261
- <li><%= format_author(author) %></li>
234
+ <li><%= author(author) %></li>
262
235
  <% end %>
263
236
  </ul>
264
237
  <% end %>
265
238
 
266
239
  <% introduction.each do |paragraph| %>
267
- <p><%=h paragraph %></p>
240
+ <p><%= h paragraph %></p>
268
241
  <% end %>
269
242
 
243
+ <h2>User stories</h2>
244
+
270
245
  <% unless stories.empty? %>
271
- <h2>User stories</h2>
272
- <% stories.each do |header, stories| %>
273
- <% unless header.strip == '' %>
274
- <h3><%=h header %></h3>
275
- <% end %>
276
- <table>
277
- <thead>
278
- <tr>
279
- <th></th>
280
- <th class="meta" title="id">#</th>
281
- <th class="meta" title="Estimate">e</th>
282
- <th class="meta" title="Iteration">i</th>
283
- <th class="meta" title="Status">s</th>
284
- </tr>
285
- </thead>
286
- <tbody>
287
- <% stories.each do |story| %>
288
- <tr class="<%= story[:status] %>" id="story<%= story[:id] %>">
289
- <td class="story"><%=h story[:description] %></td>
290
- <td class="meta id"><%=h story[:id] %></td>
291
- <td class="meta estimate"><%= format_estimate(*story[:estimate]) if story[:estimate] %></td>
292
- <td class="meta iteration"><%=h story[:iteration] %></td>
293
- <td class="meta status"><%=h story[:status] %></td>
294
- </tr>
295
- <% if story[:notes] %>
296
- <tr class="notes <%= story[:status] %>">
297
- <td colspan="5"><%=h story[:notes] %></td>
298
- </tr>
299
- <% end %>
300
- <% if story[:stories] %>
301
- <% story[:stories].each do |nested| %>
302
- <tr class="nested <%= nested[:status] %>" id="story<%= nested[:id] %>">
303
- <td class="story"><%=h nested[:description] %></td>
304
- <td class="meta id"><%=h nested[:id] %></td>
305
- <td class="meta estimate"><%= format_estimate(*nested[:estimate]) if nested[:estimate] %></td>
306
- <td class="meta iteration"><%=h nested[:iteration] %></td>
307
- <td class="meta status"><%=h nested[:status] %></td>
308
- </tr>
309
- <% if nested[:notes] %>
310
- <tr class="nested notes <%= nested[:status] %>">
311
- <td colspan="5"><%=h nested[:notes] %></td>
312
- </tr>
313
- <% end %>
246
+ <div class="stories">
247
+ <% stories.each do |header, stories| %>
248
+ <% unless header.strip == '' %>
249
+ <h3><%= h header %></h3>
250
+ <% end %>
251
+
252
+ <% stories.each do |story| %>
253
+ <div class="story <%= story[:status] %>" id="<%= dom_story_id(story[:id]) %>">
254
+ <%= id(story[:id]) %>
255
+ <div class="content">
256
+ <p><%= h story[:description] %></p>
257
+ <% if story[:notes] %>
258
+ <p><%= h story[:notes] %></p>
314
259
  <% end %>
260
+ </div>
261
+ <%= estimate(*story[:estimate]) %>
262
+ <%= iteration(story[:iteration]) %>
263
+ <%= status(story[:status]) %>
264
+ </div>
265
+ <% if story[:stories] %>
266
+ <% story[:stories].each do |nested| %>
267
+ <div class="story nested <%= nested[:status] %>" id="<%= dom_story_id(nested[:id]) %>">
268
+ <%= id(nested[:id]) %>
269
+ <div class="content">
270
+ <p><%= h nested[:description] %></p>
271
+ <% if nested[:notes] %>
272
+ <p><%= h nested[:notes] %></p>
273
+ <% end %>
274
+ </div>
275
+ <%= estimate(*nested[:estimate]) %>
276
+ <%= iteration(nested[:iteration]) %>
277
+ <%= status(nested[:status]) %>
278
+ </div>
315
279
  <% end %>
316
280
  <% end %>
317
- </tbody>
318
- </table>
319
- <% end %>
281
+ <% end %>
282
+ <% end %>
283
+ </div>
320
284
  <% end %>
321
285
 
322
286
  <% definitions.each do |header, definitions| %>
@@ -325,8 +289,8 @@ dd
325
289
  <% end %>
326
290
  <dl>
327
291
  <% definitions.each do |definition| %>
328
- <dt><%=h definition[:title] %></dt>
329
- <dd><%=h definition[:definition] %></dd>
292
+ <dt><%= h definition[:title] %></dt>
293
+ <dd><%= h definition[:definition] %></dd>
330
294
  <% end %>
331
295
  </dl>
332
296
  <% end %>
@@ -1,4 +1,4 @@
1
- def format_author(author)
1
+ def author(author)
2
2
  parts = []
3
3
  parts << author[:name] if author[:name]
4
4
  parts << "<a href=\"mailto:#{author[:email]}\">#{author[:email]}</a>" if author[:email]
@@ -24,7 +24,35 @@ def format_estimate(cardinality, interval)
24
24
  pluralize(cardinality, 'day', 'days')
25
25
  when :weeks
26
26
  pluralize(cardinality, 'week', 'days')
27
+ when :relative
28
+ cardinality.gsub('straightforward', "straight\u00ADforward")
27
29
  else
28
30
  cardinality.to_s
29
31
  end
30
32
  end
33
+
34
+ def dom_story_id(id)
35
+ "story#{id}"
36
+ end
37
+
38
+ def id(id)
39
+ ["<a href=\"##{dom_story_id(id)}\" class=\"id\" title=\"ID\">#", id, '</a>'].join if id
40
+ end
41
+
42
+ def iteration(iteration)
43
+ ['<div class="iteration" title="Iteration">', iteration, '</div>'].join if iteration
44
+ end
45
+
46
+ def status(status)
47
+ ['<div class="status" title="Status">', status, '</div>'].join if status
48
+ end
49
+
50
+ def estimate(cardinality = nil, interval = nil)
51
+ if cardinality
52
+ [
53
+ "<div class=\"estimate #{interval}\" title=\"Estimate\">",
54
+ format_estimate(cardinality, interval),
55
+ '</div>'
56
+ ].join
57
+ end
58
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saga
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manfred Stienstra
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '5'
18
+ version: '8'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '5'
25
+ version: '8'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rake
28
28
  requirement: !ruby/object:Gem::Requirement