saga 0.1.0

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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ html
2
+ pkg
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
@@ -0,0 +1,3 @@
1
+ = Saga
2
+
3
+ Saga is a tool to convert the story format used at Fingertips to a readable document.
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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path('../../lib', __FILE__))
4
+ require 'saga'
5
+ Saga.run(ARGV)
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,13 @@
1
+ module Saga
2
+ class Document
3
+ attr_accessor :title, :introduction, :authors, :stories, :definitions
4
+
5
+ def initialize
6
+ @title = ''
7
+ @introduction = []
8
+ @authors = []
9
+ @stories = {}
10
+ @definitions = {}
11
+ end
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
@@ -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
@@ -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 %> &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"><%= 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
+
2
+ => {}
3
+
4
+ => {}
5
+ #4
6
+ => {:id => 4}
7
+ #12 todo
8
+ => {:id => 12, :status => 'todo'}
9
+ #3 partially done
10
+ => {:id => 3, :status => 'partially done'}
@@ -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
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue LoadError
4
+ end
5
+
6
+ require 'mocha-on-bacon'
7
+
8
+ Bacon.extend Bacon::TapOutput
9
+ $:.unshift(File.expand_path('../../lib', __FILE__))
10
+ require 'saga'
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