saga 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,2 +1,4 @@
1
1
  html
2
- pkg
2
+ pkg
3
+ *.swp
4
+ *.swo
data/README.rdoc CHANGED
@@ -2,15 +2,22 @@
2
2
 
3
3
  Saga is a tool to convert the requirements format used at Fingertips to HTML.
4
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.
5
+ The Saga document consists of three major parts: an introduction, the stories,
6
+ and definitions. The document is usually a succinct and complete description of
7
+ a piece of software. Anyone reading the document should get a fairly good idea
8
+ of the application without further information.
9
+
6
10
 
7
11
  === The introduction
8
12
 
9
- The first line of the document is the title. You can start the line with ‘Requirements’, but this is not compulsory.
13
+ The first line of the document is the title. You can start the line with
14
+ ‘Requirements’, but this is not compulsory.
10
15
 
11
16
  Requirements Saga
12
17
 
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.
18
+ After the title follows a list of authors. It's encouraged to add all the
19
+ authors, this way people know who to speak to for more information about the
20
+ stories. Only the name of the author is compulsory.
14
21
 
15
22
  - Manfred Stienstra, manfred@fngtps.com, Fingertips, http://www.fngtps.com
16
23
 
@@ -18,28 +25,39 @@ The final part of the introduction is a concise description of the project.
18
25
 
19
26
  Saga is a tool to convert the requirements format used at Fingertips to HTML.
20
27
 
28
+
21
29
  === The stories
22
30
 
23
31
  The stories section starts with the USER STORIES header.
24
32
 
25
33
  USER STORIES
26
34
 
27
- After this follows a list of stories. You can choose to add section headers, but this is optional. For example:
35
+ After this follows a list of stories. You can choose to add section headers,
36
+ but this is optional. For example:
37
+
38
+ As a writer I would like to convert my description of the project to a nice format. - #1 todo
28
39
 
29
- As a writer I want to convert my description of the project to a nice format. - #1 todo
30
-
31
40
  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
41
+
42
+ As a writer I would like to see an example so that I don't have to remember all the details of the format. – #2 todo
34
43
  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
-
44
+ As a writer I would like to debug the parse process so I can find out what I did wrong. - #3 todo
45
+
46
+ Stories consist of a description and some extra information. The number behind
47
+ the hash is the unique number of the story. We use the id to point to a story
48
+ without having to type the whole description in everyday conversation. The
49
+ little text label at the end describes the status of the story.
50
+
51
+ A TextMate bundle for the stories format is available from:
52
+ https://github.com/Fingertips/stories.tmbundle
37
53
 
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
54
 
40
55
  === Definitions
41
56
 
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.
57
+ The document ends with a list of definitions. Here we define words used
58
+ throughout the document. We only define words if they need a strict definition
59
+ or if they might be misunderstood by someone. Like with the stories sections
60
+ are optional.
43
61
 
44
62
  ROLES
45
63
 
@@ -50,16 +68,33 @@ The document ends with a list of definitions. Here we define words used througho
50
68
 
51
69
  Project: The software project the developers are working on.
52
70
 
71
+
72
+ === Template
73
+
74
+ By default the Fingertips template is used when converting requirements to HTML.
75
+ You can, however, create a custom template. To start from the default one use
76
+ the <tt>template</tt> command:
77
+
78
+ $ saga template design/requirements_template
79
+
80
+ Edit the files in the created directory to your liking and then convert your
81
+ requirements with the <tt>--template</tt> option:
82
+
83
+ $ saga convert --template design/requirements_template requirements.txt > requirements.html
84
+
85
+
53
86
  === Usage
54
87
 
55
88
  Usage: saga [command]
56
-
89
+
57
90
  Commands:
58
91
  new - prints a blank stub
59
92
  convert <filename> - convert the stories to HTML
60
93
  inspect <filename> - print the internals of the document
61
94
  autofill <filename> - adds an id to stories without one
62
95
  planning <filename> - shows the planning of stories in iterations
63
-
96
+ template <dir> - creates a template directory
97
+
64
98
  Options:
