pdf-storycards 0.0.1 → 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.
@@ -1,3 +1,10 @@
1
+ == 0.1 / 2008-02-09
2
+
3
+ * Reads stories from STDIN, writes PDF to STDOUT, makes for more Unix-friendly command line tool and more usable library class
4
+ * Turned off munging of story narrative into a sentence by default
5
+ * Introduced concept of metadata (colon-separated name value pairs) that are parsed out of tops of stories (before Story: ...) and the ability to specify metadata keys for the lower left and/or lower right of the cards -- the corresponding values will be printed there.
6
+ * Added a real-world example script: printing cards from Thoughtworks Studio's Mingle. In examples/mingle_cards.rb.
7
+
1
8
  == 0.0.1 / 2007-12-27
2
9
 
3
10
  * Initial release
@@ -3,15 +3,24 @@ Manifest.txt
3
3
  README.txt
4
4
  Rakefile
5
5
  bin/stories2cards
6
+ examples/mingle_cards.rb
6
7
  lib/pdf/storycards.rb
7
8
  lib/pdf/storycards/scenario.rb
8
9
  lib/pdf/storycards/story.rb
9
10
  lib/pdf/storycards/story_capturing_mediator.rb
11
+ lib/pdf/storycards/story_parser.rb
12
+ lib/pdf/storycards/text_file_filter.rb
10
13
  lib/pdf/storycards/writer.rb
11
14
  spec/addition
12
15
  spec/calculator_stories
16
+ spec/calculator_stories_with_metadata
17
+ spec/not_a_text_file.gif
18
+ spec/not_a_text_file.jpg
13
19
  spec/pdf/storycards/story_capturing_mediator_spec.rb
20
+ spec/pdf/storycards/story_parser_spec.rb
14
21
  spec/pdf/storycards/story_spec.rb
22
+ spec/pdf/storycards/text_file_filter_spec.rb
15
23
  spec/pdf/storycards/writer_spec.rb
16
24
  spec/rspec_suite.rb
17
25
  spec/spec_helper.rb
26
+ spec/subtraction_with_metadata
data/README.txt CHANGED
@@ -1,24 +1,29 @@
1
1
  PDF StoryCards
2
- by Luke Melia
3
- http://www.lukemelia.com/
2
+ RDoc <http://pdf-storycards.rubyforge.org/>
3
+ Tracker <http://rubyforge.org/projects/pdf-storycards/>
4
+ by Luke Melia <http://www.lukemelia.com/>
4
5
 
5
6
  == DESCRIPTION:
6
7
 
7
- Provides a script and library to parses stories saved in the RSpec
8
- plain text story format and saves a PDF file with printable 3"x5"
9
- index cards suitable for using in agile planning and prioritization.
8
+ Provides a script and library to parse stories saved in the RSpec
9
+ plain text story format and creates a PDF file with printable 3"x5"
10
+ index cards suitable for using in Agile planning and prioritization.
10
11
 
11
12
  == FEATURES/PROBLEMS:
12
13
 
13
14
  * Create a PDF with each page as a 3x5 sheet, or as 4 cards per 8.5 x 11 sheet
14
- * Currently reads stories from a single file.
15
- * TODO: Take a directory and find all stories in it
16
- * TODO: Take stories via STDIN
15
+ * Included script reads stories from STDIN and writes PDF to STDOUT
17
16
  * TODO: Improve test coverage
17
+ * TODO: Improve documentation
18
18
 
19
19
  == SYNOPSIS:
20
20
 
21
- StorycardPdfWriter.make_pdf("/tmp/stories.txt", "/tmp/storycards.pdf", :style => :card_1up)
21
+ From the command line with
22
+ stories2cards < /path/to/stories.txt
23
+
24
+ Or via Ruby
25
+ story_text = File.read('my_story')
26
+ pdf_content = PDF::Storycards::Writer.make_pdf(story_text, :style => :card_1up)
22
27
 
23
28
  == REQUIREMENTS:
24
29
 
@@ -27,13 +32,13 @@ index cards suitable for using in agile planning and prioritization.
27
32
 
28
33
  == INSTALL:
29
34
 
30
- sudo gem install storycards
35
+ sudo gem install pdf-storycards
31
36
 
32
37
  == LICENSE:
33
38
 
34
39
  (The MIT License)
35
40
 
36
- Copyright (c) 2007 Luke Melia
41
+ Copyright (c) 2007-2008 Luke Melia
37
42
 
38
43
  Permission is hereby granted, free of charge, to any person obtaining
39
44
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -13,11 +13,12 @@ Hoe.new('pdf-storycards', PDF::Storycards::VERSION) do |p|
13
13
  p.author = 'Luke Melia'
