saga 0.1.0 → 0.2.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/Rakefile CHANGED
@@ -27,6 +27,9 @@ begin
27
27
  s.homepage = "http://fingertips.github.com"
28
28
  s.email = "manfred@fngtps.com"
29
29
  s.authors = ["Manfred Stienstra"]
30
+ s.add_dependency('erubis', '>= 2.6')
31
+ s.add_dependency('activesupport', '>= 2.3')
32
+ s.add_development_dependency('mocha-on-bacon')
30
33
  end
31
34
  rescue LoadError
32
35
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
data/lib/saga.rb CHANGED
@@ -1,3 +1,8 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue LoadError
4
+ end
5
+
1
6
  module Saga
2
7
  autoload :Document, 'saga/document'
3
8
  autoload :Formatter, 'saga/formatter'
data/lib/saga/document.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'active_support/ordered_hash'
2
+
1
3
  module Saga
2
4
  class Document
3
5
  attr_accessor :title, :introduction, :authors, :stories, :definitions
@@ -6,8 +8,8 @@ module Saga
6
8
  @title = ''
7
9
  @introduction = []
8
10
  @authors = []
9
- @stories = {}
10
- @definitions = {}
11
+ @stories = ActiveSupport::OrderedHash.new
12
+ @definitions = ActiveSupport::OrderedHash.new
11
13
  end
12
14
  end
13
15
  end
@@ -1,27 +1,30 @@
1
- require 'rubygems'
2
1
  require 'erubis'
3
2
 
4
3
  module Saga
5
4
  class Formatter
6
5
  TEMPLATE_PATH = File.expand_path('../../../templates', __FILE__)
7
6
 
8
- def initialize(document)
7
+ def initialize(document, options={})
9
8
  @document = document
9
+ @options = options
10
+ @options[:template] ||= 'default'
10
11
  end
11
12
 
12
13
  def format
13
- helpers_file = File.join(self.class.template_path, 'default/helpers.rb')
14
+ template_path = File.join(self.class.template_path, @options[:template])
15
+
16
+ helpers_file = File.join(template_path, 'helpers.rb')
14
17
  load helpers_file
15
18
  @document.extend(Helpers)
16
19
  binding = @document.send(:binding)
17
20
 
18
- template_file = File.join(self.class.template_path, 'default/document.erb')
21
+ template_file = File.join(template_path, 'document.erb')
19
22
  template = Erubis::Eruby.new(File.read(template_file))
20
23
  template.result(binding)
21
24
  end
22
25
 
23
- def self.format(document)
24
- formatter = new(document)
26
+ def self.format(document, options={})
27
+ formatter = new(document, options)
25
28
  formatter.format
26
29
  end
27
30
 
data/lib/saga/parser.rb CHANGED
@@ -19,11 +19,13 @@ module Saga
19
19
  end
20
20
 
21
21
  def handle_story(story)
22
+ @current_section = :stories
22
23
  @document.stories[@current_header] ||= []
23
24
  @document.stories[@current_header] << story
24
25
  end
25
26
 
26
27
  def handle_definition(definition)
28
+ @current_section = :definitions
27
29
  @document.definitions[@current_header] ||= []
28
30
  @document.definitions[@current_header] << definition
29
31
  end
@@ -32,12 +34,13 @@ module Saga
32
34
  return if string.strip == ''
33
35
  return(@current_section = :body) if string.strip == 'USER STORIES'
34
36
 
35
- case @current_section
36
- when :title
37
+ if :title == @current_section
37
38
  @document.title = string.gsub(/^requirements/i, '').strip
38
39
  @current_section = :introduction
39
- when :introduction
40
+ elsif :introduction == @current_section
40
41
  @document.introduction << string
42
+ elsif :stories == @current_section and string[0,2] == ' '
43
+ @document.stories[@current_header][-1][:notes] = string.strip
41
44
  else
42
45
  @current_header = string.strip
43
46
  end
data/lib/saga/runner.rb CHANGED
@@ -8,9 +8,15 @@ module Saga
8
8
  end
9
9
 
10
10
  def parser
11
- OptionParser.new do |opts|
12
- opts.banner = "Usage: saga [command] [options] <file>"
11
+ @parser ||= OptionParser.new do |opts|
12
+ opts.banner = "Usage: saga [command]"
13
13
  opts.separator ""
14
+ opts.separator "Commands:"
15
+ opts.separator " new - prints a blank stub"
16
+ opts.separator " convert <filename> - convert the stories to HTML"
17
+ opts.separator " inspect <filename> - print the internals of the document"
18
+ opts.separator ""
19
+ opts.separator "Options:"
14
20
  opts.on("-h", "--help", "Show help") do
15
21
  puts opts
16
22
  exit