99
+ -t, --template DIR Use an external template for conversion to HTML
65
100
  -h, --help Show help
data/Rakefile CHANGED
@@ -6,9 +6,7 @@ task :default => [:spec]
6
6
 
7
7
  desc "Run all specs"
8
8
  task :spec do
9
- Dir[File.dirname(__FILE__) + '/test/**/*_spec.rb'].each do |file|
10
- load file
11
- end
9
+ sh 'bacon test/*_spec.rb'
12
10
  end
13
11
 
14
12
  namespace :documentation do
@@ -32,4 +30,4 @@ begin
32
30
  s.add_development_dependency('mocha-on-bacon')
33
31
  end
34
32
  rescue LoadError
35
- end
33
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.2
1
+ 0.6.0
@@ -7,20 +7,23 @@ module Saga
7
7
  def initialize(document, options={})
8
8
  @document = document
9
9
  @options = options
10
- @options[:template] ||= 'default'
10
+ @options[:template] ||= File.join(self.class.template_path, 'default')
11
11
  end
12
12
 
13
13
  def format
14
- template_path = File.join(self.class.template_path, @options[:template])
14
+ helpers_file = File.join(@options[:template], 'helpers.rb')
15
+ if File.exist?(helpers_file)
16
+ load helpers_file
17
+ @document.extend(Helpers)
18
+ end
15
19
 
16
- helpers_file = File.join(template_path, 'helpers.rb')
17
- load helpers_file
18
- @document.extend(Helpers)
19
- binding = @document.send(:binding)
20
-
21
- template_file = File.join(template_path, 'document.erb')
22
- template = Erubis::Eruby.new(File.read(template_file))
23
- template.result(binding)
20
+ template_file = File.join(@options[:template], 'document.erb')
21
+ if File.exist?(template_file)
22
+ template = Erubis::Eruby.new(File.read(template_file))
23
+ template.result(@document.send(:binding))
24
+ else
25
+ raise ArgumentError, "The template at path `#{template_file}' could not be found."
26
+ end
24
27
  end
25
28
 
26
29
  def self.format(document, options={})
@@ -31,5 +34,9 @@ module Saga
31
34
  def self.template_path
32
35
  TEMPLATE_PATH
33
36
  end
37
+
38
+ def self.saga_format(document)
39
+ format(document, :template => File.join(template_path, 'saga'))
40
+ end
34
41
  end
35
- end
42
+ end
data/lib/saga/runner.rb CHANGED
@@ -17,9 +17,13 @@ module Saga
17
17
  opts.separator " inspect <filename> - print the internals of the document"
18
18
  opts.separator " autofill <filename> - adds an id to stories without one"
19
19
  opts.separator " planning <filename> - shows the planning of stories in iterations"
20
+ opts.separator " template <dir> - creates a template directory"
20
21
  opts.separator ""
21
22
  opts.separator "Options:"
22
- opts.on("-h", "--help", "Show help") do
23
+ opts.on("-t", "--template DIR", "Use an external template for conversion to HTML") do |template_path|
24
+ @options[:template] = File.expand_path(template_path)
25
+ end
26
+ opts.on("-h", "--help", "Show help") do
23
27
  puts opts
24
28
  exit
25
29
  end
@@ -40,11 +44,11 @@ module Saga
40
44
  :definition => 'Someone who is responsible for writing down requirements in the form of stories'
41
45
  }]
42
46
 
43
- Saga::Formatter.format(document, :template => 'saga')
47
+ Saga::Formatter.saga_format(document)
44
48
  end
45
49
 
46
- def convert(filename)
47
- Saga::Formatter.format(Saga::Parser.parse(File.read(filename)))
50
+ def convert(filename, options)
51
+ Saga::Formatter.format(Saga::Parser.parse(File.read(filename)), options)
48
52
  end
49
53
 
50
54
  def write_parsed_document(filename)
@@ -60,34 +64,46 @@ module Saga
60
64
  def autofill(filename)
61
65
  document = Saga::Parser.parse(File.read(filename))