14
14
  p.email = 'luke@lukemelia.com'
15
15
  p.summary = 'Utilities for generating printable story cards for agile planning and measurement'
16
- p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
17
- # p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
16
+ p.description = p.paragraphs_of('README.txt', 1..5).join("\n\n")
17
+ p.url = 'http://rubyforge.org/projects/pdf-storycards/'
18
18
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
19
19
  p.extra_deps << ['pdf-writer', '>= 1.1.7']
20
20
  p.extra_deps << ['rspec', '>= 1.1.1']
21
+ p.remote_rdoc_dir = '' # Release to root
21
22
  end
22
23
 
23
24
  # vim: syntax=Ruby
@@ -9,31 +9,34 @@
9
9
  # This command outputs a PDF consisting of one card for each story and
10
10
  # narrative defined in stories.txt and saves it as stories.pdf
11
11
  #
12
- # stories2cards stories.txt
12
+ # stories2cards < stories.txt
13
13
  #
14
14
  # Other examples
15
15
  #
16
- # stories2cards stories.txt -o /tmp/cards.pdf
17
- # stories2cards stories.txt --style=4up
16
+ # stories2cards -o /tmp/cards.pdf < stories.txt
17
+ # stories2cards --style=4up < stories.txt
18
18
  #
19
19
  # == Usage
20
- # stories2cards [options] text_file_with_stories
20
+ # stories2cards [options] < text_file_with_stories
21
21
  #
22
22
  # For help use stories2cards -h
23
23
  #
24
24
  # == Options
25
25
  # -h, --help Displays help message
26
26
  # -v, --version Display the version, then exit
27
- # -o, --output=PATH Specify the path to the PDF file
28
- # to be created, defaults to storycards.pdf
27
+ # -o, --output=PATH (optional) Specify the path to the PDF file
28
+ # to be created, writes to stdout by default
29
29
  # -s, --style=1up|4up Defaults to 1up
30
- # -b, --[no-]borders Print borders (only has effect with 4up style
30
+ # -l, --lowerleft=key (optional) MetaData key for value to be printed in lower left
31
+ # -r, --lowerright=key (optional) MetaData key for value to be printed in lower right
32
+ # -b, --[no-]borders Print borders (only has effect with 4up style)
33
+ # -m, --makesentences, Make english narratives into sentences
31
34
  #
32
35
  # == Author
33
36
  # Luke Melia
34
37
  #
35
38
  # == Copyright
36
- # Copyright (c) 2007 Luke Melia. Licensed under the MIT License
39
+ # Copyright (c) 2007-2008 Luke Melia. Licensed under the MIT License
37
40
  # http://www.opensource.org/licenses/mit-license.php
38
41
 
39
42
  require 'optparse'
@@ -56,20 +59,21 @@ class App
56
59
 
57
60
  attr_reader :options
58
61
 
59
- def initialize(arguments, stdin)
62
+ def initialize(arguments, stdin, stdout)
60
63
  @arguments = arguments
61
64
  @stdin = stdin
65
+ @stdout = stdout
62
66
 
63
67
  # Set defaults
64
68
  @options = OpenStruct.new
65
- @options.output = "storycards.pdf"
66
69
  @options.style = :"1up"
67
70
  end
68
71
 
69
72
  # Parse options, check arguments, then process the command
70
73
  def run
71
74
  if parsed_options? && arguments_valid?
72
- process_arguments
75
+ process_arguments
76
+ @story_text = @stdin.read
73
77
  process_command
74
78
  else
75
79
  output_usage
@@ -86,7 +90,10 @@ class App
86
90
  opts.on('-h', '--help') { output_help }
87
91
  opts.on('-o', '--output [PATH]') { |path| @options.output = path }
88
92
  opts.on('-s', '--style [STYLE]', [:"1up", :"4up"]) { |style| @options.style = style }
89
- opts.on("-b", "--[no-]borders", "Print borders") { |b| options.borders = b }
93
+ opts.on('-l', '--lowerleft [METADATA_KEY]') { |metadata_key| @options.lower_left = metadata_key }
94
+ opts.on('-r', '--lowerright [METADATA_KEY]') { |metadata_key| @options.lower_right = metadata_key }
95
+ opts.on("-b", "--[no-]borders", "Print borders") { |b| @options.borders = b }
96
+ opts.on('-m', '--makesentences', "Make english narratives into sentences") { |m| @options.make_sentences = m }
90
97
  opts.parse!(@arguments) rescue return false
91
98
 
92
99
  true
@@ -94,14 +101,12 @@ class App
94
101
 
95
102
  # True if required arguments were provided
96
103
  def arguments_valid?
