saga 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.kick +14 -0
- data/LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +32 -0
- data/VERSION +1 -0
- data/bin/saga +5 -0
- data/lib/saga.rb +13 -0
- data/lib/saga/document.rb +13 -0
- data/lib/saga/formatter.rb +32 -0
- data/lib/saga/parser.rb +51 -0
- data/lib/saga/runner.rb +41 -0
- data/lib/saga/tokenizer.rb +71 -0
- data/templates/default/document.erb +71 -0
- data/templates/default/helpers.rb +17 -0
- data/test/cases/author.txt +8 -0
- data/test/cases/definition.txt +10 -0
- data/test/cases/story.txt +4 -0
- data/test/cases/story_attributes.txt +10 -0
- data/test/saga_document_spec.rb +10 -0
- data/test/saga_formatter_spec.rb +13 -0
- data/test/saga_parser_spec.rb +112 -0
- data/test/saga_runner_spec.rb +16 -0
- data/test/saga_tokenizer_spec.rb +105 -0
- data/test/spec_helper.rb +10 -0
- metadata +85 -0
data/.gitignore
ADDED
data/.kick
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
recipe :ignore
|
2
|
+
ignore(/(^tmp|\.DS_Store)$/)
|
3
|
+
|
4
|
+
recipe :ruby
|
5
|
+
process do |files|
|
6
|
+
Ruby.run_tests(files.take_and_map do |file|
|
7
|
+
case file
|
8
|
+
when %r{^lib/(.+)\.rb$}
|
9
|
+
"test/#{$1.gsub('/', '_')}_spec.rb"
|
10
|
+
when %r{^test/(.+)_spec\.rb$}
|
11
|
+
file
|
12
|
+
end
|
13
|
+
end)
|
14
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Fingertips, Manfred Stienstra <manfred@fngtps.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
|
4
|
+
desc "Run all specs by default"
|
5
|
+
task :default => [:spec]
|
6
|
+
|
7
|
+
desc "Run all specs"
|
8
|
+
task :spec do
|
9
|
+
Dir[File.dirname(__FILE__) + '/test/**/*_spec.rb'].each do |file|
|
10
|
+
load file
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
namespace :documentation do
|
15
|
+
Rake::RDocTask.new(:generate) do |rd|
|
16
|
+
rd.main = "README.rdoc"
|
17
|
+
rd.rdoc_files.include("README.rdoc", "LICENSE", "bin/**/*.rb", "lib/**/*.rb", "templates/**/*.rb")
|
18
|
+
rd.options << "--all" << "--charset" << "utf-8"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'jeweler'
|
24
|
+
Jeweler::Tasks.new do |s|
|
25
|
+
s.name = "saga"
|
26
|
+
s.summary = s.description = "Saga is a tool to convert stories syntax to a nicely formatted document."
|
27
|
+
s.homepage = "http://fingertips.github.com"
|
28
|
+
s.email = "manfred@fngtps.com"
|
29
|
+
s.authors = ["Manfred Stienstra"]
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/saga
ADDED
data/lib/saga.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Saga
|
2
|
+
autoload :Document, 'saga/document'
|
3
|
+
autoload :Formatter, 'saga/formatter'
|
4
|
+
autoload :Parser, 'saga/parser'
|
5
|
+
autoload :Runner, 'saga/runner'
|
6
|
+
autoload :Tokenizer, 'saga/tokenizer'
|
7
|
+
|
8
|
+
def self.run(argv)
|
9
|
+
runner = ::Saga::Runner.new(argv)
|
10
|
+
runner.run
|
11
|
+
runner
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'erubis'
|
3
|
+
|
4
|
+
module Saga
|
5
|
+
class Formatter
|
6
|
+
TEMPLATE_PATH = File.expand_path('../../../templates', __FILE__)
|
7
|
+
|
8
|
+
def initialize(document)
|
9
|
+
@document = document
|
10
|
+
end
|
11
|
+
|
12
|
+
def format
|
13
|
+
helpers_file = File.join(self.class.template_path, 'default/helpers.rb')
|
14
|
+
load helpers_file
|
15
|
+
@document.extend(Helpers)
|
16
|
+
binding = @document.send(:binding)
|
17
|
+
|
18
|
+
template_file = File.join(self.class.template_path, 'default/document.erb')
|
19
|
+
template = Erubis::Eruby.new(File.read(template_file))
|
20
|
+
template.result(binding)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.format(document)
|
24
|
+
formatter = new(document)
|
25
|
+
formatter.format
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.template_path
|
29
|
+
TEMPLATE_PATH
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/saga/parser.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Saga
|
2
|
+
class Parser
|
3
|
+
attr_accessor :document
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@tokenizer = ::Saga::Tokenizer.new(self)
|
7
|
+
@document = ::Saga::Document.new
|
8
|
+
@current_section = :title
|
9
|
+
@current_header = ''
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(input)
|
13
|
+
@tokenizer.process(input)
|
14
|
+
@document
|
15
|
+
end
|
16
|
+
|
17
|
+
def handle_author(author)
|
18
|
+
@document.authors << author
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle_story(story)
|
22
|
+
@document.stories[@current_header] ||= []
|
23
|
+
@document.stories[@current_header] << story
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle_definition(definition)
|
27
|
+
@document.definitions[@current_header] ||= []
|
28
|
+
@document.definitions[@current_header] << definition
|
29
|
+
end
|
30
|
+
|
31
|
+
def handle_string(string)
|
32
|
+
return if string.strip == ''
|
33
|
+
return(@current_section = :body) if string.strip == 'USER STORIES'
|
34
|
+
|
35
|
+
case @current_section
|
36
|
+
when :title
|
37
|
+
@document.title = string.gsub(/^requirements/i, '').strip
|
38
|
+
@current_section = :introduction
|
39
|
+
when :introduction
|
40
|
+
@document.introduction << string
|
41
|
+
else
|
42
|
+
@current_header = string.strip
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.parse(input)
|
47
|
+
parser = new
|
48
|
+
parser.parse(input)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/saga/runner.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Saga
|
4
|
+
class Runner
|
5
|
+
def initialize(argv)
|
6
|
+
@argv = argv
|
7
|
+
@options = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def parser
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: saga [command] [options] <file>"
|
13
|
+
opts.separator ""
|
14
|
+
opts.on("-h", "--help", "Show help") do
|
15
|
+
puts opts
|
16
|
+
exit
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_command(command, options)
|
22
|
+
case command
|
23
|
+
when :run
|
24
|
+
else
|
25
|
+
filename = File.expand_path(command)
|
26
|
+
document = Saga::Parser.parse(File.read(filename))
|
27
|
+
puts Saga::Formatter.format(document)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
argv = @argv.dup
|
33
|
+
parser.parse!(argv)
|
34
|
+
if command = argv.shift
|
35
|
+
run_command(command, @options)
|
36
|
+
else
|
37
|
+
puts parser.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Saga
|
2
|
+
class Tokenizer
|
3
|
+
def initialize(parser)
|
4
|
+
@parser = parser
|
5
|
+
end
|
6
|
+
|
7
|
+
def process_line(input)
|
8
|
+
if input[0,2].downcase == 'as'
|
9
|
+
@parser.handle_story(self.class.tokenize_story(input))
|
10
|
+
elsif input[0,1] == '-'
|
11
|
+
@parser.handle_author(self.class.tokenize_author(input))
|
12
|
+
elsif input =~ /^(\w|[\s-])+:/
|
13
|
+
@parser.handle_definition(self.class.tokenize_definition(input))
|
14
|
+
else
|
15
|
+
@parser.handle_string(input)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(input)
|
20
|
+
input.split("\n").each do |line|
|
21
|
+
process_line(line)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.tokenize_story_attributes(input)
|
26
|
+
return {} if input.nil?
|
27
|
+
|
28
|
+
attributes = {}
|
29
|
+
rest = []
|
30
|
+
parts = input.split(/\s/)
|
31
|
+
|
32
|
+
parts.each do |part|
|
33
|
+
if part.strip == ''
|
34
|
+
next
|
35
|
+
elsif match = /\#(\d+)/.match(part)
|
36
|
+
attributes[:id] = match[1].to_i
|
37
|
+
else
|
38
|
+
rest << part
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
attributes[:status] = rest.join(' ') unless rest.empty?
|
43
|
+
attributes
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.tokenize_story(input)
|
47
|
+
description, attributes = input.split('-')
|
48
|
+
story = tokenize_story_attributes(attributes)
|
49
|
+
story[:description] = description.strip
|
50
|
+
story
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.tokenize_definition(input)
|
54
|
+
if match = /^([^:]+)\s*:\s*(.+)\s*$/.match(input)
|
55
|
+
{:title => match[1], :definition => match[2]}
|
56
|
+
else
|
57
|
+
{}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.tokenize_author(input)
|
62
|
+
author = {}
|
63
|
+
parts = input[1..-1].split(',')
|
64
|
+
author[:name] = parts[0].strip if parts[0]
|
65
|
+
author[:email] = parts[1].strip if parts[1]
|
66
|
+
author[:company] = parts[2].strip if parts[2]
|
67
|
+
author[:website] = parts[3].strip if parts[3]
|
68
|
+
author
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,71 @@
|
|
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 %> · 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 & development" /> </p>
|
25
|
+
|
26
|
+
<h1>Requirements <br /><%= title %></h1>
|
27
|
+
|
28
|
+
<% authors.each do |author| %>
|
29
|
+
<p class="author"><%= format_author(author) %></p>
|
30
|
+
<% end %>
|
31
|
+
|
32
|
+
<% unless stories.empty? %>
|
33
|
+
<h2>User stories</h2>
|
34
|
+
<% stories.each do |header, stories| %>
|
35
|
+
<% unless header.strip == '' %>
|
36
|
+
<h3><%= header %></h3>
|
37
|
+
<% end %>
|
38
|
+
<table class="review">
|
39
|
+
<tr>
|
40
|
+
<th></th>
|
41
|
+
<th title="id">#</th>
|
42
|
+
<th title="Estimate">e</th>
|
43
|
+
<th title="Iteration">i</th>
|
44
|
+
<th title="Status">s</th>
|
45
|
+
</tr>
|
46
|
+
<% stories.each do |story| %>
|
47
|
+
<tr class="<%= story[:status] %>" id="story<%= story[:id] %>">
|
48
|
+
<td class="story"><%= story[:description] %></td>
|
49
|
+
<td class="meta id"><%= story[:id] %></td>
|
50
|
+
<td class="meta estimate"><%= story[:estimate] %></td>
|
51
|
+
<td class="meta iteration"><%= story[:iteration] %></td>
|
52
|
+
<td class="meta status"><%= story[:status] %></td>
|
53
|
+
</tr>
|
54
|
+
<% end %>
|
55
|
+
</table>
|
56
|
+
<% end %>
|
57
|
+
<% end %>
|
58
|
+
|
59
|
+
<% definitions.each do |header, definitions| %>
|
60
|
+
<% unless header.strip == '' %>
|
61
|
+
<h2><%= format_header(header) %></h2>
|
62
|
+
<% end %>
|
63
|
+
<dl>
|
64
|
+
<% definitions.each do |definition| %>
|
65
|
+
<dt><%= definition[:title] %></dt>
|
66
|
+
<dd><%= definition[:definition] %></dd>
|
67
|
+
<% end %>
|
68
|
+
</dl>
|
69
|
+
<% end %>
|
70
|
+
</body>
|
71
|
+
</html>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Helpers
|
2
|
+
def format_author(author)
|
3
|
+
parts = []
|
4
|
+
parts << author[:name] if author[:name]
|
5
|
+
parts << "<a href=\"mailto:#{author[:email]}\">#{author[:email]}</a>" if author[:email]
|
6
|
+
if author[:website] and author[:company]
|
7
|
+
parts << "<a href=\"#{author[:website]}\">#{author[:company]}</a>"
|
8
|
+
elsif author[:company]
|
9
|
+
parts << author[:company]
|
10
|
+
end
|
11
|
+
parts.join(', ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def format_header(header)
|
15
|
+
"#{header[0,1].upcase}#{header[1..-1].downcase}"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
- Thijs van der Vossen
|
2
|
+
=> {:name => 'Thijs van der Vossen'}
|
3
|
+
- Thijs van der Vossen, thijs@fngtps.com
|
4
|
+
=> {:name => 'Thijs van der Vossen', :email => 'thijs@fngtps.com'}
|
5
|
+
- Thijs van der Vossen, thijs@fngtps.com, Fingertips
|
6
|
+
=> {:name => 'Thijs van der Vossen', :email => 'thijs@fngtps.com', :company => 'Fingertips'}
|
7
|
+
- Thijs van der Vossen, thijs@fngtps.com, Fingertips, http://www.fngtps.com
|
8
|
+
=> {:name => 'Thijs van der Vossen', :email => 'thijs@fngtps.com', :company => 'Fingertips', :website => 'http://www.fngtps.com'}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
|
2
|
+
=> {}
|
3
|
+
User: A user
|
4
|
+
=> {:title => 'User', :definition => 'A user'}
|
5
|
+
Administrator: Any user with full administrative access.
|
6
|
+
=> {:title => 'Administrator', :definition => 'Any user with full administrative access.'}
|
7
|
+
Non-functional: Stories related to non-functional requirements.
|
8
|
+
=> {:title => 'Non-functional', :definition => 'Stories related to non-functional requirements.'}
|
9
|
+
Service: The part of the web service used by clients to provide search and retrieval as well as account, device, and user management.
|
10
|
+
=> {:title => 'Service', :definition => 'The part of the web service used by clients to provide search and retrieval as well as account, device, and user management.'}
|
@@ -0,0 +1,4 @@
|
|
1
|
+
As a developer I would like to have created database indexes so that the queries run as fast as possible.
|
2
|
+
=> {:description => 'As a developer I would like to have created database indexes so that the queries run as fast as possible.'}
|
3
|
+
As a recorder I would like to use TLS (SSL) so that my connection with the storage API is secure and I can be sure of the API’s identity. - #4 todo
|
4
|
+
=> {:description => 'As a recorder I would like to use TLS (SSL) so that my connection with the storage API is secure and I can be sure of the API’s identity.', :id => 4, :status => 'todo' }
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "A Document" do
|
4
|
+
it "responds to accessors" do
|
5
|
+
document = Saga::Document.new
|
6
|
+
[:title, :introduction, :authors, :stories, :definitions].each do |method|
|
7
|
+
document.should.respond_to(method)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Formatter" do
|
4
|
+
before do
|
5
|
+
@document = Saga::Document.new
|
6
|
+
@document.title = 'Requirements API'
|
7
|
+
end
|
8
|
+
|
9
|
+
it "formats a saga document to HTML" do
|
10
|
+
html = Saga::Formatter.format(@document)
|
11
|
+
html.should.include('<h1>Requirements <br />Requirements API</h1>')
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
module ParserHelper
|
4
|
+
def parser
|
5
|
+
@parser ||= Saga::Parser.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse(input)
|
9
|
+
Saga::Parser.parse(input)
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_title
|
13
|
+
parser.parse("Requirements API\n\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse_introduction
|
17
|
+
parser.parse("This document describes our API.\n\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_story_marker
|
21
|
+
parser.parse('USER STORIES')
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_header
|
25
|
+
parser.parse('Storage')
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_story
|
29
|
+
parser.parse('As a recorder I would like to add a recording so that it becomes available. - #1 todo')
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_definition
|
33
|
+
parser.parse('Other: Stories that don’t fit anywhere else.')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "Parser" do
|
38
|
+
it "should initialize and parse" do
|
39
|
+
document = Saga::Parser.parse('')
|
40
|
+
document.should.be.kind_of?(Saga::Document)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "A Parser, concerning the handling of input" do
|
45
|
+
extend ParserHelper
|
46
|
+
before { @parser = nil }
|
47
|
+
|
48
|
+
it "interprets the first line of the document as a title" do
|
49
|
+
document = parse('Requirements API')
|
50
|
+
document.title.should == 'API'
|
51
|
+
|
52
|
+
document = parse('Record and Search')
|
53
|
+
document.title.should == 'Record and Search'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "interprets authors" do
|
57
|
+
document = parse('- Manfred Stienstra')
|
58
|
+
document.authors.map { |author| author[:name] }.should == ['Manfred Stienstra']
|
59
|
+
end
|
60
|
+
|
61
|
+
it "interprets the introduction" do
|
62
|
+
parse_title
|
63
|
+
parse_introduction
|
64
|
+
parser.document.introduction.should == ['This document describes our API.']
|
65
|
+
end
|
66
|
+
|
67
|
+
it "interprets stories without a header as being a gobal story" do
|
68
|
+
parse_title
|
69
|
+
parse_introduction
|
70
|
+
parse_story_marker
|
71
|
+
parse_story
|
72
|
+
|
73
|
+
parser.document.stories.keys.should == ['']
|
74
|
+
parser.document.stories[''].length.should == 1
|
75
|
+
parser.document.stories[''].first[:id].should == 1
|
76
|
+
end
|
77
|
+
|
78
|
+
it "interprets stories with a header as being part of that section" do
|
79
|
+
parse_title
|
80
|
+
parse_introduction
|
81
|
+
parse_story_marker
|
82
|
+
parse_header
|
83
|
+
parse_story
|
84
|
+
|
85
|
+
parser.document.stories.keys.should == ['Storage']
|
86
|
+
parser.document.stories['Storage'].length.should == 1
|
87
|
+
parser.document.stories['Storage'].first[:id].should == 1
|
88
|
+
end
|
89
|
+
|
90
|
+
it "interprets definitions without a header as being a gobal definition" do
|
91
|
+
parse_title
|
92
|
+
parse_introduction
|
93
|
+
parse_story_marker
|
94
|
+
parse_definition
|
95
|
+
|
96
|
+
parser.document.definitions.keys.should == ['']
|
97
|
+
parser.document.definitions[''].length.should == 1
|
98
|
+
parser.document.definitions[''].first[:title].should == 'Other'
|
99
|
+
end
|
100
|
+
|
101
|
+
it "interprets definitions with a header as being part of that section" do
|
102
|
+
parse_title
|
103
|
+
parse_introduction
|
104
|
+
parse_story_marker
|
105
|
+
parse_header
|
106
|
+
parse_definition
|
107
|
+
|
108
|
+
parser.document.definitions.keys.should == ['Storage']
|
109
|
+
parser.document.definitions['Storage'].length.should == 1
|
110
|
+
parser.document.definitions['Storage'].first[:title].should == 'Other'
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "A Runner" do
|
4
|
+
it "should show a help text when invoked without a command and options" do
|
5
|
+
@runner = Saga::Runner.new([])
|
6
|
+
@runner.expects(:puts).with(@runner.parser.to_s)
|
7
|
+
@runner.run
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should show a help test when the -h option is used" do
|
11
|
+
@runner = Saga::Runner.new(%w(-h))
|
12
|
+
@runner.expects(:puts).at_least(1)
|
13
|
+
@runner.stubs(:exit)
|
14
|
+
@runner.run
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
module CasesHelper
|
4
|
+
def _parse_expected(line)
|
5
|
+
eval(line[3..-1])
|
6
|
+
end
|
7
|
+
|
8
|
+
def each_case(path)
|
9
|
+
filename = File.expand_path("../cases/#{path}.txt", __FILE__)
|
10
|
+
input = nil
|
11
|
+
File.readlines(filename).each do |line|
|
12
|
+
if input.nil?
|
13
|
+
input = line.strip
|
14
|
+
else
|
15
|
+
yield input, _parse_expected(line)
|
16
|
+
input = nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "Tokenizer" do
|
23
|
+
extend CasesHelper
|
24
|
+
|
25
|
+
it "tokenizes story attributes input" do
|
26
|
+
each_case('story_attributes') do |input, expected|
|
27
|
+
Saga::Tokenizer.tokenize_story_attributes(input).should == expected
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "tokenizes story input" do
|
32
|
+
each_case('story') do |input, expected|
|
33
|
+
Saga::Tokenizer.tokenize_story(input).should == expected
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "tokenizes definition input" do
|
38
|
+
each_case('definition') do |input, expected|
|
39
|
+
Saga::Tokenizer.tokenize_definition(input).should == expected
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "tokenizes author input" do
|
44
|
+
each_case('author') do |input, expected|
|
45
|
+
Saga::Tokenizer.tokenize_author(input).should == expected
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "A Tokenizer" do
|
51
|
+
before do
|
52
|
+
@parser = stub('Parser')
|
53
|
+
@tokenizer = Saga::Tokenizer.new(@parser)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "sends a tokenized story to the parser" do
|
57
|
+
line = 'As a recorder I would like to use TLS (SSL) so that my connection with the storage API is secure and I can be sure of the API’s identity. - #4 todo'
|
58
|
+
story = Saga::Tokenizer.tokenize_story(line)
|
59
|
+
|
60
|
+
@parser.expects(:handle_story).with(story)
|
61
|
+
@tokenizer.process_line(line)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "sends a tokenized author to the parser" do
|
65
|
+
line = '- Manfred Stienstra, manfred@fngtps.com'
|
66
|
+
author = Saga::Tokenizer.tokenize_author(line)
|
67
|
+
|
68
|
+
@parser.expects(:handle_author).with(author)
|
69
|
+
@tokenizer.process_line(line)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "sends a tokenized definition to the parser" do
|
73
|
+
line = 'Author: Someone who writes'
|
74
|
+
definition = Saga::Tokenizer.tokenize_definition(line)
|
75
|
+
|
76
|
+
@parser.expects(:handle_definition).with(definition)
|
77
|
+
@tokenizer.process_line(line)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "send a tokenize defintion to the parser (slighly more complex)" do
|
81
|
+
line = 'Search and retrieval: Stories related to selecting and retrieving recordings.'
|
82
|
+
definition = Saga::Tokenizer.tokenize_definition(line)
|
83
|
+
|
84
|
+
@parser.expects(:handle_definition).with(definition)
|
85
|
+
@tokenizer.process_line(line)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "forwards plain strings to the parser" do
|
89
|
+
[
|
90
|
+
'Requirements User Application',
|
91
|
+
'USER STORIES',
|
92
|
+
''
|
93
|
+
].each do |line|
|
94
|
+
@parser.expects(:handle_string).with(line)
|
95
|
+
@tokenizer.process_line(line)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "processes lines from the input" do
|
100
|
+
input = File.read(File.expand_path("../cases/story.txt", __FILE__))
|
101
|
+
count = input.split("\n").length
|
102
|
+
@tokenizer.expects(:process_line).times(count)
|
103
|
+
@tokenizer.process(input)
|
104
|
+
end
|
105
|
+
end
|
data/test/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: saga
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Manfred Stienstra
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-03-31 00:00:00 +02:00
|
13
|
+
default_executable: saga
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Saga is a tool to convert stories syntax to a nicely formatted document.
|
17
|
+
email: manfred@fngtps.com
|
18
|
+
executables:
|
19
|
+
- saga
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- .kick
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
- Rakefile
|
31
|
+
- VERSION
|
32
|
+
- bin/saga
|
33
|
+
- lib/saga.rb
|
34
|
+
- lib/saga/document.rb
|
35
|
+
- lib/saga/formatter.rb
|
36
|
+
- lib/saga/parser.rb
|
37
|
+
- lib/saga/runner.rb
|
38
|
+
- lib/saga/tokenizer.rb
|
39
|
+
- templates/default/document.erb
|
40
|
+
- templates/default/helpers.rb
|
41
|
+
- test/cases/author.txt
|
42
|
+
- test/cases/definition.txt
|
43
|
+
- test/cases/story.txt
|
44
|
+
- test/cases/story_attributes.txt
|
45
|
+
- test/saga_document_spec.rb
|
46
|
+
- test/saga_formatter_spec.rb
|
47
|
+
- test/saga_parser_spec.rb
|
48
|
+
- test/saga_runner_spec.rb
|
49
|
+
- test/saga_tokenizer_spec.rb
|
50
|
+
- test/spec_helper.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://fingertips.github.com
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --charset=UTF-8
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.5
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Saga is a tool to convert stories syntax to a nicely formatted document.
|
79
|
+
test_files:
|
80
|
+
- test/saga_document_spec.rb
|
81
|
+
- test/saga_formatter_spec.rb
|
82
|
+
- test/saga_parser_spec.rb
|
83
|
+
- test/saga_runner_spec.rb
|
84
|
+
- test/saga_tokenizer_spec.rb
|
85
|
+
- test/spec_helper.rb
|