saga 0.13.2 → 0.15
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 +4 -4
- data/lib/saga/planning.rb +19 -0
- data/lib/saga/runner.rb +16 -5
- data/lib/saga/tokenizer.rb +3 -0
- data/lib/saga/verifier.rb +70 -0
- data/lib/saga/version.rb +1 -1
- data/lib/saga.rb +1 -0
- data/templates/default/document.erb +178 -218
- data/templates/default/helpers.rb +29 -1
- metadata +4 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2cafeea9d1c74e5312fb8cd3909e06492a9f2d6b0e4d25554aaf196bfc940aff
|
|
4
|
+
data.tar.gz: a59f8478840bc9e48d99d32d76847f570f440893759c3ba95b746a46bb964a3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce5e4ccb1fae09c89f12bf82737f4917f1decdf109a93d739f793d28778f8b5997748d19eae7c34e147619702fe50a863f3b84ad42dbae4effb1ea880fcb3be3
|
|
7
|
+
data.tar.gz: b885628cd6d2f61c4a9a778a487ff6e1fef038f5e33ba2fbaf2e7fea9cbb02a962aea2bea12d8f88329689e69de429bd190fe38e74a0bbaf42ad9e29c4956304
|
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
|
data/lib/saga/runner.rb
CHANGED
|
@@ -26,6 +26,7 @@ module Saga
|
|
|
26
26
|
parser.separator ' autofill <filename> - adds an id to stories without one'
|
|
27
27
|
parser.separator ' planning <filename> - shows the planning of stories in iterations'
|
|
28
28
|
parser.separator ' template <dir> - creates a template directory'
|
|
29
|
+
parser.separator ' verify <dir> - checks internal consistency of the document'
|
|
29
30
|
parser.separator ''
|
|
30
31
|
parser.separator 'Options:'
|
|
31
32
|
parser.on('-t', '--template DIR', 'Use an external template for conversion to HTML') do |template_path|
|
|
@@ -96,20 +97,26 @@ module Saga
|
|
|
96
97
|
end
|
|
97
98
|
end
|
|
98
99
|
|
|
100
|
+
def verify(filename)
|
|
101
|
+
Saga::Verifier.new(Saga::Parser.parse(File.read(filename))).run
|
|
102
|
+
end
|
|
103
|
+
|
|
99
104
|
def run_command(command)
|
|
100
105
|
case command
|
|
101
106
|
when 'new'
|
|
102
107
|
puts new_file
|
|
103
108
|
when 'convert'
|
|
104
|
-
puts convert(
|
|
109
|
+
puts convert(file_path)
|
|
105
110
|
when 'inspect'
|
|
106
|
-
write_parsed_document(
|
|
111
|
+
write_parsed_document(file_path)
|
|
107
112
|
when 'autofill'
|
|
108
|
-
puts autofill(
|
|
113
|
+
puts autofill(file_path)
|
|
109
114
|
when 'planning'
|
|
110
|
-
puts planning(
|
|
115
|
+
puts planning(file_path)
|
|
111
116
|
when 'template'
|
|
112
|
-
copy_template(
|
|
117
|
+
copy_template(file_path)
|
|
118
|
+
when 'verify'
|
|
119
|
+
verify(file_path)
|
|
113
120
|
else
|
|
114
121
|
puts convert(File.expand_path(command))
|
|
115
122
|
end
|
|
@@ -128,5 +135,9 @@ module Saga
|
|
|
128
135
|
def author
|
|
129
136
|
{ name: `osascript -e "long user name of (system info)" &1> /dev/null`.strip }
|
|
130
137
|
end
|
|
138
|
+
|
|
139
|
+
def file_path
|
|
140
|
+
File.expand_path(@argv[0])
|
|
141
|
+
end
|
|
131
142
|
end
|
|
132
143
|
end
|
data/lib/saga/tokenizer.rb
CHANGED
|
@@ -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]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Saga
|
|
2
|
+
class Verifier
|
|
3
|
+
attr_reader :document
|
|
4
|
+
|
|
5
|
+
def initialize(document)
|
|
6
|
+
@document = document
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def structure
|
|
10
|
+
@structure ||= build_structure
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run
|
|
14
|
+
return if structure.empty?
|
|
15
|
+
|
|
16
|
+
reported = []
|
|
17
|
+
structure.each do |title, details|
|
|
18
|
+
if details[:duplicates].any?
|
|
19
|
+
warning = "has multiple stories with the same id: " \
|
|
20
|
+
"#{format_sentence(details[:duplicates])}"
|
|
21
|
+
if title.strip != ""
|
|
22
|
+
puts "* Section #{format_title(title)} #{warning}"
|
|
23
|
+
else
|
|
24
|
+
puts "* Document #{warning}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
structure.each do |other_title, other_details|
|
|
29
|
+
next if title == other_title
|
|
30
|
+
|
|
31
|
+
reused = details[:ids] & other_details[:ids]
|
|
32
|
+
if reused.any?
|
|
33
|
+
pair = [title, other_title].sort
|
|
34
|
+
unless reported.include?(pair)
|
|
35
|
+
puts(
|
|
36
|
+
"* Sections #{format_title(title)} and #{format_title(other_title)} " \
|
|
37
|
+
"share story ids: #{format_sentence(reused)}"
|
|
38
|
+
)
|
|
39
|
+
reported << pair
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def build_structure
|
|
49
|
+
document.stories.each.with_object({}) do |(title, stories), structure|
|
|
50
|
+
structure[title] ||= {}
|
|
51
|
+
ids = stories.map { |story| story[:id] }
|
|
52
|
+
structure[title][:ids] = ids
|
|
53
|
+
structure[title][:duplicates] = ids.uniq.select { |id| ids.count(id) > 1 }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def format_title(title)
|
|
58
|
+
%("#{title}")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def format_sentence(items)
|
|
62
|
+
case items.size
|
|
63
|
+
when 0, 1, 2
|
|
64
|
+
items.join(" and ")
|
|
65
|
+
else
|
|
66
|
+
format_sentence(items[..-2].join(", "), items[-1])
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/saga/version.rb
CHANGED
data/lib/saga.rb
CHANGED
|
@@ -4,250 +4,219 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<title>Requirements <%= title %></title>
|
|
6
6
|
<style>
|
|
7
|
-
|
|
7
|
+
:root
|
|
8
8
|
{
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
a:active
|
|
25
|
+
@media (prefers-color-scheme: dark)
|
|
13
26
|
{
|
|
14
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
74
|
+
font-size: 137.5%;
|
|
34
75
|
}
|
|
35
76
|
h3
|
|
36
77
|
{
|
|
37
|
-
|
|
38
|
-
font-size: 125%;
|
|
78
|
+
font-size: 118.75%;
|
|
39
79
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
table + h3
|
|
80
|
+
h2,
|
|
81
|
+
h3
|
|
43
82
|
{
|
|
44
|
-
margin
|
|
83
|
+
margin: 2rem 0 1.25rem 0;
|
|
45
84
|
}
|
|
46
|
-
p,
|
|
47
85
|
ul
|
|
48
86
|
{
|
|
49
|
-
margin:
|
|
50
|
-
}
|
|
51
|
-
ul.authors
|
|
52
|
-
{
|
|
53
|
-
padding: 0;
|
|
54
|
-
font-style: italic;
|
|
87
|
+
margin: 1rem 0;
|
|
55
88
|
list-style-type: none;
|
|
56
|
-
|
|
89
|
+
font-style: italic;
|
|
57
90
|
}
|
|
58
|
-
|
|
91
|
+
dl
|
|
59
92
|
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
margin-top: -1em;
|
|
93
|
+
display: grid;
|
|
94
|
+
grid-template-columns: auto 1fr;
|
|
95
|
+
gap: 0.5rem 1rem;
|
|
96
|
+
margin: 1rem 0;
|
|
65
97
|
}
|
|
66
|
-
|
|
98
|
+
dt:after
|
|
67
99
|
{
|
|
68
|
-
|
|
100
|
+
content: ':';
|
|
69
101
|
}
|
|
70
|
-
|
|
102
|
+
a[href]
|
|
71
103
|
{
|
|
72
|
-
|
|
104
|
+
color: var(--link);
|
|
73
105
|
}
|
|
74
|
-
|
|
106
|
+
a[href]:active
|
|
75
107
|
{
|
|
76
|
-
color:
|
|
77
|
-
font-style: italic;
|
|
108
|
+
color: var(--link-active);
|
|
78
109
|
}
|
|
79
|
-
|
|
80
|
-
td
|
|
110
|
+
div.stories
|
|
81
111
|
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
display: grid;
|
|
113
|
+
grid-template-columns: fit-content(0) 1fr repeat(4, fit-content(0));
|
|
114
|
+
margin: 1rem calc(-1 * var(--inset-right)) 1rem calc(-1 * var(--inset-left));
|
|
85
115
|
}
|
|
86
|
-
|
|
87
|
-
|
|
116
|
+
h3,
|
|
117
|
+
div.stories > div.story
|
|
88
118
|
{
|
|
89
|
-
|
|
119
|
+
grid-column-start: 1;
|
|
120
|
+
grid-column-end: 6;
|
|
90
121
|
}
|
|
91
|
-
|
|
92
|
-
tr.nested td:first-child
|
|
122
|
+
div.stories > h3
|
|
93
123
|
{
|
|
94
|
-
|
|
124
|
+
margin: 1.5rem var(--inset-right) 0.75rem var(--inset-left);
|
|
95
125
|
}
|
|
96
|
-
|
|
97
|
-
td:last-child
|
|
126
|
+
div.stories > h3:first-child
|
|
98
127
|
{
|
|
99
|
-
|
|
128
|
+
margin-top: 0;
|
|
100
129
|
}
|
|
101
|
-
|
|
130
|
+
div.stories > div.story
|
|
102
131
|
{
|
|
103
|
-
|
|
132
|
+
display: grid;
|
|
133
|
+
grid-template-columns: subgrid;
|
|
134
|
+
border-top: 1px solid var(--border);
|
|
135
|
+
padding: 0.5rem;
|
|
104
136
|
}
|
|
105
|
-
|
|
137
|
+
div.stories > div.story:not(:has(+ div.story))
|
|
106
138
|
{
|
|
107
|
-
|
|
139
|
+
border-bottom: 1px solid var(--border);
|
|
108
140
|
}
|
|
109
|
-
|
|
141
|
+
div.story.done
|
|
110
142
|
{
|
|
111
|
-
|
|
143
|
+
background: var(--done-background);
|
|
112
144
|
}
|
|
113
|
-
|
|
145
|
+
div.story.dropped
|
|
114
146
|
{
|
|
115
|
-
|
|
147
|
+
color: var(--dropped);
|
|
116
148
|
}
|
|
117
|
-
|
|
149
|
+
div.story.dropped > div.content
|
|
118
150
|
{
|
|
119
|
-
text-
|
|
120
|
-
white-space: nowrap;
|
|
151
|
+
text-decoration: line-through;
|
|
121
152
|
}
|
|
122
|
-
.
|
|
153
|
+
div.story:target
|
|
123
154
|
{
|
|
124
|
-
|
|
125
|
-
text-decoration: line-through;
|
|
155
|
+
background: var(--target-background);
|
|
126
156
|
}
|
|
127
|
-
.
|
|
157
|
+
div.story > *
|
|
128
158
|
{
|
|
129
|
-
|
|
159
|
+
padding: 0 0.5rem;
|
|
130
160
|
}
|
|
131
|
-
|
|
161
|
+
div.story > *:first-child
|
|
132
162
|
{
|
|
133
|
-
|
|
163
|
+
padding-left: calc(var(--inset-right) - 0.5rem);
|
|
134
164
|
}
|
|
135
|
-
|
|
165
|
+
div.story > *:last-child
|
|
136
166
|
{
|
|
137
|
-
|
|
167
|
+
padding-right: calc(var(--inset-left) - 0.5rem);
|
|
138
168
|
}
|
|
139
|
-
|
|
169
|
+
div.story > a.id
|
|
140
170
|
{
|
|
141
|
-
|
|
171
|
+
grid-column: 1;
|
|
172
|
+
color: inherit;
|
|
173
|
+
text-decoration: none;
|
|
142
174
|
}
|
|
143
|
-
|
|
144
|
-
dd
|
|
175
|
+
div.story > div.content
|
|
145
176
|
{
|
|
146
|
-
|
|
177
|
+
grid-column: 2;
|
|
147
178
|
}
|
|
148
|
-
|
|
179
|
+
div.story > div.estimate
|
|
149
180
|
{
|
|
150
|
-
|
|
151
|
-
clear: both;
|
|
152
|
-
margin: 0;
|
|
153
|
-
padding: 0 0 0 1em;
|
|
154
|
-
font-weight: bold;
|
|
181
|
+
grid-column: 3;
|
|
155
182
|
}
|
|
156
|
-
|
|
183
|
+
div.story > div.iteration
|
|
157
184
|
{
|
|
158
|
-
|
|
185
|
+
grid-column: 4;
|
|
186
|
+
font-variant-numeric: tabular-nums;
|
|
159
187
|
}
|
|
160
|
-
|
|
188
|
+
div.story > div.status
|
|
161
189
|
{
|
|
162
|
-
|
|
163
|
-
padding: 0 1em;
|
|
164
|
-
display: block;
|
|
190
|
+
grid-column: 5;
|
|
165
191
|
}
|
|
166
|
-
@media (
|
|
167
|
-
|
|
168
|
-
{
|
|
169
|
-
margin: 2em;
|
|
170
|
-
}
|
|
171
|
-
table,
|
|
172
|
-
dl
|
|
192
|
+
@media (max-width: 25rem) {
|
|
193
|
+
div.story > div.content
|
|
173
194
|
{
|
|
174
|
-
|
|
175
|
-
margin-right: -2em;
|
|
195
|
+
grid-column: 2 / span 4;
|
|
176
196
|
}
|
|
177
|
-
|
|
178
|
-
|
|
197
|
+
div.story > div.estimate,
|
|
198
|
+
div.story > div.iteration,
|
|
199
|
+
div.story > div.status
|
|
179
200
|
{
|
|
180
|
-
padding-
|
|
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;
|
|
201
|
+
padding-top: 0.25rem;
|
|
206
202
|
}
|
|
207
203
|
}
|
|
208
|
-
|
|
204
|
+
div.content p:not(:first-of-type)
|
|
209
205
|
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
}
|
|
206
|
+
font-style: italic;
|
|
207
|
+
margin-top: 0.25rem;
|
|
208
|
+
}
|
|
209
|
+
div.story.nested div.content
|
|
210
|
+
{
|
|
211
|
+
padding-left: 2rem;
|
|
212
|
+
}
|
|
213
|
+
a.id,
|
|
214
|
+
div.estimate:not(.relative),
|
|
215
|
+
div.iteration
|
|
216
|
+
{
|
|
217
|
+
text-align: right;
|
|
218
|
+
white-space: nowrap;
|
|
219
|
+
font-variant-numeric: tabular-nums;
|
|
251
220
|
}
|
|
252
221
|
</style>
|
|
253
222
|
</head>
|
|
@@ -256,67 +225,58 @@ dd
|
|
|
256
225
|
<h1>Requirements <%= title %></h1>
|
|
257
226
|
|
|
258
227
|
<% unless authors.empty? %>
|
|
259
|
-
<ul
|
|
228
|
+
<ul>
|
|
260
229
|
<% authors.each do |author| %>
|
|
261
|
-
<li><%=
|
|
230
|
+
<li><%= author(author) %></li>
|
|
262
231
|
<% end %>
|
|
263
232
|
</ul>
|
|
264
233
|
<% end %>
|
|
265
234
|
|
|
266
235
|
<% introduction.each do |paragraph| %>
|
|
267
|
-
<p><%=h paragraph %></p>
|
|
236
|
+
<p><%= h paragraph %></p>
|
|
268
237
|
<% end %>
|
|
269
238
|
|
|
239
|
+
<h2>User stories</h2>
|
|
240
|
+
|
|
270
241
|
<% unless stories.empty? %>
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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 %>
|
|
242
|
+
<div class="stories">
|
|
243
|
+
<% stories.each do |header, stories| %>
|
|
244
|
+
<% unless header.strip == '' %>
|
|
245
|
+
<h3><%= h header %></h3>
|
|
246
|
+
<% end %>
|
|
247
|
+
|
|
248
|
+
<% stories.each do |story| %>
|
|
249
|
+
<div class="story <%= story[:status] %>" id="<%= dom_story_id(story[:id]) %>">
|
|
250
|
+
<%= id(story[:id]) %>
|
|
251
|
+
<div class="content">
|
|
252
|
+
<p><%= h story[:description] %></p>
|
|
253
|
+
<% if story[:notes] %>
|
|
254
|
+
<p><%= h story[:notes] %></p>
|
|
314
255
|
<% end %>
|
|
256
|
+
</div>
|
|
257
|
+
<%= estimate(*story[:estimate]) %>
|
|
258
|
+
<%= iteration(story[:iteration]) %>
|
|
259
|
+
<%= status(story[:status]) %>
|
|
260
|
+
</div>
|
|
261
|
+
<% if story[:stories] %>
|
|
262
|
+
<% story[:stories].each do |nested| %>
|
|
263
|
+
<div class="story nested <%= nested[:status] %>" id="<%= dom_story_id(nested[:id]) %>">
|
|
264
|
+
<%= id(nested[:id]) %>
|
|
265
|
+
<div class="content">
|
|
266
|
+
<p><%= h nested[:description] %></p>
|
|
267
|
+
<% if nested[:notes] %>
|
|
268
|
+
<p><%= h nested[:notes] %></p>
|
|
269
|
+
<% end %>
|
|
270
|
+
</div>
|
|
271
|
+
<%= estimate(*nested[:estimate]) %>
|
|
272
|
+
<%= iteration(nested[:iteration]) %>
|
|
273
|
+
<%= status(nested[:status]) %>
|
|
274
|
+
</div>
|
|
315
275
|
<% end %>
|
|
316
276
|
<% end %>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
277
|
+
<% end %>
|
|
278
|
+
<% end %>
|
|
279
|
+
</div>
|
|
320
280
|
<% end %>
|
|
321
281
|
|
|
322
282
|
<% definitions.each do |header, definitions| %>
|
|
@@ -325,8 +285,8 @@ dd
|
|
|
325
285
|
<% end %>
|
|
326
286
|
<dl>
|
|
327
287
|
<% definitions.each do |definition| %>
|
|
328
|
-
<dt><%=h definition[:title] %></dt>
|
|
329
|
-
<dd><%=h definition[:definition] %></dd>
|
|
288
|
+
<dt><%= h definition[:title] %></dt>
|
|
289
|
+
<dd><%= h definition[:definition] %></dd>
|
|
330
290
|
<% end %>
|
|
331
291
|
</dl>
|
|
332
292
|
<% end %>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
def
|
|
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,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: saga
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.15'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Manfred Stienstra
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activesupport
|
|
@@ -58,6 +57,7 @@ files:
|
|
|
58
57
|
- lib/saga/planning.rb
|
|
59
58
|
- lib/saga/runner.rb
|
|
60
59
|
- lib/saga/tokenizer.rb
|
|
60
|
+
- lib/saga/verifier.rb
|
|
61
61
|
- lib/saga/version.rb
|
|
62
62
|
- templates/default/document.erb
|
|
63
63
|
- templates/default/helpers.rb
|
|
@@ -68,7 +68,6 @@ homepage: https://github.com/Fingertips/saga
|
|
|
68
68
|
licenses:
|
|
69
69
|
- MIT
|
|
70
70
|
metadata: {}
|
|
71
|
-
post_install_message:
|
|
72
71
|
rdoc_options: []
|
|
73
72
|
require_paths:
|
|
74
73
|
- lib
|
|
@@ -83,8 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
83
82
|
- !ruby/object:Gem::Version
|
|
84
83
|
version: '0'
|
|
85
84
|
requirements: []
|
|
86
|
-
rubygems_version: 3.
|
|
87
|
-
signing_key:
|
|
85
|
+
rubygems_version: 3.6.9
|
|
88
86
|
specification_version: 4
|
|
89
87
|
summary: Saga is a tool to convert stories syntax to a nicely formatted document.
|
|
90
88
|
test_files: []
|