62
66
  document.autofill_ids
63
- Saga::Formatter.format(document, :template => 'saga')
67
+ Saga::Formatter.saga_format(document)
64
68
  end
65
69
 
66
70
  def planning(filename)
67
71
  Saga::Planning.new(Saga::Parser.parse(File.read(filename))).to_s
68
72
  end
69
73
 
74
+ def copy_template(destination)
75
+ if File.exist?(destination)
76
+ puts "The directory `#{destination}' already exists!"
77
+ else
78
+ require 'fileutils'
79
+ FileUtils.mkdir_p(destination)
80
+ FileUtils.cp(File.join(Saga::Formatter.template_path, 'default/helpers.rb'), destination)
81
+ FileUtils.cp(File.join(Saga::Formatter.template_path, 'default/document.erb'), destination)
82
+ end
83
+ end
84
+
70
85
  def run_command(command, options)
71
86
  case command
72
87
  when 'new'
73
88
  puts new_file
74
89
  when 'convert'
75
- puts convert(File.expand_path(@argv[1]))
90
+ puts convert(File.expand_path(@argv[0]), options)
76
91
  when 'inspect'
77
- write_parsed_document(File.expand_path(@argv[1]))
92
+ write_parsed_document(File.expand_path(@argv[0]))
78
93
  when 'autofill'
79
- puts autofill(File.expand_path(@argv[1]))
94
+ puts autofill(File.expand_path(@argv[0]))
80
95
  when 'planning'
81
- puts planning(File.expand_path(@argv[1]))
96
+ puts planning(File.expand_path(@argv[0]))
97
+ when 'template'
98
+ copy_template(File.expand_path(@argv[0]))
82
99
  else
83
- puts convert(File.expand_path(command))
100
+ puts convert(File.expand_path(command), options)
84
101
  end
85
102
  end
86
103
 
87
104
  def run
88
- argv = @argv.dup
89
- parser.parse!(argv)
90
- if command = argv.shift
105
+ parser.parse!(@argv)
106
+ if command = @argv.shift
91
107
  run_command(command, @options)
92
108
  else
93
109
  puts parser.to_s
@@ -99,4 +115,4 @@ module Saga
99
115
  {:name => name}
100
116
  end
101
117
  end
102
- end
118
+ 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.5.2"
8
+ s.version = "0.6.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-08-13}
12
+ s.date = %q{2011-02-17}
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}
@@ -43,6 +43,7 @@ Gem::Specification.new do |s|
43
43
  "test/cases/definition.txt",
44
44
  "test/cases/story.txt",
45
45
  "test/cases/story_attributes.txt",
46
+ "test/fixtures/document.erb",
46
47
  "test/saga_document_spec.rb",
47
48
  "test/saga_formatter_spec.rb",
48
49
  "test/saga_parser_spec.rb",