@@ -18,13 +24,37 @@ module Saga
18
24
  end
19
25
  end
20
26
 
27
+ def new_file
28
+ document = Saga::Document.new
29
+ document.title = '(Title)'
30
+ document.authors << self.class.author
31
+ Saga::Formatter.format(document, :template => 'saga')
32
+ end
33
+
34
+ def write_parsed_document(filename)
35
+ document = Saga::Parser.parse(File.read(filename))
36
+ puts document.title
37
+ document.authors.each { |author| p author }
38
+ puts
39
+ document.stories.each { |header, stories| puts header; stories.each { |story| p story } }
40
+ puts
41
+ document.definitions.each { |header, definitions| puts header; definitions.each { |definition| p definition } }
42
+ end
43
+
44
+ def convert(filename)
45
+ Saga::Formatter.format(Saga::Parser.parse(File.read(filename)))
46
+ end
47
+
21
48
  def run_command(command, options)
22
49
  case command
23
- when :run
50
+ when 'new'
51
+ puts new_file
52
+ when 'convert'
53
+ puts convert(File.expand_path(@argv[1]))
54
+ when 'inspect'
55
+ write_parsed_document(File.expand_path(@argv[1]))
24
56
  else
25
- filename = File.expand_path(command)
26
- document = Saga::Parser.parse(File.read(filename))
27
- puts Saga::Formatter.format(document)
57
+ puts convert(File.expand_path(command))
28
58
  end
29
59
  end
30
60
 
@@ -37,5 +67,10 @@ module Saga
37
67
  puts parser.to_s
38
68
  end
39
69
  end
70
+
71
+ def self.author
72
+ name = `osascript -e "long user name of (system info)" &1> /dev/null`.strip
73
+ {:name => name}
74
+ end
40
75
  end
41
76
  end
@@ -9,7 +9,7 @@ module Saga
9
9
  @parser.handle_story(self.class.tokenize_story(input))
10
10
  elsif input[0,1] == '-'
11
11
  @parser.handle_author(self.class.tokenize_author(input))
12
- elsif input =~ /^(\w|[\s-])+:/
12
+ elsif input =~ /^\w(\w|[\s-])+:/
13
13
  @parser.handle_definition(self.class.tokenize_definition(input))
14
14
  else
15
15
  @parser.handle_string(input)
@@ -44,10 +44,14 @@ module Saga
44
44
  end
45
45
 
46
46
  def self.tokenize_story(input)
47
- description, attributes = input.split('-')
48
- story = tokenize_story_attributes(attributes)
49
- story[:description] = description.strip
50
- story
47
+ parts = input.split('-')
48
+ if parts.length > 1
49
+ story = tokenize_story_attributes(parts[-1])
50
+ story[:description] = parts[0..-2].join('-').strip
51
+ story
52
+ else
53
+ { :description => input.strip }
54
+ end
51
55
  end
52
56
 
53
57
  def self.tokenize_definition(input)
