jira-cards 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GemFile +14 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +20 -0
- data/README.textile +57 -0
- data/RakeFile +34 -0
- data/VERSION +1 -0
- data/bin/jira-cards +5 -0
- data/lib/application.rb +45 -0
- data/lib/commands.rb +64 -0
- data/lib/configuration.rb +165 -0
- data/lib/default_template.css +51 -0
- data/lib/default_template.erb +44 -0
- data/lib/flags.rb +1 -0
- data/lib/html_renderer.rb +26 -0
- data/lib/issue.rb +36 -0
- data/lib/jira.rb +24 -0
- data/lib/jql_generator.rb +9 -0
- data/lib/jql_generators/issues_jql_generator.rb +18 -0
- data/lib/jql_generators/iteration_jql_generator.rb +5 -0
- data/lib/meta.rb +5 -0
- data/lib/pdf_renderer.rb +21 -0
- data/spec/html_renderer_spec.rb +24 -0
- data/spec/issue_jql_generator_spec.rb +43 -0
- data/spec/mock.rb +9 -0
- data/spec/pdf_renderer_spec.rb +23 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/test.css +4 -0
- data/spec/test.erb +2 -0
- metadata +232 -0
data/GemFile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem "bundler", "~> 1.0.0"
|
5
|
+
gem "jeweler", "~> 1.5.2"
|
6
|
+
gem "rake"
|
7
|
+
gem "rspec"
|
8
|
+
gem "ruby-debug", "< 0.10.4"
|
9
|
+
end
|
10
|
+
|
11
|
+
gem 'soap4r'
|
12
|
+
gem 'jira4r-jh', "> 0.3.0"
|
13
|
+
gem 'pdfkit'
|
14
|
+
gem 'clint'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
clint (0.2.7)
|
5
|
+
columnize (0.3.2)
|
6
|
+
git (1.2.5)
|
7
|
+
httpclient (2.1.6)
|
8
|
+
jeweler (1.5.2)
|
9
|
+
bundler (~> 1.0.0)
|
10
|
+
git (>= 1.2.5)
|
11
|
+
rake
|
12
|
+
jira4r-jh (0.4.0)
|
13
|
+
soap4r
|
14
|
+
soap4r
|
15
|
+
linecache (0.43)
|
16
|
+
pdfkit (0.5.0)
|
17
|
+
rake (0.8.7)
|
18
|
+
rspec (1.3.0)
|
19
|
+
ruby-debug (0.10.3)
|
20
|
+
columnize (>= 0.1)
|
21
|
+
ruby-debug-base (~> 0.10.3.0)
|
22
|
+
ruby-debug-base (0.10.3)
|
23
|
+
linecache (>= 0.3)
|
24
|
+
soap4r (1.5.8)
|
25
|
+
httpclient (>= 2.1.1)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
x86-mingw32
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
bundler (~> 1.0.0)
|
32
|
+
clint
|
33
|
+
jeweler (~> 1.5.2)
|
34
|
+
jira4r-jh (> 0.3.0)
|
35
|
+
pdfkit
|
36
|
+
rake
|
37
|
+
rspec
|
38
|
+
ruby-debug (< 0.10.4)
|
39
|
+
soap4r
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 James Hollingworth
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
h1. jira-cards
|
2
|
+
|
3
|
+
A command line tool for generating scrum cards from jira. It allows you to customise the styling of the scrum cards using an erb template.
|
4
|
+
|
5
|
+
h1. Installation
|
6
|
+
|
7
|
+
You are going to need to
|
8
|
+
|
9
|
+
* install the jira-cards gem <pre>gem install jira-cards</pre>
|
10
|
+
* "Enable the jira RPC-Plugin":http://confluence.atlassian.com/display/JIRA/JIRA+XML-RPC+Overview
|
11
|
+
* Install "wkhtmltopdf":http://code.google.com/p/wkhtmltopdf/
|
12
|
+
|
13
|
+
h1. Configuration
|
14
|
+
|
15
|
+
When you first run the tool it will ask you:
|
16
|
+
|
17
|
+
* The url to your jira instance e.g. jira.atlassian.com
|
18
|
+
* A username & password for accessing jira. Whatever user you use must be an admin
|
19
|
+
* Path to the wkhtmltopdf e.g. C:\\Program Files\\wkhtmltopdf\\wkhtmltopdf.exe
|
20
|
+
* If you want to use the default templates
|
21
|
+
** If no, the path to the template erb & css file
|
22
|
+
|
23
|
+
These values are then written to a config.yml file. If you run the command it will tell you where the config file is if you want to change it.
|
24
|
+
|
25
|
+
|
26
|
+
h1. Commands
|
27
|
+
|
28
|
+
The two commands are issues and iteration. For both commands you need to specify the output flag which is the path where you want the output pdf file to go to.
|
29
|
+
|
30
|
+
h2. Issues
|
31
|
+
|
32
|
+
You want this command if you want to print specific issues. The only flag is --keys which are the key's of the issues you want. e.g.
|
33
|
+
|
34
|
+
<pre>
|
35
|
+
jira-cards issues --keys "foo-1, foo-4, foo-10" --output c:\temp\test.pdf
|
36
|
+
</pre>
|
37
|
+
|
38
|
+
You can also specify ranges (a la "ruby ranges":http://www.ruby-doc.org/core/classes/Range.html) of keys e.g.
|
39
|
+
|
40
|
+
<pre>
|
41
|
+
jira-cards issues --keys foo-4..foo-10 --output c:\temp\test.pdf
|
42
|
+
</pre>
|
43
|
+
|
44
|
+
h2. Iteration
|
45
|
+
|
46
|
+
You want this if you want to get all of the cards for an iteration (or version in jira-speak). The flags are:
|
47
|
+
|
48
|
+
* --project
|
49
|
+
* --version
|
50
|
+
|
51
|
+
<pre>
|
52
|
+
jira-cards iteration --project "Some project" --version "Iteration 30" --output c:\temp\test.pdf
|
53
|
+
</pre>
|
54
|
+
|
55
|
+
h1. Templates
|
56
|
+
|
57
|
+
The scrum cards are generated from an "erb":http://ruby-doc.org/stdlib/libdoc/erb/rdoc/ html template which is then converted to pdf using "pdfkit":https://github.com/jdpace/PDFKit. If you want to use your own template you can specify the path to your erb template & css file in the initial config.
|
data/RakeFile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
require 'spec/rake/spectask'
|
12
|
+
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gem|
|
15
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
16
|
+
gem.name = "jira-cards"
|
17
|
+
gem.homepage = "http://github.com/jhollingworth/jira-cards"
|
18
|
+
gem.license = "MIT"
|
19
|
+
gem.summary = %Q{A command line tool for generating scrum cards from jira}
|
20
|
+
gem.description = %Q{You can get individual issues or all issues for an iteration. see --h for more info}
|
21
|
+
gem.email = "jamiehollingworth@gmail.com"
|
22
|
+
gem.authors = ["James Hollingworth"]
|
23
|
+
end
|
24
|
+
Jeweler::RubygemsDotOrgTasks.new
|
25
|
+
|
26
|
+
|
27
|
+
namespace :specs do
|
28
|
+
|
29
|
+
desc "Run all specs"
|
30
|
+
Spec::Rake::SpecTask.new('all') do |t|
|
31
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/bin/jira-cards
ADDED
data/lib/application.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'commands'
|
2
|
+
require 'clint'
|
3
|
+
|
4
|
+
c = Clint.new
|
5
|
+
|
6
|
+
c.usage do
|
7
|
+
$stderr.puts "\nA command line tool for generating jira scrum cards (for more help [--h|--help])\n\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
c.help do
|
11
|
+
$stderr.puts "config: shows the current jira configuration\n\n\t" +
|
12
|
+
"[--r|--reset] if this flag is specified, it will reset the configuration\n\n"
|
13
|
+
|
14
|
+
$stderr.puts "issues: print cards for specific issues\n\n\t" +
|
15
|
+
"[--k|--keys] issue keys. they can be csv, e.g. \"foo-1, foo-2\" or a range foo-2..foo-10\n\t" +
|
16
|
+
"[--o|--output] output path for pdf e.g. c:\\foo\\temp.pdf\n\n"
|
17
|
+
|
18
|
+
$stderr.puts "iteration: print cards for an iteration\n\n\t" +
|
19
|
+
"[--v|--version] the version you want to print e.g. \"Iteration 30\"\n\t" +
|
20
|
+
"[--p|--project] the project e.g. \"Some project\"\n\t" +
|
21
|
+
"[--o|--output] output path for pdf e.g. c:\\foo\\temp.pdf\n\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
c.options :help => false, :h => :help
|
25
|
+
c.parse ARGV
|
26
|
+
|
27
|
+
if c.options[:help]
|
28
|
+
c.help
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
|
32
|
+
c.subcommand Commands do |subcommand|
|
33
|
+
if subcommand == :config
|
34
|
+
c.options :reset => true, :r => :reset
|
35
|
+
end
|
36
|
+
|
37
|
+
if subcommand == :issues
|
38
|
+
c.options :keys => String, :k => :keys, :output => String, :o => :output
|
39
|
+
end
|
40
|
+
|
41
|
+
if subcommand == :iteration
|
42
|
+
c.options :project => String, :p => :project, :version => String, :v => :version, :output => String, :o => :output
|
43
|
+
end
|
44
|
+
c.parse
|
45
|
+
end
|
data/lib/commands.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'jql_generator'
|
2
|
+
require 'jira'
|
3
|
+
require 'html_renderer'
|
4
|
+
require 'pdf_renderer'
|
5
|
+
require 'configuration'
|
6
|
+
|
7
|
+
class Commands
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def config(options = {})
|
12
|
+
if options[:reset] == false
|
13
|
+
Configuration.reset!
|
14
|
+
else
|
15
|
+
Configuration.print
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def issues(options = {})
|
20
|
+
|
21
|
+
validate_flags(options, :keys, :output)
|
22
|
+
print_issues("issues", options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def iteration(options = {})
|
26
|
+
validate_flags(options, :project, :version, :output)
|
27
|
+
|
28
|
+
print_issues("iteration", options)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_flags(options, *flags)
|
34
|
+
error = false
|
35
|
+
flags.each do |flag|
|
36
|
+
if(options[flag].nil? || options[flag] == "")
|
37
|
+
$stderr.puts "--#{flag.to_s} is required"
|
38
|
+
error = true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
exit 1 if error
|
43
|
+
end
|
44
|
+
|
45
|
+
def print_issues(command, options)
|
46
|
+
config = Configuration.new
|
47
|
+
jira = Jira.new(config)
|
48
|
+
jql = JqlGenerator.generate(command, options)
|
49
|
+
|
50
|
+
html_renderer = HtmlRenderer.new(config)
|
51
|
+
pdf_renderer = PdfRenderer.new(config)
|
52
|
+
issues = jira.query(jql)
|
53
|
+
|
54
|
+
if issues.length == 0
|
55
|
+
puts "No issues found"
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
html = html_renderer.render(issues)
|
60
|
+
pdf_renderer.render(html, options[:output])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
require File.dirname(__FILE__) + '/meta'
|
5
|
+
|
6
|
+
class Configuration
|
7
|
+
include Meta
|
8
|
+
def initialize()
|
9
|
+
create_config if false == File.exists?(Configuration.config_path)
|
10
|
+
|
11
|
+
YAML.load_file(Configuration.config_path).each do |key, value|
|
12
|
+
create_method(key) {
|
13
|
+
value
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
def reset!()
|
21
|
+
File.delete(config_path) if File.exists?(config_path)
|
22
|
+
Configuration.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def config_path()
|
26
|
+
File.dirname(__FILE__) + '/config.yml'
|
27
|
+
end
|
28
|
+
|
29
|
+
def print()
|
30
|
+
if !File.exists?(config_path)
|
31
|
+
puts "jira-cards has not been configured yet"
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
YAML.load_file(Configuration.config_path).each do |key, value|
|
35
|
+
puts "#{key}: #{value}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def create_config
|
43
|
+
puts "Hi, we need to configure jira-cards. Can you answer some questions please?"
|
44
|
+
|
45
|
+
config = {
|
46
|
+
"jira_url" => jira_url,
|
47
|
+
"username" => username,
|
48
|
+
"password" => password,
|
49
|
+
"wkhtmltopdf_path" => wkhtmltopdf_path,
|
50
|
+
"template_erb" => template_erb,
|
51
|
+
"template_stylesheet" => template_stylesheet
|
52
|
+
}
|
53
|
+
|
54
|
+
File.open(Configuration.config_path, "w") do |f|
|
55
|
+
f.write(config.to_yaml)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def yaml(hash)
|
60
|
+
method = hash.respond_to?(:ya2yaml) ? :ya2yaml : :to_yaml
|
61
|
+
string = hash.deep_stringify_keys.send(method)
|
62
|
+
string.gsub("!ruby/symbol ", ":").sub("---","").split("\n").map(&:rstrip).join("\n").strip
|
63
|
+
end
|
64
|
+
|
65
|
+
def jira_url()
|
66
|
+
url = ask "What's the url to your jira instance? e.g. jira.atlassian.com"
|
67
|
+
invalid = true
|
68
|
+
while invalid
|
69
|
+
wsdl_url = "#{url}/rpc/soap/jirasoapservice-v2"
|
70
|
+
if url.match(/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$/ix).nil?
|
71
|
+
url = ask "The url #{url} is invalid. Please try again. e.g. jira.atlassian.com"
|
72
|
+
else
|
73
|
+
case Net::HTTP.get_response(url, '/rpc/soap/jirasoapservice-v2')
|
74
|
+
when Net::HTTPSuccess then
|
75
|
+
invalid = false
|
76
|
+
when Net::HTTPServiceUnavailable then
|
77
|
+
url = ask "The jira api is disabled. Please enable it, see http://confluence.atlassian.com/display/JIRA/JIRA+XML-RPC+Overview"
|
78
|
+
when Net::HTTPNotFound then
|
79
|
+
url = ask "Could not find #{wsdl_url}. Please check the url is correct"
|
80
|
+
else
|
81
|
+
url = ask "There was a problem getting the jira wsdl (#{wsdl_url}). Please check the url is correct"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
url
|
86
|
+
end
|
87
|
+
|
88
|
+
def username()
|
89
|
+
get_non_empty_value("What is your username for jira (must be an admin)", "username")
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_non_empty_value(question, type)
|
93
|
+
value = ask question
|
94
|
+
invalid = true
|
95
|
+
while invalid
|
96
|
+
if value == ""
|
97
|
+
value = ask "Please specify the #{type}"
|
98
|
+
else
|
99
|
+
invalid = false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
value
|
103
|
+
end
|
104
|
+
|
105
|
+
def password()
|
106
|
+
get_non_empty_value("What is the password?", "password")
|
107
|
+
end
|
108
|
+
|
109
|
+
def wkhtmltopdf_path()
|
110
|
+
get_file_path(
|
111
|
+
"What is the path to wkhtmltopdf? e.g. C:\\\\Program Files\\\\wkhtmltopdf\\\\wkhtmltopdf.exe. " +
|
112
|
+
"If you don't have it installed, please install from http://code.google.com/p/wkhtmltopdf/",
|
113
|
+
"wkhtmltopdf path"
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_file_path(question, type)
|
118
|
+
path = get_non_empty_value(question, type)
|
119
|
+
|
120
|
+
invalid = true
|
121
|
+
while invalid
|
122
|
+
if !File.exists?(path)
|
123
|
+
path = ask("The file #{path} does not exist")
|
124
|
+
else
|
125
|
+
invalid = false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
path
|
130
|
+
end
|
131
|
+
|
132
|
+
def template_erb()
|
133
|
+
invalid = true
|
134
|
+
while(invalid)
|
135
|
+
@use_default = (ask "Do you want to use the default template? (y/n)").downcase
|
136
|
+
if @use_default == "y" || @use_default == "n"
|
137
|
+
invalid = false
|
138
|
+
@use_default = @use_default == "y"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
if @use_default
|
143
|
+
clean_path("default_template.erb")
|
144
|
+
else
|
145
|
+
get_file_path("What is the path to the template? e.g. c:\\\\foo\\\\template.erb", "template path")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def template_stylesheet()
|
150
|
+
if @use_default
|
151
|
+
clean_path("default_template.css")
|
152
|
+
else
|
153
|
+
get_file_path("What is the path to the template stylesheet? e.g. c:\\\\foo\\\\template.css", "template stylesheet")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def clean_path(path)
|
158
|
+
File.expand_path(File.dirname(__FILE__) + "/" + path).gsub('/', '\\\\\\')
|
159
|
+
end
|
160
|
+
|
161
|
+
def ask(question)
|
162
|
+
puts question
|
163
|
+
STDIN.gets.strip
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
html
|
2
|
+
{
|
3
|
+
font-family:georgia;
|
4
|
+
}
|
5
|
+
|
6
|
+
td
|
7
|
+
{
|
8
|
+
width:50%;
|
9
|
+
height: 453px;
|
10
|
+
border-bottom:1px dashed #CCC;
|
11
|
+
padding:20px;
|
12
|
+
text-align: top;
|
13
|
+
}
|
14
|
+
|
15
|
+
h1
|
16
|
+
{
|
17
|
+
font-size:33px;
|
18
|
+
margin:0;
|
19
|
+
border:0;
|
20
|
+
}
|
21
|
+
|
22
|
+
.type
|
23
|
+
{
|
24
|
+
font-size:25px;
|
25
|
+
}
|
26
|
+
|
27
|
+
td.left
|
28
|
+
{
|
29
|
+
border-right:1px dashed #CCC;
|
30
|
+
}
|
31
|
+
|
32
|
+
.third-row td
|
33
|
+
{
|
34
|
+
border-bottom:none;
|
35
|
+
}
|
36
|
+
|
37
|
+
dl
|
38
|
+
{
|
39
|
+
font-size:20px;
|
40
|
+
}
|
41
|
+
|
42
|
+
dt
|
43
|
+
{
|
44
|
+
float:left;
|
45
|
+
color:#9E999B;
|
46
|
+
}
|
47
|
+
|
48
|
+
dd
|
49
|
+
{
|
50
|
+
padding-left:130px;
|
51
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<html>
|
2
|
+
<body>
|
3
|
+
<table width="100%" height="100%" cellspacing="0" cellpadding="0">
|
4
|
+
<% row = 0 %>
|
5
|
+
|
6
|
+
<tr class="row-<%= row %>">
|
7
|
+
|
8
|
+
<% @issues.each do |i| %>
|
9
|
+
|
10
|
+
<td valign="top" class="<%= (@issues.index(i) % 2 == 0) ? "left" : "right" %>">
|
11
|
+
|
12
|
+
<h1><%= i.key %>: <%= i.summary %></h1>
|
13
|
+
|
14
|
+
<div class="type <%= i.issue_type.downcase %>"><%= i.issue_type %></div>
|
15
|
+
|
16
|
+
<dl>
|
17
|
+
<dt>Points</dt><dd><%= i.points %></dd>
|
18
|
+
<dt>Priority</dt><dd><%= i.priority %></dd>
|
19
|
+
<dt>Story Owner</dt><dd><%= i.assignee.capitalize %></dd>
|
20
|
+
</dl>
|
21
|
+
</td>
|
22
|
+
|
23
|
+
<% if @issues.index(i) % 2 != 0 %>
|
24
|
+
|
25
|
+
<% row +=1 %>
|
26
|
+
|
27
|
+
</tr>
|
28
|
+
|
29
|
+
<% if row % 3 == 0 %>
|
30
|
+
|
31
|
+
</table>
|
32
|
+
|
33
|
+
<table style="margin-top:16px;" width="100%" height="100%" cellspacing="0" cellpadding="0">
|
34
|
+
|
35
|
+
<% end %>
|
36
|
+
|
37
|
+
<tr class="row-<%= row %> <%= ((row + 1) % 3 == 0)? "third-row" : "" %>">
|
38
|
+
|
39
|
+
<% end %>
|
40
|
+
|
41
|
+
<% end %>
|
42
|
+
</table>
|
43
|
+
</body>
|
44
|
+
</html>
|
data/lib/flags.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
class HtmlRenderer
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@template = config.template_erb
|
7
|
+
end
|
8
|
+
|
9
|
+
def render(issues)
|
10
|
+
rhtml = ERB.new(IO.read(@template))
|
11
|
+
model = Model.new(issues)
|
12
|
+
rhtml.result(model.get_binding)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
class Model
|
18
|
+
def initialize(issues)
|
19
|
+
@issues = issues
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_binding()
|
23
|
+
binding
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/issue.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/meta'
|
2
|
+
|
3
|
+
class Issue
|
4
|
+
include Meta
|
5
|
+
attr_reader :issue_type
|
6
|
+
|
7
|
+
def initialize(issue, custom_fields, types)
|
8
|
+
@issue = issue
|
9
|
+
@issue_type = get_type(types)
|
10
|
+
custom_fields.each do |id, name|
|
11
|
+
create_method(name) {
|
12
|
+
custom_field(id)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def method_missing(m, *args, &block)
|
20
|
+
@issue.send m
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_type(types)
|
24
|
+
types.each do |id, name|
|
25
|
+
return name if @issue.type == id
|
26
|
+
end
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def custom_field(id)
|
31
|
+
@issue.customFieldValues.each do |c|
|
32
|
+
return c.values if c.customfieldId == id
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
data/lib/jira.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'jira4r/jira4r'
|
2
|
+
require File.dirname(__FILE__) + '/issue'
|
3
|
+
gem 'soap4r'
|
4
|
+
|
5
|
+
class Jira
|
6
|
+
def initialize(config)
|
7
|
+
@jira = Jira4R::JiraTool.new(2, "http://#{config.jira_url}")
|
8
|
+
@jira.login(config.username, config.password)
|
9
|
+
|
10
|
+
@types = {}
|
11
|
+
@custom_fields = {}
|
12
|
+
|
13
|
+
@jira.getIssueTypes().each {|t| @types[t.id] = t.name }
|
14
|
+
@jira.getCustomFields().each { |f| @custom_fields[f.id] = f.name.downcase.gsub(/ /, '_') }
|
15
|
+
end
|
16
|
+
|
17
|
+
def query(jql)
|
18
|
+
@jira.getIssuesFromJqlSearch(jql, 10000).collect do |issue|
|
19
|
+
issue = Issue.new(issue, @custom_fields, @types)
|
20
|
+
puts "Found #{issue.key} (#{issue.issue_type})"
|
21
|
+
issue
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class IssuesJqlGenerator
|
2
|
+
def generate(options)
|
3
|
+
|
4
|
+
keys = options[:keys].split(',').collect { |k| k.strip }.collect do |key|
|
5
|
+
#if it's a specific key
|
6
|
+
if !key.match(/(.*)\-(\d+)\.\.(.*)\-(\d+)/).nil?
|
7
|
+
raise "The beginning and end ranges (#{$1}, #{$2}) do not match" if $1 != $3
|
8
|
+
($2.to_i..$4.to_i).collect { |id| "#{$1}-#{id}"}
|
9
|
+
elsif !key.match(/(.*\-\d+)/).nil?
|
10
|
+
$1
|
11
|
+
else
|
12
|
+
"Unknown key #{key}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
keys.flatten.collect { |key| "key = \"#{key}\"" } * " or "
|
17
|
+
end
|
18
|
+
end
|
data/lib/meta.rb
ADDED
data/lib/pdf_renderer.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'pdfkit'
|
2
|
+
|
3
|
+
class PdfRenderer
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@stylesheet = config.template_stylesheet
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def render(html, output_file_path)
|
11
|
+
PDFKit.configure do |c|
|
12
|
+
c.wkhtmltopdf = @config.wkhtmltopdf_path
|
13
|
+
end
|
14
|
+
|
15
|
+
kit = PDFKit.new(html, :page_size => 'Letter', :margin_left => 0, :margin_right => 0, :margin_bottom => 0, :margin_top => 0)
|
16
|
+
kit.stylesheets << @stylesheet
|
17
|
+
kit.to_file output_file_path
|
18
|
+
|
19
|
+
puts "Printed scrum cards to #{output_file_path}"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'html_renderer'
|
2
|
+
require 'mock'
|
3
|
+
|
4
|
+
describe "When I render the issues" do
|
5
|
+
before(:all) do
|
6
|
+
@issues = [
|
7
|
+
Mock.new({
|
8
|
+
:issue_type => "Story", :key => "PY-1"
|
9
|
+
})
|
10
|
+
]
|
11
|
+
@renderer = HtmlRenderer.new(Mock.new({
|
12
|
+
:template_erb => File.dirname(__FILE__) + '/test.erb'
|
13
|
+
}))
|
14
|
+
end
|
15
|
+
|
16
|
+
before(:each) do
|
17
|
+
@html = @renderer.render(@issues)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should render the issues using the default template" do
|
21
|
+
@html.should == "Type:Story\nKey:PY-1"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'issues_jql_generator'
|
3
|
+
|
4
|
+
describe "Generating issue jql" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
@generator = IssuesJqlGenerator.new
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
@jql = @generator.generate({:keys => @keys})
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "When the issue keys are separated by commas" do
|
15
|
+
before(:all) do
|
16
|
+
@keys = "py-1, py-3"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be able to split the keys" do
|
20
|
+
@jql.should == "key = \"py-1\" or key = \"py-3\""
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "When the issue keys are a range" do
|
25
|
+
before(:all) do
|
26
|
+
@keys = "py-40..py-42"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be able to work out the range" do
|
30
|
+
@jql.should == "key = \"py-40\" or key = \"py-41\" or key = \"py-42\""
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "When the issues keys are ranges separated by commas" do
|
35
|
+
before(:all) do
|
36
|
+
@keys = "py-1, py-4..py-6, py-14"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be able to split the keys and work out the range" do
|
40
|
+
@jql.should == "key = \"py-1\" or key = \"py-4\" or key = \"py-5\" or key = \"py-6\" or key = \"py-14\""
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/mock.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pdf_renderer'
|
3
|
+
|
4
|
+
describe "When I render some html to a pdf" do
|
5
|
+
before(:all) do
|
6
|
+
@html = "<html><body><h1>Hello world</h1></body></html>"
|
7
|
+
@renderer = PdfRenderer.new(Mock.new({
|
8
|
+
:template_stylesheet => File.dirname(__FILE__) + '/test.css',
|
9
|
+
:wkhtmltopdf_path => "C:\\Program Files\\wkhtmltopdf\\wkhtmltopdf.exe"
|
10
|
+
}))
|
11
|
+
|
12
|
+
@output_path = File.dirname(__FILE__) + '/test.pdf'
|
13
|
+
end
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
delete_file(@output_path)
|
17
|
+
@renderer.render(@html, @output_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should create the pdf" do
|
21
|
+
File.exists?(@output_path).should == true
|
22
|
+
end
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
SPEC_ROOT = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
$:.unshift(SPEC_ROOT)
|
4
|
+
$:.unshift(File.join(SPEC_ROOT, '..', 'lib'))
|
5
|
+
$:.unshift(File.join(SPEC_ROOT, '..', 'lib', 'jql_generators'))
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'ftools'
|
9
|
+
|
10
|
+
def delete_file(path)
|
11
|
+
(0..20).each do
|
12
|
+
begin
|
13
|
+
File.delete(path) if File.exists?(path)
|
14
|
+
return
|
15
|
+
rescue
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
puts "Failed to delete #{path}"
|
20
|
+
end
|
data/spec/test.css
ADDED
data/spec/test.erb
ADDED
metadata
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jira-cards
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- James Hollingworth
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-02 00:00:00 +00:00
|
19
|
+
default_executable: jira-cards
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
type: :runtime
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
prerelease: false
|
33
|
+
name: soap4r
|
34
|
+
requirement: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 19
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
- 3
|
46
|
+
- 0
|
47
|
+
version: 0.3.0
|
48
|
+
prerelease: false
|
49
|
+
name: jira4r-jh
|
50
|
+
requirement: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
type: :runtime
|
53
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
prerelease: false
|
63
|
+
name: pdfkit
|
64
|
+
requirement: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
type: :runtime
|
67
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
prerelease: false
|
77
|
+
name: clint
|
78
|
+
requirement: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
type: :development
|
81
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ~>
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
hash: 23
|
87
|
+
segments:
|
88
|
+
- 1
|
89
|
+
- 0
|
90
|
+
- 0
|
91
|
+
version: 1.0.0
|
92
|
+
prerelease: false
|
93
|
+
name: bundler
|
94
|
+
requirement: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
type: :development
|
97
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ~>
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 7
|
103
|
+
segments:
|
104
|
+
- 1
|
105
|
+
- 5
|
106
|
+
- 2
|
107
|
+
version: 1.5.2
|
108
|
+
prerelease: false
|
109
|
+
name: jeweler
|
110
|
+
requirement: *id006
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
type: :development
|
113
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
hash: 3
|
119
|
+
segments:
|
120
|
+
- 0
|
121
|
+
version: "0"
|
122
|
+
prerelease: false
|
123
|
+
name: rake
|
124
|
+
requirement: *id007
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
type: :development
|
127
|
+
version_requirements: &id008 !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
hash: 3
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
version: "0"
|
136
|
+
prerelease: false
|
137
|
+
name: rspec
|
138
|
+
requirement: *id008
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
type: :development
|
141
|
+
version_requirements: &id009 !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - <
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
hash: 63
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
- 10
|
150
|
+
- 4
|
151
|
+
version: 0.10.4
|
152
|
+
prerelease: false
|
153
|
+
name: ruby-debug
|
154
|
+
requirement: *id009
|
155
|
+
description: You can get individual issues or all issues for an iteration. see --h for more info
|
156
|
+
email: jamiehollingworth@gmail.com
|
157
|
+
executables:
|
158
|
+
- jira-cards
|
159
|
+
extensions: []
|
160
|
+
|
161
|
+
extra_rdoc_files:
|
162
|
+
- LICENSE.txt
|
163
|
+
- README.textile
|
164
|
+
files:
|
165
|
+
- GemFile
|
166
|
+
- Gemfile.lock
|
167
|
+
- LICENSE.txt
|
168
|
+
- README.textile
|
169
|
+
- RakeFile
|
170
|
+
- VERSION
|
171
|
+
- bin/jira-cards
|
172
|
+
- lib/application.rb
|
173
|
+
- lib/commands.rb
|
174
|
+
- lib/configuration.rb
|
175
|
+
- lib/default_template.css
|
176
|
+
- lib/default_template.erb
|
177
|
+
- lib/flags.rb
|
178
|
+
- lib/html_renderer.rb
|
179
|
+
- lib/issue.rb
|
180
|
+
- lib/jira.rb
|
181
|
+
- lib/jql_generator.rb
|
182
|
+
- lib/jql_generators/issues_jql_generator.rb
|
183
|
+
- lib/jql_generators/iteration_jql_generator.rb
|
184
|
+
- lib/meta.rb
|
185
|
+
- lib/pdf_renderer.rb
|
186
|
+
- spec/html_renderer_spec.rb
|
187
|
+
- spec/issue_jql_generator_spec.rb
|
188
|
+
- spec/mock.rb
|
189
|
+
- spec/pdf_renderer_spec.rb
|
190
|
+
- spec/spec_helper.rb
|
191
|
+
- spec/test.css
|
192
|
+
- spec/test.erb
|
193
|
+
has_rdoc: true
|
194
|
+
homepage: http://github.com/jhollingworth/jira-cards
|
195
|
+
licenses:
|
196
|
+
- MIT
|
197
|
+
post_install_message:
|
198
|
+
rdoc_options: []
|
199
|
+
|
200
|
+
require_paths:
|
201
|
+
- lib
|
202
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
203
|
+
none: false
|
204
|
+
requirements:
|
205
|
+
- - ">="
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
hash: 3
|
208
|
+
segments:
|
209
|
+
- 0
|
210
|
+
version: "0"
|
211
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
212
|
+
none: false
|
213
|
+
requirements:
|
214
|
+
- - ">="
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
hash: 3
|
217
|
+
segments:
|
218
|
+
- 0
|
219
|
+
version: "0"
|
220
|
+
requirements: []
|
221
|
+
|
222
|
+
rubyforge_project:
|
223
|
+
rubygems_version: 1.3.7
|
224
|
+
signing_key:
|
225
|
+
specification_version: 3
|
226
|
+
summary: A command line tool for generating scrum cards from jira
|
227
|
+
test_files:
|
228
|
+
- spec/html_renderer_spec.rb
|
229
|
+
- spec/issue_jql_generator_spec.rb
|
230
|
+
- spec/mock.rb
|
231
|
+
- spec/pdf_renderer_spec.rb
|
232
|
+
- spec/spec_helper.rb
|