trelloscrum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b7400785d9440390067e1d35fd8d33cbb6ca7930
4
+ data.tar.gz: 7e4ca5f8d0974e16adbbcae7962f24581a031f24
5
+ SHA512:
6
+ metadata.gz: 3fc10493800ca0dac2f36719fb2f849b147a227dbcaa73f6f5cf53c3b4f181573fe267d06180f0b3201029f15fcd2b8b3d98265b9a7a64c7ffaca92bb39de02d
7
+ data.tar.gz: 3398005874c404863abc6ec9fe4d9e1592421b536edc012e9f7b28c437511ce452d14b5e4e020b572fda1753895b87a83a13bad766ccb361fb75be7acc67bc40
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "pry"
data/bin/trelloscrum ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require_relative '../lib/cli'
5
+
6
+ TrelloScrum::Cli.start()
data/lib/cli.rb ADDED
@@ -0,0 +1,130 @@
1
+ require 'thor'
2
+
3
+ require 'json'
4
+ require 'chronic'
5
+ require 'trello'
6
+
7
+ require_relative 'pdf'
8
+
9
+ module TrelloScrum
10
+ class Cli < Thor
11
+ class_option :"config", :default => "./config.json", :type => :string, :desc => "Path to config, default is local directory/config.json"
12
+ class_option :"verbose", :aliases => ["v"], :default => false, :type => :boolean, :desc => "Verbose output"
13
+
14
+
15
+ desc "pdf OUTFILE", "generate PDF for cards"
16
+ method_option :"only-estimated", :default => true, :type => :boolean, :desc => "Wether or not to output only estimates"
17
+ method_option :"list", :type => :string, :desc => "Listname to use"
18
+ method_option :"board", :type => :string, :desc => "Board id to use"
19
+ method_option :"filter-title", :type => :string, :desc => "Regexp to filter on titles, only show's cards matching title"
20
+
21
+ def pdf(outfile)
22
+ setup_trello
23
+ cards = get_cards
24
+ generate_pdf(cards, outfile)
25
+ end
26
+
27
+ desc "setup DEVELOPER_PUBLIC_KEY MEMBER_TOKEN [BOARD_ID]", "config trello"
28
+ long_desc <<-EOT
29
+ Generate the appropriate keys for Trello:
30
+
31
+ 1. Get the developer public key
32
+ \x5 1. Log in to Trello
33
+ \x5 2. Go to https://trello.com/1/appKey/generate
34
+ \x5 3. Save the developer public key
35
+
36
+ 2. Get the member token
37
+ \x5 1. Go to https://trello.com/1/connect?key=DEVELOPER_PUBLIC_KEY&name=TrelloScrumCard&response_type=token
38
+ Replace DEVELOPER_PUBLIC_KEY with the previously generated key
39
+ \x5 2. Click "Allow"
40
+ \x5 3. Save the member token
41
+
42
+ EOT
43
+ def setup(developer_public_key, member_token, board_id=nil)
44
+ self.config["developer_public_key"] = developer_public_key
45
+ self.config["member_token"] = member_token
46
+ self.config["board_id"] = board_id if board_id
47
+
48
+ File.open(options.config, "w") do |f|
49
+ f.write JSON.pretty_generate(self.config)
50
+ end
51
+
52
+ puts "New config written to #{options.config}"
53
+ end
54
+
55
+ protected
56
+
57
+ def log(msg)
58
+ puts msg if options.verbose
59
+ end
60
+
61
+ def setup_trello
62
+ if !config["developer_public_key"] || config["developer_public_key"].empty?
63
+ puts "Please make sure you have configured a developer public key (run setup help for more info)"
64
+ exit(1)
65
+ end
66
+
67
+ if !config["member_token"] || config["member_token"].empty?
68
+ puts "Please make sure you have configured a member token (run setup help for more info)"
69
+ exit(1)
70
+ end
71
+
72
+ Trello.configure do |c|
73
+ c.developer_public_key = config["developer_public_key"]
74
+ c.member_token = config["member_token"]
75
+ end
76
+ end
77
+
78
+ def config
79
+ if File.exist?(options.config)
80
+ @config ||= JSON.parse(File.read(options.config));
81
+ else
82
+ @config ||= {}
83
+ end
84
+ end
85
+
86
+ def get_cards
87
+ list_name = options.list || config["list_name"]
88
+
89
+ if !list_name || list_name.empty?
90
+ puts "Please enter a lisname or configurate one (use --list)"
91
+ exit(1)
92
+ end
93
+
94
+ board_id = options.board || config["board_id"]
95
+
96
+ if !board_id || board_id.empty?
97
+ puts "Please enter a board_id or configurate one (use --board)"
98
+ exit(1)
99
+ end
100
+
101
+ log "Getting cards from list #{list_name} of board #{board_id}"
102
+
103
+ board = Trello::Board.find(board_id)
104
+
105
+ list = board.lists.find{|l| l.name == list_name }
106
+
107
+ log "Found list: #{list ? "yes" : "no"}"
108
+
109
+ cards = list.cards.sort!{|a, b| a.pos <=> b.pos }
110
+
111
+ log "List contains #{cards.size} cards"
112
+
113
+ cards.find_all do |card|
114
+ keep = true
115
+ keep = false if options[:"only-estimated"] && !(card.name =~ /^\(\d+/)
116
+ keep = false if options[:"filter-title"] && !(card.name =~ Regexp.new(options[:"filter-title"]))
117
+ keep
118
+ end
119
+ end
120
+
121
+ def generate_pdf(cards, output_path)
122
+ pdf = Pdf.new
123
+ pdf.render_cards(cards)
124
+ pdf.save(output_path)
125
+ end
126
+
127
+
128
+
129
+ end
130
+ end
data/lib/pdf.rb ADDED
@@ -0,0 +1,203 @@
1
+ require 'prawn'
2
+ require 'prawn/table'
3
+
4
+ module TrelloScrum
5
+ class Pdf
6
+
7
+ attr_reader :doc, :options
8
+
9
+ def initialize(page_size = 'A4', options = {})
10
+
11
+ @options = {
12
+ base_font_size: 20
13
+ }.update(options)
14
+
15
+ @doc = Prawn::Document.new :page_size => page_size, :page_layout => :landscape
16
+
17
+ self.doc.font_families.update("FontAwesome" => {:normal => "#{File.dirname(__FILE__)}/../resources/fontawesome-webfont.ttf"})
18
+ self.doc.font "Helvetica", :size => self.options[:base_font_size]
19
+
20
+
21
+ end
22
+
23
+ def render_cards(cards)
24
+ cards.each_with_index do |card, i|
25
+ render_card(card)
26
+
27
+ # Start next card on new page
28
+ self.doc.start_new_page unless i == cards.size - 1
29
+ end
30
+ end
31
+
32
+ def save(filename)
33
+ self.doc.render_file filename
34
+ end
35
+
36
+ protected
37
+
38
+ def render_card(card)
39
+ points,client,title = parse_card_title(card.name)
40
+
41
+ puts "- #{points} :: #{client} :: #{title}"
42
+
43
+ box_width = 100
44
+
45
+ # Output storypoints
46
+ points_box_size = render_points_box(points)
47
+
48
+ self.doc.bounding_box([0, self.doc.cursor], :width => self.doc.bounds.width - points_box_size) do
49
+ self.doc.text(client, {
50
+ :size => self.options[:base_font_size],
51
+ :overflow => :expand
52
+ })
53
+
54
+ self.doc.text(title, {
55
+ :size => self.options[:base_font_size] * 2.25,
56
+ :style => :bold,
57
+ :overflow => :expand
58
+ })
59
+ end
60
+
61
+ # Output due date
62
+ render_due_date(card.due)
63
+
64
+ self.doc.move_down 10
65
+
66
+ if card.checklists.any?
67
+ # Take half the size of the remaining card for description, leave the rest fot checklists
68
+ # Yes, taking doc.cursor is weird, but keep in mind the coordinate system's origin is bottom left
69
+ desc_height = doc.cursor / 2
70
+ else
71
+ desc_height = doc.cursor
72
+ end
73
+
74
+ # Manually creating box here so we can get heigh later.
75
+ desc_box = Prawn::Text::Box.new(card.desc,
76
+ at: [0, self.doc.cursor],
77
+ width: self.doc.bounds.width,
78
+ height: desc_height,
79
+ overflow: :shrink_to_fit,
80
+ document: self.doc)
81
+
82
+
83
+ desc_box.render()
84
+
85
+ # Textbox doesn't move the cursor so we'll do it manually
86
+ self.doc.move_down desc_box.height
87
+
88
+ if card.checklists.any?
89
+ self.doc.move_down 20
90
+
91
+ card.checklists.each do |checklist|
92
+ render_checklist(checklist)
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ def render_checklist(checklist)
99
+ data = checklist.items.map do |item|
100
+ [
101
+ (item.state != "complete" ? "\uF096" : "\uF046"),
102
+ item.name
103
+ ]
104
+ end
105
+
106
+ if data.any?
107
+ self.doc.text(checklist.name, {
108
+ :style => :bold
109
+ })
110
+
111
+ table_height = self.doc.cursor
112
+
113
+ table = nil
114
+ real_table_height = self.doc.bounds.height + 1 # Should always be bigger than table_height
115
+ table_font_size = self.options[:base_font_size]
116
+ while(real_table_height > table_height && table_font_size > 8) do
117
+ table = Prawn::Table.new(data, self.doc) do |tbl|
118
+ tbl.width = self.doc.bounds.width
119
+ tbl.cells.size = table_font_size
120
+ tbl.cells.borders = []
121
+ if table_font_size < 15
122
+ tbl.cells.padding = [0,0,2,0]
123
+ tbl.column(0).padding = [1.5,0,2,0]
124
+ else
125
+ tbl.cells.padding = [0,0,5,0]
126
+ end
127
+ tbl.column(0).font = "FontAwesome"
128
+ tbl.column(0).width = table_font_size + 2
129
+ end
130
+ real_table_height = table.height
131
+ table_font_size -= 1
132
+ end
133
+
134
+ table.draw
135
+ end
136
+ end
137
+
138
+ def render_due_date(date)
139
+ return unless date
140
+
141
+ date_text = date.strftime("%-d %^B")
142
+
143
+ due_box_text = {
144
+ text: date_text,
145
+ color: "FF0000",
146
+ styles: [:bold],
147
+ style: :bold, # Hack to make it work for width measuring too...
148
+ size: self.options[:base_font_size] * 3
149
+ }
150
+
151
+ due_box_width = self.doc.width_of(due_box_text[:text], due_box_text)
152
+ due_box_height = self.doc.height_of_formatted([due_box_text], {})
153
+
154
+ due_box = Prawn::Text::Formatted::Box.new([due_box_text], {
155
+ at: [self.doc.bounds.right - due_box_width, self.doc.bounds.absolute_bottom],
156
+ width: due_box_width,
157
+ height: due_box_height,
158
+ disable_wrap_by_char: true,
159
+ align: :right,
160
+ overflow: :shrink_to_fit,
161
+ document: doc
162
+ })
163
+
164
+ due_box.render()
165
+ end
166
+
167
+ def render_points_box(points)
168
+ return 0 if points.to_s.empty?
169
+
170
+ points_box_size = 100
171
+ points_box_padding = 10
172
+
173
+ points_box = Prawn::Text::Box.new(points.to_s,
174
+ {
175
+ at: [self.doc.bounds.absolute_right - points_box_size + points_box_padding, self.doc.bounds.absolute_top - points_box_padding],
176
+ align: :center,
177
+ valign: :center,
178
+ disable_wrap_by_char: true,
179
+ width: points_box_size - 2 * points_box_padding,
180
+ height: points_box_size - 2 * points_box_padding,
181
+ size: 60,
182
+ style: :bold,
183
+ overflow: :shrink_to_fit,
184
+ document: self.doc
185
+ }
186
+ )
187
+
188
+ points_box.render
189
+
190
+ self.doc.stroke do
191
+ self.doc.rectangle [self.doc.bounds.absolute_right - points_box_size, self.doc.bounds.absolute_top], points_box_size, points_box_size
192
+ end
193
+
194
+ return points_box_size
195
+ end
196
+
197
+ def parse_card_title(title)
198
+ match = title.match(/^\s*(\((\d+)\))?\s*(\[(.*?)\])?\s*(.*)/)
199
+ [match[2], match[4], match[5]]
200
+ end
201
+
202
+ end
203
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module TrelloScrum
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/readme.md ADDED
@@ -0,0 +1,59 @@
1
+ # Trello SCRUM
2
+
3
+ Generates PDF's with (+/-) one page per card with title, body and checklists. Print 4 of them on an A4 for the best action.
4
+
5
+ ## Getting started
6
+
7
+ 1. Install the trelloscrum gem: `gem install trelloscrum`
8
+ 1. Create a `config.json` in the current directory by running `trelloscrum setup`. For more information on how to set up everything run `trelloscrum help setup`
9
+ 1. Run
10
+
11
+ ## Card format
12
+
13
+ To get a nice output for cards (see below) write card titles in the following format (all fields are optional).
14
+
15
+ `(STORYPOINTS) [CLIENT] Rest of the title`
16
+
17
+ Other supported (native) Trello fields are:
18
+
19
+ * The card body of course (markdown is not supported)
20
+ * Card checklists (works best with just one list)
21
+ * Due date
22
+
23
+ This is an example output for a card generated by TrelloScrum
24
+
25
+ ![Example card](https://github.com/flurin/trelloscrum/raw/master/doc/card_example.png)
26
+
27
+ ## Commandline options
28
+
29
+ ```
30
+ Commands:
31
+ trelloscrum help [COMMAND] # Describe available commands or one specific command
32
+ trelloscrum pdf OUTFILE # generate PDF for cards
33
+ trelloscrum setup DEVELOPER_PUBLIC_KEY MEMBER_TOKEN [BOARD_ID] # config trello
34
+
35
+ Options:
36
+ [--config=CONFIG] # Path to config, default is local directory/config.json
37
+ # Default: ./config.json
38
+ v, [--verbose], [--no-verbose] # Verbose output
39
+ ```
40
+
41
+ For more options run `trelloscrum help [COMMAND]`
42
+
43
+ ## Config.json
44
+
45
+ ```
46
+ {
47
+ "developer_public_key" : "",
48
+ "member_token" : "",
49
+ "list_name" : "",
50
+ "board_id" : ""
51
+ }
52
+ ```
53
+
54
+ ## License
55
+
56
+ - All code and documentation is licesned under the MIT license:
57
+ - http://opensource.org/licenses/mit-license.html
58
+ - The Font Awesome font is licensed under the SIL OFL 1.1:
59
+ - http://scripts.sil.org/OFL
Binary file
@@ -0,0 +1,34 @@
1
+ basedir = File.expand_path(File.dirname(__FILE__))
2
+ require "#{basedir}/lib/version"
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "trelloscrum"
6
+ spec.version = TrelloScrum::VERSION
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "Creates product backlog cards from trello cards"
9
+ spec.files = Dir.glob("{bin,lib,resources}/**/**/*") +
10
+ ["trelloscrum.gemspec", "Gemfile", "readme.md"]
11
+ spec.require_path = "lib"
12
+
13
+ spec.executables = ["trelloscrum"]
14
+
15
+ spec.required_ruby_version = '>= 1.9.3'
16
+ spec.required_rubygems_version = ">= 1.3.6"
17
+
18
+ spec.authors = ["Flurin Egger"]
19
+ spec.email = ["flurin@digitpaint.nl"]
20
+ spec.licenses = ['MIT', 'SIL OFL']
21
+
22
+
23
+ spec.add_dependency "json", "~> 1.8.1"
24
+ spec.add_dependency "prawn", "~> 2.0.1"
25
+ spec.add_dependency "prawn-table", "~> 0.2.1"
26
+ spec.add_dependency "chronic", "~> 0.10.2"
27
+ spec.add_dependency "ruby-trello", "~> 1.1.2"
28
+ spec.add_dependency "thor", "~> 0.19.1"
29
+
30
+ spec.homepage = "https://github.com/flurin/trelloscrum"
31
+ spec.description = <<END_DESC
32
+ Generates PDF's with (+/-) one page per card with title, body and checklists. Print 4 of them on an A4 for the best action.
33
+ END_DESC
34
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trelloscrum
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Flurin Egger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.8.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: prawn
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: prawn-table
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.2.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: chronic
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: ruby-trello
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 1.1.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 1.1.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: thor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 0.19.1
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 0.19.1
97
+ description: |2
98
+ Generates PDF's with (+/-) one page per card with title, body and checklists. Print 4 of them on an A4 for the best action.
99
+ email:
100
+ - flurin@digitpaint.nl
101
+ executables:
102
+ - trelloscrum
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - Gemfile
107
+ - bin/trelloscrum
108
+ - lib/cli.rb
109
+ - lib/pdf.rb
110
+ - lib/version.rb
111
+ - readme.md
112
+ - resources/fontawesome-webfont.ttf
113
+ - trelloscrum.gemspec
114
+ homepage: https://github.com/flurin/trelloscrum
115
+ licenses:
116
+ - MIT
117
+ - SIL OFL
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - '>='
126
+ - !ruby/object:Gem::Version
127
+ version: 1.9.3
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - '>='
131
+ - !ruby/object:Gem::Version
132
+ version: 1.3.6
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.2.2
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Creates product backlog cards from trello cards
139
+ test_files: []