cards 0.6

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/CHANGES.txt ADDED
@@ -0,0 +1,8 @@
1
+ = Change Log
2
+
3
+ == Version 0.6
4
+
5
+ * moved main code to win/ subdirectory and moved mac branch to be main branch
6
+ * total refactoring of the mac branch to support configurable dsls
7
+ * releasing as a gem
8
+ * changing name to cards
data/Manifest.txt ADDED
@@ -0,0 +1,29 @@
1
+ CHANGES.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/cards.rb
6
+ lib/cards/builder.rb
7
+ lib/cards/card.rb
8
+ lib/cards/card_wall.rb
9
+ lib/cards/csv_builder.rb
10
+ lib/cards/csv_parser.rb
11
+ lib/cards/extensions.rb
12
+ lib/cards/layouts/column_layout.rb
13
+ lib/cards/layouts/nil_layout.rb
14
+ lib/cards/layouts/row_layout.rb
15
+ lib/cards/master_story_list.rb
16
+ lib/cards/swim_lanes.rb
17
+ lib/cards/text.rb
18
+ lib/cards/tracker_csv.rb
19
+ lib/cards/writers/dot_writer.rb
20
+ lib/cards/writers/graffle_writer.rb
21
+ lib/cards/writers/text_writer.rb
22
+ spec/cards/card_spec.rb
23
+ spec/cards/card_wall_spec.rb
24
+ spec/cards/csv_parser_spec.rb
25
+ spec/cards/extensions_spec.rb
26
+ spec/cards/swim_lanes_spec.rb
27
+ spec/cards/writers/dot_writer_spec.rb
28
+ spec/cards/writers/text_writer_spec.rb
29
+ spec/spec_helper.rb
data/README.txt ADDED
@@ -0,0 +1,44 @@
1
+ = Project: CardWallGen
2
+
3
+ == Description
4
+
5
+ CardWallGen is a program that reads a requirements map and generates a view
6
+ of it. Typically it reads it from excel or csv and generates a graphical
7
+ view in visio or omnigraffle, though it may also generate another csv view of it.
8
+
9
+ == Usage
10
+
11
+ We try to make it simple to configure. However, you still need a driver file
12
+ that might look something like :
13
+
14
+ CardWall.from csv_file("Business Goals") do
15
+ column :goal
16
+ end
17
+
18
+ CardWall.from csv_file("Current Workflow") do
19
+ row :activity
20
+ column :task
21
+ end
22
+
23
+ CardWall.from csv_file("Stories") do
24
+ row :activity
25
+ row :task
26
+ column :story, :wrap_at => 4 do |card, row|
27
+ card.name << "\n\n#{row[:description]}" unless row[:description].blank?
28
+ card.name << "\n\n "
29
+ end
30
+ end
31
+
32
+ MasterStoryList.from csv_file("Stories")
33
+
34
+ TrackerCsv.from csv_file("Stories")
35
+
36
+ == Contact
37
+
38
+ Author:: Jeremy Stell-Smith
39
+ Email:: jeremystellsmith@gmail.com
40
+ License:: LGPL License
41
+
42
+ == Home Page
43
+
44
+ http://rubyforge.org/projects/cardwallgen
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ ENV["FILE"] ||= "/Users/jeremy/Desktop/PunchList Master Story List/Card Wall-Card Wall.csv"
2
+
3
+ require 'rubygems'
4
+ gem "rspec"
5
+ gem "hoe"
6
+
7
+ require 'rake/clean'
8
+ require 'spec/rake/spectask'
9
+ require 'hoe'
10
+
11
+ require 'lib/cards'
12
+
13
+ desc "Default Task"
14
+ task :default => [:spec]
15
+
16
+ task :test => :spec
17
+
18
+ Spec::Rake::SpecTask.new(:spec) do |t|
19
+ t.spec_files = FileList['spec/**/*_spec.rb']
20
+ end
21
+
22
+ desc "export to dot file"
23
+ task :run_dot do
24
+ file = ENV["FILE"] || SAMPLE_FILE
25
+ file.as(:png).delete_if_exists
26
+ file.as(:dot).delete_if_exists
27
+
28
+ CardWall.convert(file, :dot, file.as(:dot))
29
+
30
+ "dot -Tpng #{file.as(:dot).no_spaces} > #{file.as(:png).no_spaces}".run
31
+ "open #{file.as(:png).no_spaces}".run if file.as(:png).exists?
32
+ end
33
+
34
+ desc "export to text"
35
+ task :run_text do
36
+ CardWall.convert(ENV["FILE"] || SAMPLE_FILE, :text)
37
+ end
38
+
39
+ desc "export to graffle"
40
+ task :run do
41
+ file = ENV["FILE"] || SAMPLE_FILE
42
+ file.as(:graffle).delete_if_exists
43
+
44
+ CardWall.convert(file, :graffle, file.as(:graffle))
45
+ end
46
+
47
+ Hoe.new('cards', Cards::VERSION) do |p|
48
+ p.rubyforge_name = 'cardwallgen'
49
+ p.summary = p.description = p.paragraphs_of('README.txt', 2).first
50
+ p.url = p.paragraphs_of('README.txt', -1).first.strip
51
+ p.author = 'Jeremy Stell-Smith'
52
+ p.email = 'jeremystellsmith@gmail.com'
53
+ p.changes = p.paragraphs_of('CHANGES.txt', 0..2).join("\n\n")
54
+ end
data/lib/cards.rb ADDED
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'cards/extensions'
4
+ require 'cards/builder'
5
+ require 'cards/csv_parser'
6
+
7
+ require 'cards/card_wall'
8
+ # writers
9
+ require 'cards/writers/dot_writer'
10
+ require 'cards/writers/graffle_writer'
11
+
12
+ require 'cards/master_story_list'
13
+ require 'cards/tracker_csv'
14
+
15
+ module Cards
16
+ VERSION = "0.6"
17
+ end
@@ -0,0 +1,9 @@
1
+ require 'cards/card'
2
+ require 'cards/layouts/row_layout'
3
+ require 'cards/layouts/column_layout'
4
+ require 'cards/layouts/nil_layout'
5
+
6
+ module Cards
7
+ class Builder
8
+ end
9
+ end
data/lib/cards/card.rb ADDED
@@ -0,0 +1,44 @@
1
+ module Cards
2
+ class Card
3
+ attr_accessor :name, :color, :children, :x, :y
4
+ attr_writer :layout
5
+
6
+ def initialize(name)
7
+ @name = name
8
+ @x, @y = 0, 0
9
+ @children = []
10
+ end
11
+
12
+ def add(*cards)
13
+ @children += cards
14
+ end
15
+
16
+ def visit(&block)
17
+ yield self
18
+ @children.each {|c| c.visit(&block) }
19
+ end
20
+
21
+ def layout
22
+ my_layout.layout(self)
23
+ end
24
+
25
+ def width
26
+ return 1 if children.empty?
27
+ my_layout.width(self)
28
+ end
29
+
30
+ def height
31
+ return 1 if children.empty?
32
+ my_layout.height(self)
33
+ end
34
+
35
+ def cell() [x, y]; end
36
+ def cell=(cells) self.x, self.y = *cells; end
37
+
38
+ private
39
+
40
+ def my_layout
41
+ @layout || NilLayout.new
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,95 @@
1
+ require 'cards/builder'
2
+
3
+ module Cards
4
+ class CardWall < Builder
5
+ def define(&block)
6
+ DefinitionContext.new(self).instance_eval(&block)
7
+ self
8
+ end
9
+
10
+ def self.writer=(writer_class)
11
+ @writer_class = writer_class
12
+ end
13
+
14
+ def self.colors
15
+ @colors ||= {
16
+ :role => :blue,
17
+ :activity => :blue,
18
+ :task => :red,
19
+ :story => :yellow
20
+ }
21
+ end
22
+
23
+ def self.from(parser, &block)
24
+ builder = self.new.define(&block)
25
+
26
+ parser.each_row do |row|
27
+ builder.process(row)
28
+ end
29
+
30
+ builder.show(@writer_class.new(parser.default_output_file))
31
+ end
32
+
33
+ def initialize
34
+ @handlers = []
35
+ @layouts = []
36
+ @root = Card.new("root")
37
+ @root.layout = RowLayout.new
38
+ @root.y = -1
39
+ @last_cards = [@root]
40
+ end
41
+
42
+ def process(row)
43
+ @handlers.each do |handler|
44
+ unless row[handler].blank?
45
+ send("add_#{handler}", row[handler], row)
46
+ end
47
+ end
48
+ end
49
+
50
+ def add_handler(type, layout, &block)
51
+ i = @handlers.size
52
+ @handlers << type
53
+ @root.layout = layout if @layouts.empty?
54
+ @layouts << layout
55
+ (class << self; self; end).module_eval do
56
+ define_method("add_#{type}") do |name, row|
57
+ card = yield(name, row)
58
+ card.layout = @layouts[i+1]
59
+ card.color ||= CardWall.colors[type] || :white
60
+ @last_cards[i].add(card)
61
+ @last_cards[i+1] = card
62
+ end
63
+ end
64
+ end
65
+
66
+ def show(output)
67
+ @root.layout
68
+ @root.visit {|card| output.create_card(card.name, card.color, card.cell) unless card == @root}
69
+ end
70
+
71
+ class DefinitionContext
72
+ def initialize(wraps)
73
+ @this = wraps
74
+ @i = 0
75
+ end
76
+
77
+ def row(name, *options)
78
+ @this.add_handler(name, RowLayout.new(*options)) do |name, row|
79
+ Card.new(name)
80
+ end
81
+ end
82
+
83
+ def column(name, *options)
84
+ @this.add_handler(name, ColumnLayout.new(*options)) do |name, row|
85
+ c = Card.new(name)
86
+ yield c, row if block_given?
87
+ c
88
+ end
89
+ end
90
+
91
+ def lanes(name, options)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,46 @@
1
+ class CsvBuilder
2
+ def initialize(file_name)
3
+ @file_name = file_name
4
+ @file = File.open(file_name, "wb")
5
+ @csv = CSV::Writer.create(@file)
6
+ @csv << header
7
+ end
8
+
9
+ def header
10
+ raise "implement me"
11
+ end
12
+
13
+ def self.from(parser)
14
+ builder = self.new(parser.default_output_file + ".#{self.to_s.gsub("Cards::", "").downcase}.csv")
15
+
16
+ parser.each_row do |row|
17
+ builder.process(row)
18
+ end
19
+
20
+ builder.done
21
+ end
22
+
23
+ def process(row)
24
+ [:activity, :task, :story].each do |handler|
25
+ send("add_#{handler}", row[handler], row) if row[handler]
26
+ end
27
+ end
28
+
29
+ def add_activity(name, params = {})
30
+ @activity = name
31
+ end
32
+
33
+ def add_task(name, params = {})
34
+ @task = name
35
+ end
36
+
37
+ def add_story(name, params = {})
38
+ raise "implement me"
39
+ end
40
+
41
+ def done
42
+ @csv.close
43
+ @file.close
44
+ `open #{@file_name.gsub(' ', '\\ ')}`
45
+ end
46
+ end
@@ -0,0 +1,60 @@
1
+ require 'csv'
2
+
3
+ module Cards
4
+ class CsvParser
5
+ def initialize(file)
6
+ @file = file
7
+ end
8
+
9
+ def rows
10
+ rows = []
11
+ each_row {|row| rows << row}
12
+ rows
13
+ end
14
+
15
+ def each_row
16
+ header = nil
17
+ CSV.open(@file, 'r') do |row|
18
+ if header.nil?
19
+ header = define_header(row)
20
+ else
21
+ yield Row.new(header, row)
22
+ end
23
+ end
24
+ end
25
+
26
+ def default_output_file
27
+ File.basename(@file).sub(/\..*/, '')
28
+ end
29
+
30
+ private
31
+
32
+ def define_header(header_row)
33
+ header = {}
34
+ header_row.each_with_index do |name, i|
35
+ header[name.downcase.gsub(' ', '_').to_sym] = i
36
+ end
37
+ return header
38
+ end
39
+
40
+ class Row
41
+ def initialize(header, row)
42
+ @header, @row = header, row
43
+ end
44
+
45
+ def [](key)
46
+ index = @header[key] || raise("can't find column header #{key.inspect} in #{@header.inspect}")
47
+ @row[index]
48
+ end
49
+
50
+ def to_h
51
+ h = {}
52
+ @header.each_key do |column_header|
53
+ val = @row[@header[column_header]]
54
+ h[column_header] = val unless val.blank?
55
+ end
56
+ h
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ class String
2
+ def blank?
3
+ self.size == 0 || self.strip.size == 0
4
+ end
5
+
6
+ def no_spaces
7
+ self.gsub(' ', '\\ ')
8
+ end
9
+
10
+ def as(extension)
11
+ self.gsub(/\.[^\/]+$/, '') + ".#{extension}"
12
+ end
13
+
14
+ def exists?
15
+ File.exist?(self)
16
+ end
17
+
18
+ def delete_if_exists
19
+ File.delete(self) if self.exists?
20
+ end
21
+
22
+ def run
23
+ puts self
24
+ `#{self}`
25
+ end
26
+ end
27
+
28
+ class Array
29
+ def sum
30
+ s = 0
31
+ each {|i| s += i}
32
+ s
33
+ end
34
+
35
+ def widths
36
+ map {|c| c.width}
37
+ end
38
+
39
+ def heights
40
+ map {|c| c.height}
41
+ end
42
+ end
43
+
44
+ class Numeric
45
+ def inches # as points
46
+ self * 72.0
47
+ end
48
+ end
49
+
50
+ class Object
51
+ def blank?
52
+ nil?
53
+ end
54
+
55
+ def dump_methods(ignore = Object)
56
+ ignore = ignore.instance_methods if ignore.is_a? Class
57
+ p (methods - ignore).sort
58
+ end
59
+ end
@@ -0,0 +1,34 @@
1
+ require 'enumerator'
2
+
3
+ module Cards
4
+ class ColumnLayout
5
+ def initialize(options = {})
6
+ @wrap_at = options.delete(:wrap_at) || 10
7
+ end
8
+
9
+ def layout(card)
10
+ x = card.x
11
+ card.children.each_slice(@wrap_at) do |slice|
12
+ y = card.y + 1
13
+ slice.each do |child|
14
+ child.x, child.y = x, y
15
+ child.layout
16
+ y += child.height
17
+ end
18
+ x += slice.widths.max
19
+ end
20
+ end
21
+
22
+ def width(card)
23
+ width = 0
24
+ card.children.each_slice(@wrap_at) do |slice|
25
+ width += slice.widths.max
26
+ end
27
+ width
28
+ end
29
+
30
+ def height(card)
31
+ card.children.heights.sum
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ module Cards
2
+ class NilLayout
3
+ def layout(card)
4
+ end
5
+
6
+ def width(card)
7
+ 1
8
+ end
9
+
10
+ def height(card)
11
+ 1
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ module Cards
2
+ class RowLayout
3
+ def layout(card)
4
+ x = card.x
5
+ card.children.each_with_index do |child, i|
6
+ child.x = x
7
+ child.y = card.y + 1
8
+ child.layout
9
+ x += child.width
10
+ end
11
+ end
12
+
13
+ def width(card)
14
+ card.children.map {|c| c.width}.sum
15
+ end
16
+
17
+ def height(card)
18
+ card.children.map {|c| c.height}.max
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ require 'cards/csv_builder'
2
+
3
+ module Cards
4
+ # this is an example of a custom csv builder that you can create
5
+ class MasterStoryList < CsvBuilder
6
+ def header
7
+ ["Activity", "Task", "Story", "Description", "Estimate", "Phase", "Priority"]
8
+ end
9
+
10
+ def add_story(name, params = {})
11
+ return unless [nil, '', 'Story', 'Chore'].include?(params[:story_type])
12
+
13
+ @csv << [
14
+ @activity,
15
+ @task,
16
+ name,
17
+ params[:description],
18
+ nil, # params[:estimate],
19
+ nil, # params[:phase],
20
+ nil # params[:priority]
21
+ ]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ require 'cards/card_wall'
2
+
3
+ module Cards
4
+ class SwimLanes < CardWall
5
+ def initialize
6
+ super
7
+ end
8
+ end
9
+ end
data/lib/cards/text.rb ADDED
@@ -0,0 +1,23 @@
1
+ class Text
2
+ def add_role(name, params = {})
3
+ puts name
4
+ end
5
+
6
+ def add_activity(name, params = {})
7
+ puts " #{name}"
8
+ end
9
+
10
+ def add_task(name, params = {})
11
+ puts " #{name}"
12
+ end
13
+
14
+ def add_story(name, params = {})
15
+ string = " #{name}"
16
+ string << " - #{params[:description]}" unless params[:description].blank?
17
+ string << " - #{params[:estimate]}" unless params[:estimate].blank?
18
+ puts string
19
+ end
20
+
21
+ def done
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ require 'cards/csv_builder'
2
+
3
+ class TrackerCsv < CsvBuilder
4
+ def header
5
+ ["Story", "Labels", "Story Type", "Estimate", "Description"]
6
+ end
7
+
8
+ def add_story(name, params = {})
9
+ return unless [nil, '', 'Story', 'Chore'].include?(params[:story_type])
10
+
11
+ @csv << [
12
+ name,
13
+ @task,
14
+ params[:story_type],
15
+ nil, # params[:estimate],
16
+ "#{@activity}\n#{params[:description]}"
17
+ ]
18
+ end
19
+ end
@@ -0,0 +1,85 @@
1
+ module Cards
2
+ class DotWriter
3
+ def initialize(file)
4
+ @file = file
5
+ @node_color = {:green => "darkseagreen1", :blue => "lightblue", :red => "pink", :yellow => "lightyellow"}
6
+ @nodes = []
7
+
8
+ puts "digraph G {"
9
+ puts "graph [nodesep=.1, ranksep=.1, ordering=out];"
10
+ puts "node [shape=box, color=black, style=filled, width=2, height=1.2, fixedsize=true, labeljust=\"l\"];"
11
+ puts "edge [style=invis, weight=1];"
12
+ end
13
+
14
+ def puts(string)
15
+ @file.puts string
16
+ end
17
+
18
+ def create_card(name, color, position)
19
+ create_node("card", position, %{label="#{wrap(name)}",fillcolor=#{@node_color[color]}})
20
+ end
21
+
22
+ def done
23
+ create_gap_nodes
24
+ create_ranks
25
+ create_edges
26
+ puts "}"
27
+ end
28
+
29
+ private
30
+
31
+ def create_node(name, position, properties)
32
+ node_id = name.gsub(/\s*/, "")[0, 10] << "_" << position.join("_")
33
+ x = position[0]; y = position[1]
34
+ column = @nodes[x] == nil ? @nodes[x] = [] : @nodes[x]
35
+ column[y] = node_id
36
+ puts %{#{node_id} [#{properties}];}
37
+ end
38
+
39
+ def create_gap_nodes
40
+ @nodes.each_index do |x|
41
+ if !@nodes[x]
42
+ create_gap([x,0])
43
+ else
44
+ @nodes[x].each_index do |y|
45
+ create_gap([x,y]) unless @nodes[x][y]
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def create_gap(position)
52
+ create_node("gap", position, "style=invis")
53
+ end
54
+
55
+ def create_ranks
56
+ 3.times do |y|
57
+ rank = "{rank=same"
58
+ @nodes.each_index { |x| rank += "; #{@nodes[x][y]}" if @nodes[x][y] }
59
+ rank << "};"
60
+ puts rank
61
+ end
62
+ end
63
+
64
+ def create_edges
65
+ (@nodes.length-1).times do |x|
66
+ create_edge(@nodes[x][0], @nodes[x+1][0])
67
+ end
68
+
69
+ @nodes.each do |column|
70
+ (column.length-1).times do |y|
71
+ create_edge(column[y], column[y+1])
72
+ end
73
+ end
74
+ end
75
+
76
+ def create_edge(node1, node2)
77
+ puts %{#{node1} -> #{node2};}
78
+ end
79
+
80
+ def wrap(text)
81
+ label_width = 20
82
+ text.gsub(/\n/," ").scan(/\S.{0,#{label_width-2}}\S(?=\s|$)|\S+/).join("\\n")
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,69 @@
1
+ require 'rubygems'
2
+ gem 'rb-appscript'
3
+ gem 'rubyosa'
4
+ require 'appscript'
5
+ require 'rbosa'
6
+
7
+ module Cards
8
+ class GraffleWriter
9
+ CARD_WALL_STENCIL = File.expand_path(File.dirname(__FILE__) + "/CardWall.gstencil")
10
+ COLORS = {
11
+ :green => [0xcccc, 0xffff, 0xcccc],
12
+ :red => [0xffff, 0xcccc, 0xcccc],
13
+ :yellow => [0xffff, 0xffff, 0xcccc],
14
+ :blue => [0xcccc, 0xcccc, 0xffff],
15
+ :white => [0xffff, 0xffff, 0xffff]
16
+ }
17
+ CARD_WIDTH = 2.875.inches
18
+ CARD_HEIGHT = 2.875.inches
19
+ CARD_PADDING = 0.4.inches
20
+
21
+ attr_reader :app
22
+
23
+ def initialize(name = "Card Wall")
24
+ @app = Appscript.app('OmniGraffle 4')
25
+ @doc = @app.documents[0]
26
+ puts "opening #{name}"
27
+ open_document(name)
28
+ activate
29
+ clear_shapes
30
+ end
31
+
32
+ def open_document(name)
33
+ @doc = @app.documents[name]
34
+ return @doc if @doc.exists
35
+
36
+ @doc = @app.make :new => :document, :with_properties => {:name => name}
37
+ canvas.adjusts_pages.set true
38
+ @app.windows[name].zoom.set 0.2
39
+ end
40
+
41
+ def clear_shapes
42
+ canvas.shapes.get.each do |shape|
43
+ shape.delete
44
+ end
45
+ end
46
+
47
+ def create_card(text, color = :yellow, cell = [0, 0])
48
+ canvas.make :new => :shape,
49
+ :with_properties => {
50
+ :origin => [cell[0] * (CARD_WIDTH + CARD_PADDING),
51
+ cell[1] * (CARD_HEIGHT + CARD_PADDING)],
52
+ :size => [CARD_WIDTH, CARD_HEIGHT],
53
+ :fill_color => COLORS[color],
54
+ :text => {:size => 16.0, :text => text}
55
+ }
56
+ end
57
+
58
+ def done
59
+ end
60
+
61
+ def canvas
62
+ @doc.canvases[0]
63
+ end
64
+
65
+ def method_missing(sym, *args)
66
+ @app.send(sym, *args)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,23 @@
1
+ module Cards
2
+ class TextWriter
3
+ def initialize
4
+ @rows = []
5
+ end
6
+
7
+ def show(card)
8
+ create_card(card.name, card.color, card.cell)
9
+ end
10
+
11
+ def create_card(name, color, cell)
12
+ (@rows[cell[1]] ||= [])[cell[0]] = name
13
+ end
14
+
15
+ def to_s
16
+ @rows.map{|row| row.map{|val| val ? val : " " }.join }.join("\n")
17
+ end
18
+
19
+ def inspect
20
+ @rows.map{|row| "|#{row.join("|")}|" }.join("\n")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,141 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require 'cards/card'
4
+ require 'cards/layouts/row_layout'
5
+ require 'cards/layouts/column_layout'
6
+ include Cards
7
+
8
+ module Cards
9
+ def a() @a ||= new_card("a"); end
10
+ def b() @b ||= new_card("b"); end
11
+ def c() @c ||= new_card("c"); end
12
+ def d() @d ||= new_card("d"); end
13
+ def e() @e ||= new_card("e"); end
14
+ def f() @f ||= new_card("f"); end
15
+ def g() @g ||= new_card("g"); end
16
+ def h() @h ||= new_card("h"); end
17
+ def i() @i ||= new_card("i"); end
18
+ def j() @j ||= new_card("j"); end
19
+ def k() @k ||= new_card("k"); end
20
+ def l() @l ||= new_card("l"); end
21
+ def m() @m ||= new_card("m"); end
22
+
23
+ def new_card(name)
24
+ Card.new(name)
25
+ end
26
+ end
27
+
28
+ describe Card do
29
+ include Cards
30
+
31
+ it "should have x & y" do
32
+ a.x = 1
33
+ a.y = 2
34
+ a.x.should == 1
35
+ a.y.should == 2
36
+ end
37
+ end
38
+
39
+ describe Card, "row layout" do
40
+ include Cards
41
+
42
+ it "should have 1 row" do
43
+ a.add(b, c, d)
44
+
45
+ cards_to_s(a).should ==
46
+ "a
47
+ bcd"
48
+ end
49
+
50
+ it "should show with 2 rows" do
51
+ a.add(b, g)
52
+ b.add(c, f)
53
+ c.add(d, e)
54
+
55
+ cards_to_s(a).should ==
56
+ "a
57
+ b g
58
+ c f
59
+ de"
60
+ end
61
+
62
+ def cards_to_s(card)
63
+ out = TextWriter.new
64
+ card.layout
65
+ card.visit {|c| out.show(c)}
66
+ out.to_s
67
+ end
68
+
69
+ def new_card(name)
70
+ c = Card.new(name)
71
+ c.layout = RowLayout.new
72
+ c
73
+ end
74
+ end
75
+
76
+ describe Card, "column layout" do
77
+ include Cards
78
+
79
+ before do
80
+ a.layout = RowLayout.new
81
+ @layout = ColumnLayout.new
82
+ end
83
+
84
+ it "should have 1 column" do
85
+ a.add(b, c, d)
86
+ d.add(e, f)
87
+ c.add(g, h, i)
88
+
89
+ cards_to_s(a).should ==
90
+ "a
91
+ bcd
92
+ ge
93
+ hf
94
+ i"
95
+ end
96
+
97
+ it "should show with 2 rows and then columns" do
98
+ a.add(b, c, d)
99
+ a.children.each {|child| child.layout = RowLayout.new}
100
+ b.add(e, f, g)
101
+ d.add(h)
102
+ h.add(i, j)
103
+ f.add(k, l, m)
104
+
105
+ cards_to_s(a).should ==
106
+ "a
107
+ b cd
108
+ efg h
109
+ k i
110
+ l j
111
+ m"
112
+ end
113
+
114
+ it "should wrap" do
115
+ @layout = ColumnLayout.new(:wrap_at => 3)
116
+ a.add(b, c, d)
117
+ c.add(e, f, g, h, i, j, k)
118
+ d.add(l, m)
119
+
120
+ cards_to_s(a).should ==
121
+ "a
122
+ bc d
123
+ ehkl
124
+ fi m
125
+ gj"
126
+
127
+ end
128
+
129
+ def cards_to_s(card)
130
+ out = TextWriter.new
131
+ card.layout
132
+ card.visit {|c| out.show(c)}
133
+ out.to_s
134
+ end
135
+
136
+ def new_card(name)
137
+ c = Card.new(name)
138
+ c.layout = @layout
139
+ c
140
+ end
141
+ end
@@ -0,0 +1,76 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require 'cards/card_wall'
4
+ include Cards
5
+
6
+ describe CardWall, "row, col" do
7
+ before do
8
+ @builder = CardWall.new.define do
9
+ row :activity
10
+ column :task
11
+ end
12
+ @out = TextWriter.new
13
+ end
14
+
15
+ it "should display cards" do
16
+ @builder.process :activity => 'a', :task => 1
17
+ @builder.process :task => 2
18
+ @builder.process :task => 3
19
+ @builder.process :activity => 'b', :task => 4
20
+ @builder.process :activity => 'c', :task => 5
21
+ @builder.process :task => 6
22
+ @builder.process :task => 7
23
+ @builder.process :task => 8
24
+ @builder.process :task => 9
25
+ @builder.process :task => 0
26
+ @builder.process :task => 1
27
+ @builder.process :activity => 'd', :task => 2
28
+
29
+ @builder.show(@out)
30
+ @out.to_s.should ==
31
+ "abcd
32
+ 1452
33
+ 2 6
34
+ 3 7
35
+ 8
36
+ 9
37
+ 0
38
+ 1"
39
+ end
40
+ end
41
+
42
+ describe CardWall, "row, row, col" do
43
+ before do
44
+ @builder = CardWall.new.define do
45
+ row :activity
46
+ row :task
47
+ column :story
48
+ end
49
+ @out = TextWriter.new
50
+ end
51
+
52
+ it "should display cards" do
53
+ @builder.process :activity => 'a', :task => 1, :story => 'A'
54
+ @builder.process :task => 2, :story => 'B'
55
+ @builder.process :story => 'C'
56
+ @builder.process :task => 3, :story => 'D'
57
+ @builder.process :activity => 'b', :task => 4
58
+ @builder.process :activity => 'c', :task => 5
59
+ @builder.process :task => 6, :story => 'E'
60
+ @builder.process :story => 'F'
61
+ @builder.process :story => 'G'
62
+ @builder.process :story => 'H'
63
+ @builder.process :task => 7
64
+ @builder.process :task => 8
65
+ @builder.process :activity => 'd', :task => 2, :story => 'I'
66
+
67
+ @builder.show(@out)
68
+ @out.to_s.should ==
69
+ "a bc d
70
+ 123456782
71
+ ABD E I
72
+ C F
73
+ G
74
+ H"
75
+ end
76
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require 'file_sandbox_behavior'
4
+ require 'cards/csv_parser'
5
+ include Cards
6
+
7
+ describe CsvParser do
8
+ include FileSandbox
9
+
10
+ it "should parse csv" do
11
+ sandbox.new :file => 'tmp.csv', :with_contents =>
12
+ "activity,task,story,estimate
13
+ manage email,,
14
+ ,read email,
15
+ ,,display email body
16
+ manage email,think about email,,1
17
+ ,read email,,2
18
+ ,,display email body,3"
19
+
20
+ rows = CsvParser.new(sandbox['tmp.csv'].path).rows
21
+ rows[0].to_h.should == {:activity => 'manage email'}
22
+ rows[1].to_h.should == {:task => 'read email'}
23
+ rows[2].to_h.should == {:story => 'display email body'}
24
+ rows[3].to_h.should == {:activity => 'manage email', :task => 'think about email', :estimate => "1"}
25
+ rows[4].to_h.should == {:task => 'read email', :estimate => "2"}
26
+ rows[5].to_h.should == {:story => 'display email body', :estimate => "3"}
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "String extensions" do
4
+ it "should know what's blank" do
5
+ nil.should be_blank
6
+ "".should be_blank
7
+ " \t\n".should be_blank
8
+ "a".should_not be_blank
9
+ end
10
+ end
11
+
12
+ describe "Array extensions" do
13
+ it "should have max" do
14
+ [1, 2, 5, 4].max.should == 5
15
+ end
16
+
17
+ it "should have min" do
18
+ [1, 2, 5, 4].min.should == 1
19
+ end
20
+
21
+ it "should compute sum" do
22
+ [1, 2, 3].sum.should == 6
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'cards/swim_lanes'
3
+ include Cards
4
+
5
+ describe SwimLanes do
6
+ before do
7
+ @lanes = SwimLanes.new.define do
8
+ row :area
9
+ lanes :pain_points, :by => %w(H M L)
10
+ end
11
+ end
12
+
13
+ it "should show rows w/ no swim lanes" do
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,70 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require 'cards/writers/dot_writer'
3
+ require 'stringio'
4
+ include Cards
5
+
6
+ describe DotWriter do
7
+ CardStruct = Struct.new(:name, :color, :position)
8
+ {
9
+ CardStruct["role", :green, [0,0]] => /card_0_0 \[label="role",fillcolor=darkseagreen1\];/,
10
+ CardStruct["activity", :blue, [0,1]] => /card_0_1 \[label="activity",fillcolor=lightblue\];/,
11
+ CardStruct["task", :red, [0,2]] => /card_0_2 \[label="task",fillcolor=pink\];/,
12
+ CardStruct["feature", :yellow, [0,3]] => /card_0_3 \[label="feature",fillcolor=lightyellow\];/,
13
+ }.each do |card, expected_node|
14
+ it %{given #{card.color} card created with name #{card.name.inspect} in position #{card.position.inspect} it should create node #{expected_node.inspect}} do
15
+ create_card(card.name, card.color, card.position)
16
+ @actual_output.should =~ expected_node
17
+ end
18
+ end
19
+
20
+ {
21
+ [[0,0],[0,2]] => /gap_0_1 \[style=invis\];/,
22
+ [[0,0],[2,0]] => /gap_1_0 \[style=invis\];/,
23
+ [[0,0],[1,1]] => /gap_1_0 \[style=invis\];/,
24
+ }.each do |card_positions, expected_gap_node|
25
+ it %{given cards created in positions #{card_positions.inspect} it should create invisible node #{expected_gap_node.inspect}} do
26
+ create_cards_in_positions(card_positions)
27
+ @actual_output.should =~ expected_gap_node
28
+ end
29
+ end
30
+
31
+ {
32
+ [[0,0],[1,0],[2,0]] => [/card_0_0 -> card_1_0;/,/card_1_0 -> card_2_0;/],
33
+ [[0,0],[0,1],[0,2]] => [/card_0_0 -> card_0_1;/,/card_0_1 -> card_0_2;/],
34
+ }.each do |card_positions, edges|
35
+ it %{given cards created in positions #{card_positions.inspect} it should create edges #{edges.inspect} } do
36
+ create_cards_in_positions(card_positions)
37
+ @actual_output.expect(edges)
38
+ end
39
+ end
40
+
41
+ {
42
+ [[0,0],[1,0]] => [/\{rank=same; card_0_0; card_1_0\};/],
43
+ }.each do |card_positions, ranks|
44
+ it %{given cards created in positions #{card_positions.inspect} it should create ranks #{ranks.inspect} } do
45
+ create_cards_in_positions(card_positions)
46
+ @actual_output.expect(ranks)
47
+ end
48
+ end
49
+
50
+ def build
51
+ @builder = DotWriter.new(StringIO.new(@actual_output = "", "w+"))
52
+ yield
53
+ @builder.done
54
+ end
55
+
56
+ def create_cards_in_positions(positions)
57
+ build { positions.each { |position| @builder.create_card("card", :yellow, position)} }
58
+ end
59
+
60
+ def create_card(name, color, position)
61
+ build { @builder.create_card(name, color, position) }
62
+ end
63
+
64
+ class String
65
+ def expect(lines)
66
+ lines.each { |line| self.should =~ line }
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ include Cards
3
+
4
+ describe TextWriter do
5
+ before do
6
+ @output = TextWriter.new
7
+ end
8
+
9
+ it "should show simple output for to_s" do
10
+ @output.create_card('a', nil, [0, 0])
11
+ @output.create_card('b', nil, [0, 1])
12
+ @output.create_card('c', nil, [0, 2])
13
+ @output.create_card('d', nil, [2, 2])
14
+ @output.create_card('e', nil, [1, 2])
15
+ @output.create_card('f', nil, [3, 0])
16
+
17
+ @output.to_s.should ==
18
+ "a f
19
+ b
20
+ ced"
21
+ @output.inspect.should ==
22
+ "|a|||f|
23
+ |b|
24
+ |c|e|d|"
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+
3
+ require "rubygems"
4
+ gem "rspec"
5
+ gem "mocha"
6
+ gem "file_sandbox"
7
+
8
+ require 'cards/extensions'
9
+ require 'cards/writers/text_writer'
10
+
11
+ Spec::Runner.configure do |config|
12
+ #config.mock_with :mocha
13
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cards
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.6"
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Stell-Smith
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-05 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.5.3
23
+ version:
24
+ description: CardWallGen is a program that reads a requirements map and generates a view of it. Typically it reads it from excel or csv and generates a graphical view in visio or omnigraffle, though it may also generate another csv view of it.
25
+ email: jeremystellsmith@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - CHANGES.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ files:
35
+ - CHANGES.txt
36
+ - Manifest.txt
37
+ - README.txt
38
+ - Rakefile
39
+ - lib/cards.rb
40
+ - lib/cards/builder.rb
41
+ - lib/cards/card.rb
42
+ - lib/cards/card_wall.rb
43
+ - lib/cards/csv_builder.rb
44
+ - lib/cards/csv_parser.rb
45
+ - lib/cards/extensions.rb
46
+ - lib/cards/layouts/column_layout.rb
47
+ - lib/cards/layouts/nil_layout.rb
48
+ - lib/cards/layouts/row_layout.rb
49
+ - lib/cards/master_story_list.rb
50
+ - lib/cards/swim_lanes.rb
51
+ - lib/cards/text.rb
52
+ - lib/cards/tracker_csv.rb
53
+ - lib/cards/writers/dot_writer.rb
54
+ - lib/cards/writers/graffle_writer.rb
55
+ - lib/cards/writers/text_writer.rb
56
+ - spec/cards/card_spec.rb
57
+ - spec/cards/card_wall_spec.rb
58
+ - spec/cards/csv_parser_spec.rb
59
+ - spec/cards/extensions_spec.rb
60
+ - spec/cards/swim_lanes_spec.rb
61
+ - spec/cards/writers/dot_writer_spec.rb
62
+ - spec/cards/writers/text_writer_spec.rb
63
+ - spec/spec_helper.rb
64
+ has_rdoc: true
65
+ homepage: http://rubyforge.org/projects/cardwallgen
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --main
69
+ - README.txt
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project: cardwallgen
87
+ rubygems_version: 1.1.1
88
+ signing_key:
89
+ specification_version: 2
90
+ summary: CardWallGen is a program that reads a requirements map and generates a view of it. Typically it reads it from excel or csv and generates a graphical view in visio or omnigraffle, though it may also generate another csv view of it.
91
+ test_files: []
92
+