97
- return false unless @arguments.length == 1
98
- return false unless File.exists?(@arguments[0])
104
+ return false if @arguments.length > 0
99
105
  return true
100
106
  end
101
107
 
102
108
  # Setup the arguments
103
109
  def process_arguments
104
- @source_file = @arguments[0]
105
110
  end
106
111
 
107
112
  def output_help
@@ -118,12 +123,18 @@ class App
118
123
  end
119
124
 
120
125
  def process_command
121
- style = :card_1up if @options.style == :"1up"
122
- style = :letter_4up if @options.style == :"4up"
123
- PDF::Storycards::Writer.make_pdf(@source_file, @options.output, :style => style)
126
+ @options.style = :card_1up if @options.style == :"1up"
127
+ @options.style = :letter_4up if @options.style == :"4up"
128
+ options_as_hash = @options.instance_variable_get('@table')
129
+ pdf_text = PDF::Storycards::Writer.make_pdf(@story_text, options_as_hash)
130
+ if @options.output
131
+ File.open(@options.output, 'w') {|f| f.write(pdf_text) }
132
+ else
133
+ @stdout.print pdf_text
134
+ end
124
135
  end
125
- end
136
+ end
126
137
 
127
138
  # Create and run the application
128
- app = App.new(ARGV, STDIN)
139
+ app = App.new(ARGV, $stdin, $stdout)
129
140
  app.run
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'active_record'
3
+ require 'pdf/storycards'
4
+
5
+ ActiveRecord::Base.establish_connection(
6
+ :adapter => "mysql",
7
+ :host => "localhost",
8
+ :username => "root",
9
+ :password => "",
10
+ :database => "mingle"
11
+ )
12
+
13
+ target_sprint_number = 4
14
+
15
+ class WeplayLaunchCard < ActiveRecord::Base
16
+ end
17
+
18
+ cards = WeplayLaunchCard.find(:all, :conditions => ["card_type_name = 'story' AND cp_target_sprint_number = ?", target_sprint_number], :order => 'cp_storyrank')
19
+ stories = cards.collect do |card|
20
+ story = "CardNumber: #{card.number}\n"
21
+ story << "Estimate: #{card.cp_estimated_effort}\n\n" unless card.cp_estimated_effort.nil?
22
+ story << "Story: #{card.name}\n\n"
23
+ story << " #{card.description.gsub("\n","\n ")}\n\n" unless card.description.nil?
24
+ story
25
+ end
26
+ story_text = stories.join(".\n")
27
+
28
+ pdf_text = PDF::Storycards::Writer.make_pdf(story_text, :style => :letter_4up, :borders => true, :lower_left => 'Estimate', :lower_right => 'CardNumber')
29
+ $stdout.print pdf_text
@@ -1,10 +1,12 @@
1
1
  module PDF
2
2
  module Storycards
3
- VERSION = '0.0.1'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
6
6
 
7
+ require 'pdf/storycards/text_file_filter'
7
8
  require 'pdf/storycards/scenario'
8
9
  require 'pdf/storycards/story'
9
10
  require 'pdf/storycards/story_capturing_mediator'
11
+ require 'pdf/storycards/story_parser'
10
12
  require 'pdf/storycards/writer'
@@ -1,7 +1,10 @@
1
1
  module PDF
2
2
  module Storycards
3
+ # The string Story#narrative is imbued with a method called to_sentence, which formats the
4
+ # story narrative as a single sentence without newlines.
3
5
  class Story
4
6
  def initialize(title, narrative)
7
+ @meta_data = {}
5
8
  @title = title
6
9
  @narrative = narrative
7
10
  def @narrative.to_sentence
@@ -10,7 +13,8 @@ module PDF
10
13
  @scenarios = []
11
14
  end
12
15
 
13
- attr_accessor :title, :narrative
16
+ attr_reader :title, :narrative
17
+ attr_accessor :meta_data
14
18
 
15
19
  def add_scenario(scenario)
16
20
  @scenarios << scenario
@@ -1,8 +1,5 @@
1
1
  # RSpec's story parser needs to be constructed with a Mediator. The mediator defined in the class
2
2
  # below simply collects stories and exposes the collection as StoryCapturingMediator#stories.
3
- #
4
- # It also sprinkles in a magic method on Story#narrative called to_sentence, which formats the
5
- # story narrative as a single sentence without newlines.
6
3
  module PDF
7
4
  module Storycards
8
5
 
