cards 0.6

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