saga 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +62 -1
- data/VERSION +1 -1
- data/lib/saga/document.rb +30 -0
- data/lib/saga/runner.rb +12 -2
- data/saga.gemspec +2 -2
- data/templates/saga/document.erb +16 -1
- data/templates/saga/helpers.rb +14 -0
- data/test/saga_document_spec.rb +57 -0
- data/test/saga_runner_spec.rb +8 -0
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -1,3 +1,64 @@
|
|
1
1
|
= Saga
|
2
2
|
|
3
|
-
Saga is a tool to convert the
|
3
|
+
Saga is a tool to convert the requirements format used at Fingertips to HTML.
|
4
|
+
|
5
|
+
The Saga document consists of three major parts: an introduction, the stories, and definitions. The document is usually a succinct and complete description of a piece of software. Anyone reading the document should get a fairly good idea of the application without further information.
|
6
|
+
|
7
|
+
=== The introduction
|
8
|
+
|
9
|
+
The first line of the document is the title. You can start the line with ‘Requirements’, but this is not compulsory.
|
10
|
+
|
11
|
+
Requirements Saga
|
12
|
+
|
13
|
+
After the title follows a list of authors. It's encouraged to add all the authors, this way people know who to speak to for more information about the stories. Only the name of the author is compulsory.
|
14
|
+
|
15
|
+
- Manfred Stienstra, manfred@fngtps.com, Fingertips, http://www.fngtps.com
|
16
|
+
|
17
|
+
The final part of the introduction is a concise description of the project.
|
18
|
+
|
19
|
+
Saga is a tool to convert the requirements format used at Fingertips to HTML.
|
20
|
+
|
21
|
+
=== The stories
|
22
|
+
|
23
|
+
The stories section starts with the USER STORIES header.
|
24
|
+
|
25
|
+
USER STORIES
|
26
|
+
|
27
|
+
After this follows a list of stories. You can choose to add section headers, but this is optional. For example:
|
28
|
+
|
29
|
+
As a writer I want to convert my description of the project to a nice format. - #1 todo
|
30
|
+
|
31
|
+
Workflow
|
32
|
+
|
33
|
+
As a writer I want an example so I don't have to remember all the details of the format. – #2 todo
|
34
|
+
Try to find author details for the system information and autofill that in the generated stub.
|
35
|
+
As a writer I want to debug the parse process so I can find out what I did wrong. - #3 todo
|
36
|
+
|
37
|
+
|
38
|
+
Stories consist of a description and some extra information. The number behind the hash is the unique number of the story. We use the id to point to a story without having to type the whole description in everyday conversation. The little text label at the end describes the status of the story.
|
39
|
+
|
40
|
+
=== Definitions
|
41
|
+
|
42
|
+
The document ends with a list of definitions. Here we define words used throughout the document. We only define words if they need a strict definition or if they might be misunderstood by someone. Like with the stories sections are optional.
|
43
|
+
|
44
|
+
ROLES
|
45
|
+
|
46
|
+
Writer: Someone who was appointed the task of writing the stories.
|
47
|
+
Developer: A person developing the application.
|
48
|
+
|
49
|
+
DEFINITIONS
|
50
|
+
|
51
|
+
Project: The software project the developers are working on.
|
52
|
+
|
53
|
+
=== Usage
|
54
|
+
|
55
|
+
Usage: saga [command]
|
56
|
+
|
57
|
+
Commands:
|
58
|
+
new - prints a blank stub
|
59
|
+
convert <filename> - convert the stories to HTML
|
60
|
+
inspect <filename> - print the internals of the document
|
61
|
+
autofill <filename> - adds an id to stories without one
|
62
|
+
|
63
|
+
Options:
|
64
|
+
-h, --help Show help
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/saga/document.rb
CHANGED
@@ -11,5 +11,35 @@ module Saga
|
|
11
11
|
@stories = ActiveSupport::OrderedHash.new
|
12
12
|
@definitions = ActiveSupport::OrderedHash.new
|
13
13
|
end
|
14
|
+
|
15
|
+
def used_ids
|
16
|
+
@stories.values.inject([]) do |ids, stories|
|
17
|
+
ids.concat stories.map { |story| story[:id] }
|
18
|
+
ids
|
19
|
+
end.compact
|
20
|
+
end
|
21
|
+
|
22
|
+
def unused_ids(limit)
|
23
|
+
position = 1
|
24
|
+
used_ids = used_ids()
|
25
|
+
(1..limit).map do
|
26
|
+
while used_ids.include?(position) do position += 1 end
|
27
|
+
used_ids << position
|
28
|
+
position
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def length
|
33
|
+
stories.inject(0) { |total, (_, stories)| total + stories.length }
|
34
|
+
end
|
35
|
+
|
36
|
+
def autofill_ids
|
37
|
+
unused_ids = unused_ids(length - used_ids.length)
|
38
|
+
stories.each do |_, stories|
|
39
|
+
stories.each do |story|
|
40
|
+
story[:id] ||= unused_ids.shift
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
14
44
|
end
|
15
45
|
end
|
data/lib/saga/runner.rb
CHANGED
@@ -15,6 +15,7 @@ module Saga
|
|
15
15
|
opts.separator " new - prints a blank stub"
|
16
16
|
opts.separator " convert <filename> - convert the stories to HTML"
|
17
17
|
opts.separator " inspect <filename> - print the internals of the document"
|
18
|
+
opts.separator " autofill <filename> - adds an id to stories without one"
|
18
19
|
opts.separator ""
|
19
20
|
opts.separator "Options:"
|
20
21
|
opts.on("-h", "--help", "Show help") do
|
@@ -31,6 +32,10 @@ module Saga
|
|
31
32
|
Saga::Formatter.format(document, :template => 'saga')
|
32
33
|
end
|
33
34
|
|
35
|
+
def convert(filename)
|
36
|
+
Saga::Formatter.format(Saga::Parser.parse(File.read(filename)))
|
37
|
+
end
|
38
|
+
|
34
39
|
def write_parsed_document(filename)
|
35
40
|
document = Saga::Parser.parse(File.read(filename))
|
36
41
|
puts document.title
|
@@ -41,10 +46,13 @@ module Saga
|
|
41
46
|
document.definitions.each { |header, definitions| puts header; definitions.each { |definition| p definition } }
|
42
47
|
end
|
43
48
|
|
44
|
-
def
|
45
|
-
Saga::
|
49
|
+
def autofill(filename)
|
50
|
+
document = Saga::Parser.parse(File.read(filename))
|
51
|
+
document.autofill_ids
|
52
|
+
Saga::Formatter.format(document, :template => 'saga')
|
46
53
|
end
|
47
54
|
|
55
|
+
|
48
56
|
def run_command(command, options)
|
49
57
|
case command
|
50
58
|
when 'new'
|
@@ -53,6 +61,8 @@ module Saga
|
|
53
61
|
puts convert(File.expand_path(@argv[1]))
|
54
62
|
when 'inspect'
|
55
63
|
write_parsed_document(File.expand_path(@argv[1]))
|
64
|
+
when 'autofill'
|
65
|
+
puts autofill(File.expand_path(@argv[1]))
|
56
66
|
else
|
57
67
|
puts convert(File.expand_path(command))
|
58
68
|
end
|
data/saga.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{saga}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Manfred Stienstra"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-04-02}
|
13
13
|
s.default_executable = %q{saga}
|
14
14
|
s.description = %q{Saga is a tool to convert stories syntax to a nicely formatted document.}
|
15
15
|
s.email = %q{manfred@fngtps.com}
|
data/templates/saga/document.erb
CHANGED
@@ -4,4 +4,19 @@ Requirements <%= title %>
|
|
4
4
|
- <%= format_author(author) %>
|
5
5
|
<% end %>
|
6
6
|
|
7
|
-
USER STORIES
|
7
|
+
USER STORIES
|
8
|
+
<% stories.each do |header, stories| %>
|
9
|
+
|
10
|
+
<%= "#{header}\n" if header %>
|
11
|
+
<% stories.each do |story| %>
|
12
|
+
<%= format_story(story) %>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<% definitions.each do |header, definitions| %>
|
17
|
+
|
18
|
+
<%= "#{header}\n" if header %>
|
19
|
+
<% definitions.each do |definition| %>
|
20
|
+
<%= format_definition(definition) %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
data/templates/saga/helpers.rb
CHANGED
@@ -4,4 +4,18 @@ module Helpers
|
|
4
4
|
author[key]
|
5
5
|
end.compact.join(', ')
|
6
6
|
end
|
7
|
+
|
8
|
+
def format_story(story)
|
9
|
+
story_attributes = []
|
10
|
+
story_attributes << "##{story[:id]}" if story[:id]
|
11
|
+
story_attributes << "##{story[:status]}" if story[:status]
|
12
|
+
|
13
|
+
parts = [[story[:description], story_attributes.join(' ')].join(' - ')]
|
14
|
+
parts << " #{story[:notes]}" if story[:notes]
|
15
|
+
parts.join("\n\n")
|
16
|
+
end
|
17
|
+
|
18
|
+
def format_definition(definition)
|
19
|
+
[definition[:title], definition[:definition]].join(': ')
|
20
|
+
end
|
7
21
|
end
|
data/test/saga_document_spec.rb
CHANGED
@@ -25,4 +25,61 @@ describe "A Document" do
|
|
25
25
|
end
|
26
26
|
document.definitions.keys.should == sections
|
27
27
|
end
|
28
|
+
|
29
|
+
it "should return a list of used IDs" do
|
30
|
+
document = Saga::Document.new
|
31
|
+
document.used_ids.should == []
|
32
|
+
|
33
|
+
document.stories[''] = []
|
34
|
+
document.stories[''] << { :id => 2 }
|
35
|
+
document.used_ids.should == [2]
|
36
|
+
|
37
|
+
document.stories['Non-functional'] = []
|
38
|
+
document.stories['Non-functional'] << { :id => 12 }
|
39
|
+
document.used_ids.should == [2, 12]
|
40
|
+
|
41
|
+
document.stories['Non-functional'] << { :id => 3 }
|
42
|
+
document.used_ids.should == [2, 12, 3]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return a list of unused IDs" do
|
46
|
+
document = Saga::Document.new
|
47
|
+
document.unused_ids(0).should == []
|
48
|
+
document.unused_ids(3).should == [1,2,3]
|
49
|
+
document.unused_ids(4).should == [1,2,3,4]
|
50
|
+
|
51
|
+
document.stories[''] = []
|
52
|
+
document.stories[''] << { :id => 2 }
|
53
|
+
document.stories[''] << { :id => 4 }
|
54
|
+
document.stories[''] << { :id => 5 }
|
55
|
+
document.stories[''] << { :id => 6 }
|
56
|
+
document.stories[''] << { :id => 100 }
|
57
|
+
|
58
|
+
document.unused_ids(0).should == []
|
59
|
+
document.unused_ids(1).should == [1]
|
60
|
+
document.unused_ids(2).should == [1,3]
|
61
|
+
document.unused_ids(3).should == [1,3,7]
|
62
|
+
document.unused_ids(4).should == [1,3,7,8]
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should autofills ids" do
|
66
|
+
document = Saga::Document.new
|
67
|
+
document.autofill_ids
|
68
|
+
|
69
|
+
document.stories[''] = []
|
70
|
+
document.stories[''] << { :description => 'First story'}
|
71
|
+
document.stories[''] << { :description => 'Second story'}
|
72
|
+
|
73
|
+
document.stories['Non-functional'] = []
|
74
|
+
document.stories['Non-functional'] << { :id => 1, :description => 'Third story' }
|
75
|
+
|
76
|
+
document.stories['Developer'] = []
|
77
|
+
document.stories['Developer'] << { :description => 'Fourth story' }
|
78
|
+
document.stories['Developer'] << { :id => 3, :description => 'Fifth story' }
|
79
|
+
|
80
|
+
document.autofill_ids
|
81
|
+
document.stories[''].map { |story| story[:id] }.should == [2, 4]
|
82
|
+
document.stories['Non-functional'].map { |story| story[:id] }.should == [1]
|
83
|
+
document.stories['Developer'].map { |story| story[:id] }.should == [5, 3]
|
84
|
+
end
|
28
85
|
end
|
data/test/saga_runner_spec.rb
CHANGED
@@ -80,4 +80,12 @@ describe "A Runner" do
|
|
80
80
|
runner.expects(:write_parsed_document).with(File.expand_path('requirements.txt'))
|
81
81
|
runner.run
|
82
82
|
end
|
83
|
+
|
84
|
+
it "autofills the parsed document" do
|
85
|
+
runner = Saga::Runner.new(%w(autofill requirements.txt))
|
86
|
+
runner.expects(:autofill).with(File.expand_path('requirements.txt')).returns('output')
|
87
|
+
collect_stdout do
|
88
|
+
runner.run
|
89
|
+
end.should == "output\n"
|
90
|
+
end
|
83
91
|
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.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manfred Stienstra
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-04-02 00:00:00 +02:00
|
13
13
|
default_executable: saga
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|