data/saga.gemspec ADDED
@@ -0,0 +1,85 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{saga}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Manfred Stienstra"]
12
+ s.date = %q{2010-03-31}
13
+ s.default_executable = %q{saga}
14
+ s.description = %q{Saga is a tool to convert stories syntax to a nicely formatted document.}
15
+ s.email = %q{manfred@fngtps.com}
16
+ s.executables = ["saga"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".gitignore",
23
+ ".kick",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/saga",
29
+ "lib/saga.rb",
30
+ "lib/saga/document.rb",
31
+ "lib/saga/formatter.rb",
32
+ "lib/saga/parser.rb",
33
+ "lib/saga/runner.rb",
34
+ "lib/saga/tokenizer.rb",
35
+ "saga.gemspec",
36
+ "templates/default/document.erb",
37
+ "templates/default/helpers.rb",
38
+ "templates/requirements.txt.erb",
39
+ "templates/saga/document.erb",
40
+ "templates/saga/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
+ ]
52
+ s.homepage = %q{http://fingertips.github.com}
53
+ s.rdoc_options = ["--charset=UTF-8"]
54
+ s.require_paths = ["lib"]
55
+ s.rubygems_version = %q{1.3.5}
56
+ s.summary = %q{Saga is a tool to convert stories syntax to a nicely formatted document.}
57
+ s.test_files = [
58
+ "test/saga_document_spec.rb",
59
+ "test/saga_formatter_spec.rb",
60
+ "test/saga_parser_spec.rb",
61
+ "test/saga_runner_spec.rb",
62
+ "test/saga_tokenizer_spec.rb",
63
+ "test/spec_helper.rb"
64
+ ]
65
+
66
+ if s.respond_to? :specification_version then
67
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
68
+ s.specification_version = 3
69
+
70
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
71
+ s.add_runtime_dependency(%q<erubis>, [">= 2.6"])
72
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.3"])
73
+ s.add_development_dependency(%q<mocha-on-bacon>, [">= 0"])
74
+ else
75
+ s.add_dependency(%q<erubis>, [">= 2.6"])
76
+ s.add_dependency(%q<activesupport>, [">= 2.3"])
77
+ s.add_dependency(%q<mocha-on-bacon>, [">= 0"])
78
+ end
79
+ else
80
+ s.add_dependency(%q<erubis>, [">= 2.6"])
81
+ s.add_dependency(%q<activesupport>, [">= 2.3"])
82
+ s.add_dependency(%q<mocha-on-bacon>, [">= 0"])
83
+ end
84
+ end
85
+
@@ -51,6 +51,11 @@
51
51
  <td class="meta iteration"><%= story[:iteration] %></td>
52
52
  <td class="meta status"><%= story[:status] %></td>
53
53
  </tr>
54
+ <% if story[:notes] %>
55
+ <tr class="<%= story[:status] %>">
56
+ <td class="notes" colspan="5"><%= story[:notes] %></td>
57
+ </tr>
58
+ <% end %>
54
59
  <% end %>
55
60
  </table>
56
61
  <% end %>
@@ -0,0 +1,7 @@
1
+ Requirements <%= title %>
2
+
3
+ - <%= author %>
4
+
5
+ USER STORIES
6
+
7
+ As a developer I would like to write stories so I know what to develop. - #1 todo
@@ -0,0 +1,7 @@
1
+ Requirements <%= title %>
2
+
3
+ <% authors.each do |author| %>
4
+ - <%= format_author(author) %>
5
+ <% end %>
6
+
7
+ USER STORIES
@@ -0,0 +1,7 @@
1
+ module Helpers
2
+ def format_author(author)
3
+ [:name, :email, :company, :website].map do |key|
4
+ author[key]
5
+ end.compact.join(', ')
6
+ end
7
+ end
data/test/cases/story.txt CHANGED
@@ -2,3 +2,5 @@ As a developer I would like to have created database indexes so that the queries
2
2
  => {:description => 'As a developer I would like to have created database indexes so that the queries run as fast as possible.'}
3
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
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' }
5
+ As a subscriber I would like to be able to buy additional pre-paid recording slots even though I also have a subscription so that I don’t have to upgrade my subscription level when I temporarily need to make a lot more recordings than usual. - #16 todo
6
+ => {:description => 'As a subscriber I would like to be able to buy additional pre-paid recording slots even though I also have a subscription so that I don’t have to upgrade my subscription level when I temporarily need to make a lot more recordings than usual.', :id => 16, :status => 'todo'}
@@ -7,4 +7,22 @@ describe "A Document" do
7
7
  document.should.respond_to(method)
8
8
  end
9
9
  end
10
+
11
+ it "stores stories in the order it receives them" do
12
+ document = Saga::Document.new
13
+ sections = %w(First Second Third Fourth Fifth)
14
+ sections.each do |section|
15
+ document.stories[section] = {}
16
+ end
17
+ document.stories.keys.should == sections
18
+ end
19
+
20
+ it "stores definitions in the order it receives them" do
21
+ document = Saga::Document.new
22
+ sections = %w(First Second Third Fourth Fifth)
23
+ sections.each do |section|
24
+ document.definitions[section] = {}
25
+ end
26
+ document.definitions.keys.should == sections
27
+ end
10
28
  end
@@ -10,4 +10,9 @@ describe "Formatter" do
10
10
  html = Saga::Formatter.format(@document)
11
11
  html.should.include('<h1>Requirements <br />Requirements API</h1>')
12
12
  end
13
+
14
+ it "formats a saga document to saga" do
15
+ saga = Saga::Formatter.format(@document, :template => 'saga')
16
+ saga.should.include('Requirements Requirements API')
17
+ end
13
18
  end
@@ -29,6 +29,10 @@ module ParserHelper
29
29
  parser.parse('As a recorder I would like to add a recording so that it becomes available. - #1 todo')
30
30
  end
31
31
 
32
+ def parse_story_comment
33
+ parser.parse(' “Your recording was created successfully.”')
34
+ end
35
+
32
36
  def parse_definition
33
37
  parser.parse('Other: Stories that don’t fit anywhere else.')
34
38
  end
@@ -87,6 +91,17 @@ describe "A Parser, concerning the handling of input" do
87
91
  parser.document.stories['Storage'].first[:id].should == 1
88
92
  end
89
93
 
94
+ it "interprets a comment after a story as being part of the story" do
95
+ parse_story_marker
96
+ parse_story
97
+ parse_story_comment
98
+
99
+ parser.document.stories.keys.should == ['']
100
+ parser.document.stories[''].length.should == 1
101
+ parser.document.stories[''].first[:id].should == 1
102
+ parser.document.stories[''].first[:notes].should == '“Your recording was created successfully.”'
103
+ end
104
+
90
105
  it "interprets definitions without a header as being a gobal definition" do
91
106
  parse_title
92
107
  parse_introduction
@@ -1,16 +1,83 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
+ require 'singleton'
3
+
4
+ module OutputHelper
5
+ class Collector
6
+ attr_reader :written
7
+ def initialize
8
+ @written = []
9
+ end
10
+
11
+ def write(string)
12
+ @written << string
13
+ end
14
+ end
15
+
16
+ def collect_stdout(&block)
17
+ collector = Collector.new
18
+ stdout = $stdout
19
+ $stdout = collector
20
+ begin
21
+ block.call
22
+ ensure
23
+ $stdout = stdout
24
+ end
25
+ collector.written.join
26
+ end
27
+ end
2
28
 
3
29
  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
30
+ extend OutputHelper
31
+
32
+ it "shows a help text when invoked without a command and options" do
33
+ runner = Saga::Runner.new([])
34
+ collect_stdout do
35
+ runner.run
36
+ end.should == runner.parser.to_s
37
+ end
38
+
39
+ it "shows a help test when the -h option is used" do
40
+ runner = Saga::Runner.new(%w(-h))
41
+ runner.stubs(:exit)
42
+ collect_stdout do
43
+ runner.run
44
+ end.should == runner.parser.to_s*2 # Because we stub exit it runs twice ):
45
+ end
46
+
47
+ it "generates a requirements stub to can get started" do
48
+ Saga::Runner.stubs(:author).returns({:name => "Manfred Stienstra"})
49
+ runner = Saga::Runner.new(%w(new))
50
+ output = collect_stdout do
51
+ runner.run
52
+ end
53
+ output.should.include('Requirements (Title)')
54
+ output.should.include('- Manfred Stienstra')
55
+ end
56
+
57
+ it "knows information about the user currently logged in to the system" do
58
+ author = Saga::Runner.author
59
+ author[:name].should.not.be.nil
60
+ end
61
+
62
+ it "converts the provided filename" do
63
+ runner = Saga::Runner.new(%w(requirements.txt))
64
+ runner.expects(:convert).with(File.expand_path('requirements.txt')).returns('output')
65
+ collect_stdout do
66
+ runner.run
67
+ end.should == "output\n"
68
+ end
69
+
70
+ it "converts the provided filename when the convert command is given" do
71
+ runner = Saga::Runner.new(%w(convert requirements.txt))
72
+ runner.expects(:convert).with(File.expand_path('requirements.txt')).returns('output')
73
+ collect_stdout do
74
+ runner.run
75
+ end.should == "output\n"
8
76
  end
9
77
 
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
78
+ it "inspects the parsed document" do
79
+ runner = Saga::Runner.new(%w(inspect requirements.txt))
80
+ runner.expects(:write_parsed_document).with(File.expand_path('requirements.txt'))
81
+ runner.run
15
82
  end
16
83
  end
@@ -77,6 +77,12 @@ describe "A Tokenizer" do
77
77
  @tokenizer.process_line(line)
78
78
  end
79
79
 
80
+ it "doesn't mistake a story note with a semicolon as a definition" do
81
+ line = ' It would be nice if we could use http://www.braintreepaymentsolutions.com/'
82
+ @parser.expects(:handle_string).with(line)
83
+ @tokenizer.process_line(line)
84
+ end
85
+
80
86
  it "send a tokenize defintion to the parser (slighly more complex)" do
81
87
  line = 'Search and retrieval: Stories related to selecting and retrieving recordings.'
82
88
  definition = Saga::Tokenizer.tokenize_definition(line)
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manfred Stienstra
@@ -11,8 +11,37 @@ cert_chain: []
11
11
 
12
12
  date: 2010-03-31 00:00:00 +02:00
13
13
  default_executable: saga
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: erubis
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "2.6"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "2.3"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha-on-bacon
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
16
45
  description: Saga is a tool to convert stories syntax to a nicely formatted document.
17
46
  email: manfred@fngtps.com
18
47
  executables:
@@ -36,8 +65,12 @@ files:
36
65
  - lib/saga/parser.rb
37
66
  - lib/saga/runner.rb
38
67
  - lib/saga/tokenizer.rb
68
+ - saga.gemspec
39
69
  - templates/default/document.erb
40
70
  - templates/default/helpers.rb
71
+ - templates/requirements.txt.erb
72
+ - templates/saga/document.erb
73
+ - templates/saga/helpers.rb
41
74
  - test/cases/author.txt
42
75
  - test/cases/definition.txt
43
76
  - test/cases/story.txt