@@ -0,0 +1,41 @@
1
+ module PDF
2
+ module Storycards
3
+ class StoryParser
4
+
5
+ def parse_stories(story_text)
6
+ all_stories = []
7
+ story_groups = story_text.split(/^\.$/)
8
+ story_groups.each do |story_group|
9
+ all_stories << parse_story_group(story_group)
10
+ end
11
+ return all_stories.flatten
12
+ end
13
+
14
+ def parse_story_group(story_text)
15
+ meta_data = extract_meta_data(story_text)
16
+ story_mediator = StoryCapturingMediator.new
17
+ rspec_story_parser = Spec::Story::Runner::StoryParser.new(story_mediator)
18
+ rspec_story_parser.parse(story_text.split("\n"))
19
+ stories = story_mediator.stories
20
+ stories.each do |story|
21
+ story.meta_data = meta_data
22
+ end
23
+ stories
24
+ end
25
+
26
+ def extract_meta_data(story_text)
27
+ meta_data = {}
28
+ story_text.split("\n").each do |line|
29
+ match_data = /^([^ :]+):(.*)$/.match(line)
30
+ unless match_data.nil?
31
+ name = match_data[1].strip
32
+ value = match_data[2].strip
33
+ break if name == 'Story'
34
+ meta_data[name] = value
35
+ end
36
+ end
37
+ meta_data
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ module PDF
2
+ module Storycards
3
+ class TextFileFilter
4
+ include Enumerable
5
+
6
+ def initialize(*paths)
7
+ @paths = paths
8
+ @text_files = paths.select{|path| nonbinary?(path)}
9
+ @text_files.map!{ |path| Pathname.new(path).cleanpath.to_s }
10
+ end
11
+
12
+ def each(&block)
13
+ @text_files.each(&block)
14
+ end
15
+
16
+ private
17
+
18
+ NON_ASCII_PRINTABLE = /[^\x20-\x7e\s]/
19
+
20
+ def nonbinary?(path, size = 1024)
21
+ open(path) do |io|
22
+ while buf = io.read(size)
23
+ return false if NON_ASCII_PRINTABLE =~ buf
24
+ end
25
+ return true
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -5,12 +5,14 @@ module PDF
5
5
  module Storycards
6
6
  class Writer
7
7
 
8
- def self.make_pdf( path, output_path = 'storycards.pdf', opts = {} )
9
- output_path = 'storycards.pdf' if output_path.nil?
8
+ CARD_WIDTH = PDF::Writer.in2pts(5)
9
+ CARD_HEIGHT = PDF::Writer.in2pts(3)
10
+
11
+ def self.make_pdf( story_text, opts )
10
12
  opts[:style] = :card_1up unless opts[:style]
11
- opts[:border] = false unless opts[:border]
13
+ opts[:borders] = false unless opts[:borders]
12
14
 
13
- stories = self.parse_stories(path)
15
+ stories = StoryParser.new.parse_stories(story_text)
14
16
 
15
17
  if opts[:style] == :card_1up
16
18
  pdf = PDF::Writer.new(:paper => [7.62, 12.7], :orientation => :landscape) #3x5 card
@@ -18,18 +20,16 @@ module PDF
18
20
  pdf.move_pointer(-60)
19
21
 
20
22
  stories.each_with_index do |story, index|
21
- pdf.y = 0 if index > 0
22
- print_card_text(pdf, story)
23
+ pdf.start_new_page if index > 0
24
+ print_card_corners(pdf, story, opts[:lower_left], opts[:lower_right], 0, 0)
25
+ print_card_text(pdf, story, 0, opts[:make_sentences])
23
26
  end
24
27
 
25
- pdf.save_as(output_path)
26
- return output_path
28
+ return pdf.render
27
29
 
28
30
  elsif opts[:style] == :letter_4up
29
31
 
30
32
  pdf = PDF::Writer.new(:paper => "LETTER", :orientation => :landscape)
31
- card_width = PDF::Writer.in2pts(5)
32
- card_height = PDF::Writer.in2pts(3)
33
33
  margin = PDF::Writer.in2pts(0.33)
34
34
  gutter = margin
35
35
  padding = 10
@@ -37,54 +37,64 @@ module PDF
37
37
  pdf.start_columns(2, gutter)
38
38
  stories.each_with_index do |story, index|
39
39
 
40
- is_laying_out_left_hand_column = (index < 2)
40
+ is_laying_out_left_hand_column = (index % 4 < 2)
41
41
  if is_laying_out_left_hand_column
42
42
  rect_x = margin
43
43
  else
44
- rect_x = pdf.page_width - margin - card_width
44
+ rect_x = pdf.page_width - margin - CARD_WIDTH
45
45
  end
46
46
 
47
47
  is_laying_out_top_row = (index % 2 == 0)
48
48
  if is_laying_out_top_row
49
49
  pdf.start_new_page unless index == 0
50
50
  pdf.y = pdf.page_height - margin - padding
