saga 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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