@@ -0,0 +1,80 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
4
+ <head>
5
+ <title>Requirements <%= title %> &middot; Fingertips</title>
6
+ <link rel="stylesheet" type="text/css" media="all" href="http://resources.fngtps.com/2006/doc.css" />
7
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
8
+ <style type="text/css">
9
+ td.id { text-align: right !important; }
10
+ td.id a { color: #000; }
11
+ table.review { border-bottom: 1px solid #ccc; margin-top: -1em;}
12
+ table.review td { padding: .25em 0; vertical-align: top; text-align: left; min-width: 1em; }
13
+ table.review th { padding: .25em 0; vertical-align: top; text-align: right; }
14
+ table.review td.story { width: 100%; border-top: 1px solid #ccc; }
15
+ table.review td.meta { padding-left: 1em; border-top: 1px solid #ccc; text-align: right !important; white-space: nowrap; }
16
+ table.review td.notes { color: #666; padding: 0 0 .25em 0; font-style: italic; }
17
+ table.review .dropped { color: #666; text-decoration: line-through; }
18
+ table.review .done { color: #666; background-color: #f0f8ff; }
19
+ </style>
20
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
21
+ </head>
22
+ <body id="doc" class="requirements">
23
+
24
+ <p id="logo"><img src="http://resources.fngtps.com/2006/print-logo.png" alt="Fingertips design &amp; development" /> </p>
25
+
26
+ <h1>Requirements <br /><%= title %></h1>
27
+
28
+ <% authors.each do |author| %>
29
+ <p class="author"><%= author %></p>
30
+ <% end %>
31
+
32
+ <% introduction.each do |paragraph| %>
33
+ <p><%= paragraph %></p>
34
+ <% end %>
35
+
36
+ <% unless stories.empty? %>
37
+ <h2>User stories</h2>
38
+ <% stories.each do |header, stories| %>
39
+ <% unless header.strip == '' %>
40
+ <h3><%= header %></h3>
41
+ <% end %>
42
+ <table class="review">
43
+ <tr>
44
+ <th></th>
45
+ <th title="id">#</th>
46
+ <th title="Estimate">e</th>
47
+ <th title="Iteration">i</th>
48
+ <th title="Status">s</th>
49
+ </tr>
50
+ <% stories.each do |story| %>
51
+ <tr class="<%= story[:status] %>" id="story<%= story[:id] %>">
52
+ <td class="story"><%= story[:description] %></td>
53
+ <td class="meta id"><%= story[:id] %></td>
54
+ <td class="meta estimate"><%= story[:estimate] %></td>
55
+ <td class="meta iteration"><%= story[:iteration] %></td>
56
+ <td class="meta status"><%= story[:status] %></td>
57
+ </tr>
58
+ <% if story[:notes] %>
59
+ <tr class="<%= story[:status] %>">
60
+ <td class="notes" colspan="5"><%= story[:notes] %></td>
61
+ </tr>
62
+ <% end %>
63
+ <% end %>
64
+ </table>
65
+ <% end %>
66
+ <% end %>
67
+
68
+ <% definitions.each do |header, definitions| %>
69
+ <% unless header.strip == '' %>
70
+ <h2><%= header %></h2>
71
+ <% end %>
72
+ <dl>
73
+ <% definitions.each do |definition| %>
74
+ <dt><%= definition[:title] %></dt>
75
+ <dd><%= definition[:definition] %></dd>
76
+ <% end %>
77
+ </dl>
78
+ <% end %>
79
+ </body>
80
+ </html>
@@ -24,7 +24,21 @@ describe "Formatter" do
24
24
  end
25
25
 
26
26
  it "formats a saga document to saga" do
27
- saga = Saga::Formatter.format(@document, :template => 'saga')
27
+ saga = Saga::Formatter.saga_format(@document)
28
28
  saga.should.include('Requirements Requirements API')
29
29
  end
30
- end
30
+
31
+ describe "with an external template" do
32
+ it "raises when the document.erb file doesn't exist" do
33
+ lambda {
34
+ Saga::Formatter.format(@document, :template => '/does/not/exist')
35
+ }.should.raise(ArgumentError, "The template at path `/does/not/exist/document.erb' could not be found.")
36
+ end
37
+
38
+ it "omits the helpers.rb file it it doesn't exist" do
39
+ formatter = Saga::Formatter.new(@document, :template => File.expand_path('../fixtures', __FILE__))
40
+ formatter.expects(:load).never
41
+ formatter.format.should.not.be.empty
42
+ end
43
+ end
44
+ end
@@ -61,7 +61,7 @@ describe "A Runner" do
61
61
 
62
62
  it "converts the provided filename" do
63
63
  runner = Saga::Runner.new(%w(requirements.txt))
64
- runner.expects(:convert).with(File.expand_path('requirements.txt')).returns('output')
64
+ runner.expects(:convert).with(File.expand_path('requirements.txt'), {}).returns('output')
65
65
  collect_stdout do
66
66
  runner.run
67
67
  end.should == "output\n"
@@ -69,12 +69,22 @@ describe "A Runner" do
69
69
 
70
70
  it "converts the provided filename when the convert command is given" do
71
71
  runner = Saga::Runner.new(%w(convert requirements.txt))
72
- runner.expects(:convert).with(File.expand_path('requirements.txt')).returns('output')
72
+ runner.expects(:convert).with(File.expand_path('requirements.txt'), {}).returns('output')
73
73
  collect_stdout do
74
74
  runner.run
75
75
  end.should == "output\n"
76
76
  end
77
77
 
78
+ it "converts the provided filename with an external template" do
79
+ Saga::Parser.stubs(:parse)
80
+ File.stubs(:read)
81
+ Saga::Formatter.expects(:format).with do |_, options|
82
+ options[:template].should == File.expand_path('path/to/a/template')
83
+ end
84
+ runner = Saga::Runner.new(%W(convert --template path/to/a/template requirements.txt))
85
+ collect_stdout { runner.run }
86
+ end
87
+
78
88
  it "inspects the parsed document" do
79
89
  runner = Saga::Runner.new(%w(inspect requirements.txt))
80
90
  runner.expects(:write_parsed_document).with(File.expand_path('requirements.txt'))
@@ -96,4 +106,32 @@ describe "A Runner" do
96
106
  runner.run
97
107
  end.should == "output\n"
98
108
  end
99
- end
109
+
110
+ it "copies the default template to the specified path" do
111
+ begin
112
+ destination = "/tmp/saga-template-dir"
113
+ Saga::Runner.new(%W(template #{destination})).run
114
+ File.read(File.join(destination, 'helpers.rb')).should ==
115
+ File.read(File.join(Saga::Formatter.template_path, 'default/helpers.rb'))
116
+ File.read(File.join(destination, 'document.erb')).should ==
117
+ File.read(File.join(Saga::Formatter.template_path, 'default/document.erb'))
118
+ ensure
119
+ FileUtils.rm_rf(destination)
120
+ end
121
+ end
122
+
123
+ it "complains when tryin to create a template at an existing path" do
124
+ begin
125
+ destination = "/tmp/saga-template-dir"
126
+ FileUtils.mkdir_p(destination)
127
+ runner = Saga::Runner.new(%W(template #{destination}))
128
+ collect_stdout do
129
+ runner.run
130
+ end.should == "The directory `#{destination}' already exists!\n"
131
+ File.should.not.exist File.join(destination, 'helpers.rb')
132
+ File.should.not.exist File.join(destination, 'document.erb')
133
+ ensure
134
+ FileUtils.rm_rf(destination)
135
+ end
136
+ end
137
+ end
data/test/saga_spec.rb CHANGED
@@ -24,7 +24,7 @@ describe "Saga" do
24
24
  it "round-trips through the parser and formatter" do
25
25
  document = @document
26
26
  2.times do
27
- saga = Saga::Formatter.format(document, :template => 'saga')
27
+ saga = Saga::Formatter.saga_format(document)
28
28
  document = Saga::Parser.parse(saga)
29
29
  end
30
30
 
@@ -32,4 +32,4 @@ describe "Saga" do
32
32
  document.authors.should == @document.authors
33
33
  document.stories.should == @document.stories
34
34
  end
35
- end
35
+ end
data/test/spec_helper.rb CHANGED
@@ -7,4 +7,4 @@ require 'mocha-on-bacon'
7
7
 
8
8
  Bacon.extend Bacon::TapOutput
9
9
  $:.unshift(File.expand_path('../../lib', __FILE__))
10
- require 'saga'
10
+ require 'saga'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saga
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 5
9
- - 2
10
- version: 0.5.2
8
+ - 6
9
+ - 0
10
+ version: 0.6.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Manfred Stienstra
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-13 00:00:00 +02:00
18
+ date: 2011-02-17 00:00:00 +01:00
19
19
  default_executable: saga
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -96,6 +96,7 @@ files:
96
96
  - test/cases/definition.txt
97
97
  - test/cases/story.txt
98
98
  - test/cases/story_attributes.txt
99
+ - test/fixtures/document.erb
99
100
  - test/saga_document_spec.rb
100
101
  - test/saga_formatter_spec.rb
101
102
  - test/saga_parser_spec.rb