51
- rect_y = pdf.page_height - margin - card_height
51
+ rect_y = pdf.page_height - margin - CARD_HEIGHT
52
52
  else
53
53
  rect_y = margin
54
- pdf.y = margin + card_height - padding
54
+ pdf.y = margin + CARD_HEIGHT - padding
55
55
  end
56
56
 
57
- print_border(pdf, rect_x, rect_y, card_width, card_height) if opts[:border]
58
- print_card_text(pdf, story, padding)
57
+ print_border(pdf, rect_x, rect_y) if opts[:borders]
58
+ print_card_corners(pdf, story, opts[:lower_left], opts[:lower_right], rect_x, rect_y)
59
+ print_card_text(pdf, story, padding, opts[:make_sentences])
59
60
  end
60
61
 
61
- pdf.save_as(output_path)
62
- return output_path
62
+ return pdf.render
63
63
 
64
+ else
65
+ raise "Unknown style: #{opts[:style]}"
64
66
  end
65
67
  end
66
68
  private
67
69
 
68
- def self.print_border(pdf, x, y, width, height)
70
+ def self.print_border(pdf, x, y)
69
71
  pdf.stroke_color(Color::RGB::Black)
70
72
  pdf.stroke_style(PDF::Writer::StrokeStyle.new(1))
71
- pdf.rectangle(rect_x, rect_y, card_width, card_height).close_stroke
72
- end
73
-
74
- def self.parse_stories(path)
75
- story_mediator = StoryCapturingMediator.new
76
- story_parser = Spec::Story::Runner::StoryParser.new(story_mediator)
77
- story_text = File.read(path)
78
- story_parser.parse(story_text.split("\n"))
79
- return story_mediator.stories
73
+ pdf.rectangle(x, y, CARD_WIDTH, CARD_HEIGHT).close_stroke
80
74
  end
81
-
82
- def self.print_card_text(pdf, story, padding = 0)
75
+
76
+ def self.print_card_text(pdf, story, padding = 0, make_sentences = false)
77
+ top = pdf.y
83
78
  pdf.text "<b>#{story.title}</b>", :font_size => 18, :justification => :center,
84
79
  :left => padding, :right => padding
85
80
  pdf.move_pointer(12)
86
- pdf.text story.narrative.to_sentence, :justification => :left, :font_size => 14,
87
- :left => padding, :right => padding
81
+ narrative = story.narrative
82
+ narrative = narrative.to_sentence if make_sentences
83
+ pdf.text narrative, :justification => :left, :font_size => 14,
84
+ :left => padding, :right => padding
85
+ end
86
+
87
+ def self.print_card_corners(pdf, story, lower_left_meta_data_key, lower_right_meta_data_key, card_x, card_y)
88
+ text_y = card_y + 16 + 8
89
+ text_width = CARD_WIDTH/2 - PDF::Writer.in2pts(0.4)
90
+ if story.meta_data[lower_left_meta_data_key]
91
+ text_x = card_x + PDF::Writer.in2pts(0.4)
92
+ pdf.add_text_wrap text_x, text_y, text_width, story.meta_data[lower_left_meta_data_key], 16, :left
93
+ end
94
+ if story.meta_data[lower_right_meta_data_key]
95
+ text_x = card_x + CARD_WIDTH/2
96
+ pdf.add_text_wrap text_x, text_y, text_width, story.meta_data[lower_right_meta_data_key], 16, :right
97
+ end
88
98
  end
89
99
  end
90
100
  end
