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 +8 -0
- data/Manifest.txt +29 -0
- data/README.txt +44 -0
- data/Rakefile +54 -0
- data/lib/cards.rb +17 -0
- data/lib/cards/builder.rb +9 -0
- data/lib/cards/card.rb +44 -0
- data/lib/cards/card_wall.rb +95 -0
- data/lib/cards/csv_builder.rb +46 -0
- data/lib/cards/csv_parser.rb +60 -0
- data/lib/cards/extensions.rb +59 -0
- data/lib/cards/layouts/column_layout.rb +34 -0
- data/lib/cards/layouts/nil_layout.rb +14 -0
- data/lib/cards/layouts/row_layout.rb +21 -0
- data/lib/cards/master_story_list.rb +24 -0
- data/lib/cards/swim_lanes.rb +9 -0
- data/lib/cards/text.rb +23 -0
- data/lib/cards/tracker_csv.rb +19 -0
- data/lib/cards/writers/dot_writer.rb +85 -0
- data/lib/cards/writers/graffle_writer.rb +69 -0
- data/lib/cards/writers/text_writer.rb +23 -0
- data/spec/cards/card_spec.rb +141 -0
- data/spec/cards/card_wall_spec.rb +76 -0
- data/spec/cards/csv_parser_spec.rb +28 -0
- data/spec/cards/extensions_spec.rb +24 -0
- data/spec/cards/swim_lanes_spec.rb +17 -0
- data/spec/cards/writers/dot_writer_spec.rb +70 -0
- data/spec/cards/writers/text_writer_spec.rb +26 -0
- data/spec/spec_helper.rb +13 -0
- metadata +92 -0
data/CHANGES.txt
ADDED
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
|
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,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
|
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
|