@@ -0,0 +1,56 @@
1
+ This is a story about a calculator. The text up here above the Story: declaration
2
+ won't be processed, so you can write whatever you wish!
3
+
4
+ CardNumber: 12
5
+ Estimate: Very Easy
6
+
7
+ Story: simple multiplication
8
+
9
+ As an accountant
10
+ I want to multiply numbers
11
+ So that I can count beans
12
+
13
+ Scenario: multiply one and one
14
+ Given a value of 1
15
+ And a multiplier of 1
16
+
17
+ When the multiplier is applied to the value
18
+
19
+ Then the product should be 1
20
+ And the corks should be popped
21
+
22
+ .
23
+ CardNumber: 15
24
+ Estimate: Very Hard
25
+
26
+ Story: simple subtraction
27
+
28
+ As an accountant
29
+ I want to subtract numbers
30
+ So that I can count beans
31
+
32
+ Scenario: subtract one minus one
33
+ Given an argument of 1
34
+ And an argument of 1
35
+
36
+ When the second argument is subtracted from the first argument
37
+
38
+ Then the different should be 0
39
+ And the corks should be popped
40
+
41
+ Scenario: subtract two from five
42
+ Given an argument of 5
43
+ And an argument of 2
44
+
45
+ When the second argument is subtracted from the first argument
46
+
47
+ Then the difference should be 3
48
+ Then it should snow
49
+
50
+ Scenario: subtract two more
51
+ GivenScenario subtract two from five
52
+ And an argument of 2
53
+
54
+ When the result is reduced by the argument
55
+
56
+ Then the result should be 1
Binary file
Binary file
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ module PDF
4
+ class Writer
5
+ def save_as(path)
6
+ #no op
7
+ end
8
+ end
9
+ end
10
+
11
+ describe PDF::Storycards::Writer do
12
+
13
+ before(:all) do
14
+ @single_story_path = File.dirname(__FILE__) + '/../../addition'
15
+ @single_story_with_meta_data_path = File.dirname(__FILE__) + '/../../subtraction_with_metadata'
16
+ @multiple_stories_with_meta_data_path = File.dirname(__FILE__) + '/../../calculator_stories_with_metadata'
17
+ end
18
+
19
+ it "should parse an example plain text story file and create a new story" do
20
+ story_text = File.read(@single_story_path)
21
+ stories = PDF::Storycards::StoryParser.new.parse_stories(story_text)
22
+ stories.first.title.should == 'simple addition'
23
+ stories.first.narrative.should == "As an accountant\nI want to add numbers\nSo that I can count beans"
24
+ end
25
+
26
+ it "should parse extract metadata from plain text story file and set it on the story" do
27
+ story_text = File.read(@single_story_with_meta_data_path)
28
+ stories = PDF::Storycards::StoryParser.new.parse_stories(story_text)
29
+ stories.first.meta_data['CardNumber'].should == '152'
30
+ stories.first.meta_data['Estimate'].should == 'Medium'
31
+ end
32
+
33
+ it "should treat a single . on a line as a separator between story groups" do
34
+ story_text = File.read(@multiple_stories_with_meta_data_path)
35
+ stories = PDF::Storycards::StoryParser.new.parse_stories(story_text)
36
+ stories.first.title.should == 'simple multiplication'
37
+ stories.first.meta_data['CardNumber'].should == '12'
38
+ stories.first.meta_data['Estimate'].should == 'Very Easy'
39
+ stories.last.title.should == 'simple subtraction'
40
+ stories.last.meta_data['CardNumber'].should == '15'
41
+ stories.last.meta_data['Estimate'].should == 'Very Hard'
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+ require 'pathname'
3
+
4
+ describe PDF::Storycards::TextFileFilter do
5
+ before(:all) do
6
+ @text_file_path_1 = File.dirname(__FILE__) + '/../../addition'
7
+ @text_file_clean_path_1 = Pathname.new(@text_file_path_1).cleanpath.to_s
8
+ @text_file_path_2 = File.dirname(__FILE__) + '/../../calculator_stories'
9
+ @non_text_file_path_1 = File.dirname(__FILE__) + '/../../not_a_text_file.jpg'
10
+ @non_text_file_clean_path_1 = Pathname.new(@non_text_file_path_1).cleanpath.to_s
11
+ @non_text_file_path_2 = File.dirname(__FILE__) + '/../../not_a_text_file.gif'
12
+ @directory = File.dirname(__FILE__) + '/../../../'
13
+ end
14
+
15
+ it "should expose an empty collection when initialized with nothing" do
16
+ filter = PDF::Storycards::TextFileFilter.new()
17
+ filter.map.should == []
18
+ end
19
+
20
+ it "should expose a collection of the clean path to one text file when initialized with only that text file" do
21
+ filter = PDF::Storycards::TextFileFilter.new(@text_file_path_1)
22
+ filter.map.should == [@text_file_clean_path_1]
23
+ end
24
+
25
+ it "should expose an empty collection when initialized with a single non-text file" do
26
+ filter = PDF::Storycards::TextFileFilter.new(@non_text_file_path_1)
27
+ filter.map.should == []
28
+ end
29
+
30
+ end
@@ -11,28 +11,22 @@ end
11
11
  describe PDF::Storycards::Writer do
12
12
 
13
13
  before(:all) do
14
- @single_story_path = File.dirname(__FILE__) + '/../../addition'
15
- @multiple_stories_path = File.dirname(__FILE__) + '/../../calculator_stories'
16
- end
17
-
18
- it "should parse an example plain text story file and create a new story" do
19
- stories = PDF::Storycards::Writer.send(:parse_stories, @single_story_path)
20
- stories.first.title.should == 'simple addition'
21
- stories.first.narrative.should == "As an accountant\nI want to add numbers\nSo that I can count beans"
14
+ multiple_stories_path = File.dirname(__FILE__) + '/../../calculator_stories'
15
+ @story_text = File.read(multiple_stories_path)
22
16
  end
23
17
 
24
18
  it "should not print border when printing 1-up" do
25
19
  PDF::Storycards::Writer.should_not_receive(:print_border)
26
- PDF::Storycards::Writer.make_pdf(@multiple_stories_path, 'storycards.pdf', :style => :card_1up)
20
+ PDF::Storycards::Writer.make_pdf(@story_text, :output => 'storycards.pdf', :style => :card_1up)
27
21
  end
28
22
 
29
23
  it "should not print border when printing 4-up with default options" do
30
24
  PDF::Storycards::Writer.should_not_receive(:print_border)
31
- PDF::Storycards::Writer.make_pdf(@multiple_stories_path, 'storycards.pdf', :style => :letter_4up)
25
+ PDF::Storycards::Writer.make_pdf(@story_text, :output => 'storycards.pdf', :style => :letter_4up)
32
26
  end
33
27
 
34
28
  it "should print border when printing 4-up with border option set to true" do
35
29
  PDF::Storycards::Writer.should_receive(:print_border).twice
36
- PDF::Storycards::Writer.make_pdf(@multiple_stories_path, 'storycards.pdf', :style => :letter_4up, :border => true)
30
+ PDF::Storycards::Writer.make_pdf(@story_text, :output => 'storycards.pdf', :style => :letter_4up, :borders => true)
37
31
  end
38
32
  end
@@ -1,3 +1,4 @@
1
+ require 'rubygems'
1
2
  require 'stringio'
2
3
  require 'spec'
3
4
  require 'spec/mocks'
@@ -0,0 +1,40 @@
1
+ This is a story about a calculator. The text up here above the Story: declaration
2
+ won't be processed, so you can write whatever you wish!
3
+
4
+ There is some metadata below. It's also above the Story: declaration, so it won't be
5
+ processed by storyrunner, but pdf-storycards can work with it.
6
+
7
+ CardNumber: 152
8
+ Estimate: Medium
9
+
10
+ Story: simple subtraction
11
+
12
+ As an accountant
13
+ I want to subtract numbers
14
+ So that I can count beans
15
+
16
+ Scenario: subtract one minus one
17
+ Given an argument of 1
18
+ And an argument of 1
19
+
20
+ When the second argument is subtracted from the first argument
21
+
22
+ Then the different should be 0
23
+ And the corks should be popped
24
+
25
+ Scenario: subtract two from five
26
+ Given an argument of 5
27
+ And an argument of 2
28
+
29
+ When the second argument is subtracted from the first argument
30
+
31
+ Then the difference should be 3
32
+ Then it should snow
33
+
34
+ Scenario: subtract two more
35
+ GivenScenario subtract two from five
36
+ And an argument of 2
37
+
38
+ When the result is reduced by the argument
39
+
40
+ Then the result should be 1
metadata CHANGED
@@ -1,21 +1,80 @@
1
1
  --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
2
4
  name: pdf-storycards
3
5
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
6
+ version: 0.1.0
7
+ date: 2008-02-10 00:00:00 -05:00
8
+ summary: Utilities for generating printable story cards for agile planning and measurement
9
+ require_paths:
10
+ - lib
11
+ email: luke@lukemelia.com
12
+ homepage: http://rubyforge.org/projects/pdf-storycards/
13
+ rubyforge_project: pdf-storycards
14
+ description: "== DESCRIPTION: Provides a script and library to parse stories saved in the RSpec plain text story format and creates a PDF file with printable 3\"x5\" index cards suitable for using in Agile planning and prioritization. == FEATURES/PROBLEMS: * Create a PDF with each page as a 3x5 sheet, or as 4 cards per 8.5 x 11 sheet * Included script reads stories from STDIN and writes PDF to STDOUT * TODO: Improve test coverage * TODO: Improve documentation == SYNOPSIS: From the command line with stories2cards < /path/to/stories.txt Or via Ruby story_text = File.read('my_story') pdf_content = PDF::Storycards::Writer.make_pdf(story_text, :style => :card_1up) == REQUIREMENTS:"
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
5
25
  platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
6
29
  authors:
7
30
  - Luke Melia
8
- autorequire:
9
- bindir: bin
10
- cert_chain: []
31
+ files:
32
+ - History.txt
33
+ - Manifest.txt
34
+ - README.txt
35
+ - Rakefile
36
+ - bin/stories2cards
37
+ - examples/mingle_cards.rb
38
+ - lib/pdf/storycards.rb
39
+ - lib/pdf/storycards/scenario.rb
40
+ - lib/pdf/storycards/story.rb
41
+ - lib/pdf/storycards/story_capturing_mediator.rb
42
+ - lib/pdf/storycards/story_parser.rb
43
+ - lib/pdf/storycards/text_file_filter.rb
44
+ - lib/pdf/storycards/writer.rb
45
+ - spec/addition
46
+ - spec/calculator_stories
47
+ - spec/calculator_stories_with_metadata
48
+ - spec/not_a_text_file.gif
49
+ - spec/not_a_text_file.jpg
50
+ - spec/pdf/storycards/story_capturing_mediator_spec.rb
51
+ - spec/pdf/storycards/story_parser_spec.rb
52
+ - spec/pdf/storycards/story_spec.rb
53
+ - spec/pdf/storycards/text_file_filter_spec.rb
54
+ - spec/pdf/storycards/writer_spec.rb
55
+ - spec/rspec_suite.rb
56
+ - spec/spec_helper.rb
57
+ - spec/subtraction_with_metadata
58
+ test_files: []
59
+
60
+ rdoc_options:
61
+ - --main
62
+ - README.txt
63
+ extra_rdoc_files:
64
+ - History.txt
65
+ - Manifest.txt
66
+ - README.txt
67
+ executables:
68
+ - stories2cards
69
+ extensions: []
70
+
71
+ requirements: []
11
72
 
12
- date: 2007-12-29 00:00:00 -05:00
13
- default_executable:
14
73
  dependencies:
15
74
  - !ruby/object:Gem::Dependency
16
75
  name: pdf-writer
17
76
  version_requirement:
18
- version_requirements: !ruby/object:Gem::Requirement
77
+ version_requirements: !ruby/object:Gem::Version::Requirement
19
78
  requirements:
20
79
  - - ">="
21
80
  - !ruby/object:Gem::Version
@@ -24,7 +83,7 @@ dependencies:
24
83
  - !ruby/object:Gem::Dependency
25
84
  name: rspec
26
85
  version_requirement:
27
- version_requirements: !ruby/object:Gem::Requirement
86
+ version_requirements: !ruby/object:Gem::Version::Requirement
28
87
  requirements:
29
88
  - - ">="
30
89
  - !ruby/object:Gem::Version
@@ -33,66 +92,9 @@ dependencies:
33
92
  - !ruby/object:Gem::Dependency
34
93
  name: hoe
35
94
  version_requirement:
36
- version_requirements: !ruby/object:Gem::Requirement
95
+ version_requirements: !ruby/object:Gem::Version::Requirement
37
96
  requirements:
38
97
  - - ">="
39
98
  - !ruby/object:Gem::Version
40
- version: 1.4.0
99
+ version: 1.5.0
41
100
  version:
42
- description: "== FEATURES/PROBLEMS: * Create a PDF with each page as a 3x5 sheet, or as 4 cards per 8.5 x 11 sheet * Currently reads stories from a single file. * TODO: Take a directory and find all stories in it * TODO: Take stories via STDIN * TODO: Improve test coverage == SYNOPSIS: StorycardPdfWriter.make_pdf(\"/tmp/stories.txt\", \"/tmp/storycards.pdf\", :style => :card_1up) == REQUIREMENTS:"
43
- email: luke@lukemelia.com
44
- executables:
45
- - stories2cards
46
- extensions: []
47
-
48
- extra_rdoc_files:
49
- - History.txt
50
- - Manifest.txt
51
- - README.txt
52
- files:
53
- - History.txt
54
- - Manifest.txt
55
- - README.txt
56
- - Rakefile
57
- - bin/stories2cards
58
- - lib/pdf/storycards.rb
59
- - lib/pdf/storycards/scenario.rb
60
- - lib/pdf/storycards/story.rb
61
- - lib/pdf/storycards/story_capturing_mediator.rb
62
- - lib/pdf/storycards/writer.rb
63
- - spec/addition
64
- - spec/calculator_stories
65
- - spec/pdf/storycards/story_capturing_mediator_spec.rb
66
- - spec/pdf/storycards/story_spec.rb
67
- - spec/pdf/storycards/writer_spec.rb
68
- - spec/rspec_suite.rb
69
- - spec/spec_helper.rb
70
- has_rdoc: true
71
- homepage: http://www.zenspider.com/ZSS/Products/pdf-storycards/
72
- post_install_message:
73
- rdoc_options:
74
- - --main
75
- - README.txt
76
- require_paths:
77
- - lib
78
- required_ruby_version: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: "0"
83
- version:
84
- required_rubygems_version: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- version: "0"
89
- version:
90
- requirements: []
91
-
92
- rubyforge_project: pdf-storycards
93
- rubygems_version: 1.0.1
94
- signing_key:
95
- specification_version: 2
96
- summary: Utilities for generating printable story cards for agile planning and measurement
97
- test_files: []
98
-