twig 1.1 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +11 -0
- data/README.md +7 -3
- data/bin/twig +5 -1
- data/bin/twig-gh-open +3 -30
- data/bin/twig-gh-open-issue +1 -31
- data/bin/twig-gh-update +1 -31
- data/lib/twig.rb +24 -9
- data/lib/twig/cli.rb +97 -36
- data/lib/twig/display.rb +2 -2
- data/lib/twig/github.rb +36 -0
- data/lib/twig/options.rb +24 -7
- data/lib/twig/version.rb +1 -1
- data/spec/twig/cli_spec.rb +248 -140
- data/spec/twig/display_spec.rb +19 -15
- data/spec/twig/options_spec.rb +33 -25
- data/spec/twig_spec.rb +55 -8
- data/twig.gemspec +1 -1
- metadata +8 -7
data/HISTORY.md
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
Twig
|
2
2
|
====
|
3
3
|
|
4
|
+
1.2 (2013-03-21)
|
5
|
+
----------------
|
6
|
+
* ENHANCEMENT: Add `--only-<property>` and `--except-<property>` options for
|
7
|
+
filtering custom properties by value.
|
8
|
+
* ENHANCEMENT: Simplify setup for writing GitHub-related Twig subcommands.
|
9
|
+
* FIX: Fix showing `twig --help` outside of a Git repository. (GH-16. Thanks
|
10
|
+
[ryangreenberg](https://github.com/ryangreenberg)!)
|
11
|
+
* FIX: Fix showing `twig --version` outside of a Git repository.
|
12
|
+
* FIX: Fix the project's homepage URL in `twig --help`. (GH-17. Thanks
|
13
|
+
[ryangreenberg](https://github.com/ryangreenberg)!)
|
14
|
+
|
4
15
|
1.1 (2013-03-06)
|
5
16
|
----------------
|
6
17
|
* ENHANCEMENT: Add branch name tab completion for `-b` and `--branch` options.
|
data/README.md
CHANGED
@@ -52,14 +52,18 @@ Filtering branches
|
|
52
52
|
------------------
|
53
53
|
|
54
54
|
Twig lists all of your branches by default (newest first), but you can filter
|
55
|
-
them by name and
|
55
|
+
them by age, name, and custom properties:
|
56
56
|
|
57
|
+
* `twig --max-days-old <age>`:
|
58
|
+
Only list branches below a given age
|
57
59
|
* `twig --only-branch <pattern>`:
|
58
60
|
Only list branches whose name matches a given pattern
|
59
61
|
* `twig --except-branch <pattern>`:
|
60
62
|
Don't list branches whose name matches a given pattern
|
61
|
-
* `twig --
|
62
|
-
Only list branches
|
63
|
+
* `twig --only-<property> <pattern>`:
|
64
|
+
Only list branches with a given property that matches a given pattern
|
65
|
+
* `twig --except-<property> <pattern>`:
|
66
|
+
Don't list branches with a given property that matches a given pattern
|
63
67
|
* `twig --all`:
|
64
68
|
List all branches regardless of other filtering options
|
65
69
|
|
data/bin/twig
CHANGED
@@ -3,7 +3,11 @@
|
|
3
3
|
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'twig'))
|
4
4
|
|
5
5
|
twig = Twig.new
|
6
|
-
|
6
|
+
repo_required = ARGV != ['--help'] && ARGV != ['help'] && ARGV != ['--version']
|
7
|
+
|
8
|
+
if repo_required && !twig.repo?
|
9
|
+
abort "Current directory is not a git repository."
|
10
|
+
end
|
7
11
|
|
8
12
|
# Gettin' twiggy wit' it.
|
9
13
|
twig.read_config_file!
|
data/bin/twig-gh-open
CHANGED
@@ -4,37 +4,10 @@
|
|
4
4
|
#
|
5
5
|
# Author: Ron DeVera <http://rondevera.com>
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
if origin_url.empty? || username.empty? || repository.empty?
|
10
|
-
abort_for_non_github_repo
|
11
|
-
end
|
7
|
+
require 'rubygems'
|
8
|
+
require 'twig/github'
|
12
9
|
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
def origin_url
|
17
|
-
@origin_url ||= `git config remote.origin.url`.strip
|
18
|
-
end
|
19
|
-
|
20
|
-
def origin_url_parts
|
21
|
-
@origin_url_parts ||= origin_url.split(/[\/:]/)
|
22
|
-
end
|
23
|
-
|
24
|
-
def username
|
25
|
-
@username ||= origin_url_parts[-2] || ''
|
26
|
-
end
|
27
|
-
|
28
|
-
def repository
|
29
|
-
@repo ||= origin_url_parts[-1].sub(/\.git$/, '') || ''
|
30
|
-
end
|
31
|
-
|
32
|
-
def abort_for_non_github_repo
|
33
|
-
abort 'This does not appear to be a GitHub repository.'
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
TwigGithubRepo.new do |gh_repo|
|
10
|
+
Twig::GithubRepo.new do |gh_repo|
|
38
11
|
url = "https://github.com/#{gh_repo.username}/#{gh_repo.repository}"
|
39
12
|
puts "GitHub URL: #{url}"
|
40
13
|
`which open && open #{url}`
|
data/bin/twig-gh-open-issue
CHANGED
@@ -11,37 +11,7 @@
|
|
11
11
|
require 'rubygems'
|
12
12
|
require 'twig'
|
13
13
|
|
14
|
-
|
15
|
-
def initialize
|
16
|
-
if origin_url.empty? || username.empty? || repository.empty?
|
17
|
-
abort_for_non_github_repo
|
18
|
-
end
|
19
|
-
|
20
|
-
yield(self)
|
21
|
-
end
|
22
|
-
|
23
|
-
def origin_url
|
24
|
-
@origin_url ||= `git config remote.origin.url`.strip
|
25
|
-
end
|
26
|
-
|
27
|
-
def origin_url_parts
|
28
|
-
@origin_url_parts ||= origin_url.split(/[\/:]/)
|
29
|
-
end
|
30
|
-
|
31
|
-
def username
|
32
|
-
@username ||= origin_url_parts[-2] || ''
|
33
|
-
end
|
34
|
-
|
35
|
-
def repository
|
36
|
-
@repo ||= origin_url_parts[-1].sub(/\.git$/, '') || ''
|
37
|
-
end
|
38
|
-
|
39
|
-
def abort_for_non_github_repo
|
40
|
-
abort 'This does not appear to be a GitHub repository.'
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
TwigGithubRepo.new do |gh_repo|
|
14
|
+
Twig::GithubRepo.new do |gh_repo|
|
45
15
|
twig = Twig.new
|
46
16
|
twig.read_cli_options!(ARGV)
|
47
17
|
branch_name = twig.options[:branch] || twig.current_branch_name
|
data/bin/twig-gh-update
CHANGED
@@ -10,41 +10,11 @@ require 'json'
|
|
10
10
|
require 'net/https'
|
11
11
|
require 'uri'
|
12
12
|
|
13
|
-
class TwigGithubRepo
|
14
|
-
def initialize
|
15
|
-
if origin_url.empty? || username.empty? || repository.empty?
|
16
|
-
abort_for_non_github_repo
|
17
|
-
end
|
18
|
-
|
19
|
-
yield(self)
|
20
|
-
end
|
21
|
-
|
22
|
-
def origin_url
|
23
|
-
@origin_url ||= `git config remote.origin.url`.strip
|
24
|
-
end
|
25
|
-
|
26
|
-
def origin_url_parts
|
27
|
-
@origin_url_parts ||= origin_url.split(/[\/:]/)
|
28
|
-
end
|
29
|
-
|
30
|
-
def username
|
31
|
-
@username ||= origin_url_parts[-2] || ''
|
32
|
-
end
|
33
|
-
|
34
|
-
def repository
|
35
|
-
@repo ||= origin_url_parts[-1].sub(/\.git$/, '') || ''
|
36
|
-
end
|
37
|
-
|
38
|
-
def abort_for_non_github_repo
|
39
|
-
abort 'This does not appear to be a GitHub repository.'
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
13
|
twig = Twig.new
|
44
14
|
twig.read_config_file!
|
45
15
|
twig.read_cli_options!(ARGV)
|
46
16
|
|
47
|
-
|
17
|
+
Twig::GithubRepo.new do |gh_repo|
|
48
18
|
$stdout.sync = true
|
49
19
|
print 'Getting latest states for GitHub issues...'
|
50
20
|
|
data/lib/twig.rb
CHANGED
@@ -26,7 +26,7 @@ class Twig
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def repo?
|
29
|
-
Twig.run('git rev-parse')
|
29
|
+
Twig.run('git rev-parse 2>&1')
|
30
30
|
$?.success?
|
31
31
|
end
|
32
32
|
|
@@ -57,15 +57,30 @@ class Twig
|
|
57
57
|
max_seconds_old = options[:max_days_old] * 86400 if options[:max_days_old]
|
58
58
|
|
59
59
|
branches.select do |branch|
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
catch :skip_branch do
|
61
|
+
if max_seconds_old
|
62
|
+
seconds_old = now.to_i - branch.last_commit_time.to_i
|
63
|
+
next if seconds_old > max_seconds_old
|
64
|
+
end
|
65
|
+
|
66
|
+
(options[:property_except] || {}).each do |property_name, property_value|
|
67
|
+
if property_name == :branch
|
68
|
+
throw :skip_branch if branch.name =~ property_value
|
69
|
+
elsif branch.get_property(property_name.to_s) =~ property_value
|
70
|
+
throw :skip_branch
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
(options[:property_only] || {}).each do |property_name, property_value|
|
75
|
+
if property_name == :branch
|
76
|
+
throw :skip_branch if branch.name !~ property_value
|
77
|
+
elsif branch.get_property(property_name.to_s) !~ property_value
|
78
|
+
throw :skip_branch
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
true
|
63
83
|
end
|
64
|
-
|
65
|
-
next if options[:branch_except] && branch.name =~ options[:branch_except]
|
66
|
-
next if options[:branch_only] && branch.name !~ options[:branch_only]
|
67
|
-
|
68
|
-
true
|
69
84
|
end
|
70
85
|
end
|
71
86
|
|
data/lib/twig/cli.rb
CHANGED
@@ -19,7 +19,7 @@ class Twig
|
|
19
19
|
|
20
20
|
#{intro}
|
21
21
|
|
22
|
-
|
22
|
+
http://rondevera.github.com/twig
|
23
23
|
|
24
24
|
BANNER
|
25
25
|
end
|
@@ -37,8 +37,8 @@ class Twig
|
|
37
37
|
# Split words into lines
|
38
38
|
while words.any?
|
39
39
|
current_word = words.shift
|
40
|
-
current_word_size =
|
41
|
-
last_line_size = lines.last &&
|
40
|
+
current_word_size = unformat_string(current_word).size
|
41
|
+
last_line_size = lines.last && unformat_string(lines.last).size
|
42
42
|
|
43
43
|
if last_line_size && (last_line_size + current_word_size + 1 <= width)
|
44
44
|
lines.last << ' ' << current_word
|
@@ -82,7 +82,26 @@ class Twig
|
|
82
82
|
|
83
83
|
desc = 'Show this help content.'
|
84
84
|
opts.on('--help', *help_description(desc)) do
|
85
|
-
|
85
|
+
summary_lines = opts.to_a
|
86
|
+
|
87
|
+
# Filter out `--only-PROPERTY` lines
|
88
|
+
summary_lines.each do |line|
|
89
|
+
is_custom_property_only = (
|
90
|
+
line.include?('--only-') &&
|
91
|
+
!line.include?('--only-branch') &&
|
92
|
+
!line.include?('--only-PROPERTY')
|
93
|
+
)
|
94
|
+
is_custom_property_except = (
|
95
|
+
line.include?('--except-') &&
|
96
|
+
!line.include?('--except-branch') &&
|
97
|
+
!line.include?('--except-PROPERTY')
|
98
|
+
)
|
99
|
+
unless is_custom_property_only || is_custom_property_except
|
100
|
+
puts line
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
exit
|
86
105
|
end
|
87
106
|
|
88
107
|
desc = 'Show Twig version.'
|
@@ -94,37 +113,62 @@ class Twig
|
|
94
113
|
|
95
114
|
help_separator(opts, 'Filtering branches:')
|
96
115
|
|
116
|
+
desc = 'Only list branches below a given age.'
|
117
|
+
opts.on(
|
118
|
+
'--max-days-old AGE', *help_description(desc, :add_separator => true)
|
119
|
+
) do |age|
|
120
|
+
set_option(:max_days_old, age)
|
121
|
+
end
|
122
|
+
|
97
123
|
desc = 'Only list branches whose name matches a given pattern.'
|
98
124
|
opts.on(
|
99
125
|
'--only-branch PATTERN',
|
100
126
|
*help_description(desc, :add_separator => true)
|
101
127
|
) do |pattern|
|
102
|
-
set_option(:
|
128
|
+
set_option(:property_only, :branch => pattern)
|
103
129
|
end
|
104
130
|
|
105
131
|
desc = 'Do not list branches whose name matches a given pattern.'
|
106
132
|
opts.on(
|
107
133
|
'--except-branch PATTERN',
|
108
|
-
*help_description(desc, :add_separator =>
|
134
|
+
*help_description(desc, :add_separator => false)
|
135
|
+
# `:add_separator => false` skips the extra line generated by the
|
136
|
+
# custom property filters below.
|
109
137
|
) do |pattern|
|
110
|
-
set_option(:
|
138
|
+
set_option(:property_except, :branch => pattern)
|
111
139
|
end
|
112
140
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
141
|
+
custom_properties = Twig::Branch.all_properties
|
142
|
+
custom_properties.each do |property_name|
|
143
|
+
opts.on("--only-#{property_name} PATTERN") do |pattern|
|
144
|
+
set_option(:property_only, property_name.to_sym => pattern)
|
145
|
+
end
|
146
|
+
|
147
|
+
opts.on("--except-#{property_name} PATTERN") do |pattern|
|
148
|
+
set_option(:property_except, property_name.to_sym => pattern)
|
149
|
+
end
|
118
150
|
end
|
119
151
|
|
152
|
+
custom_properties_desc_lines = [
|
153
|
+
['--only-PROPERTY PATTERN', 'Only list branches with a given property'],
|
154
|
+
['', 'that matches a given pattern.'],
|
155
|
+
['', ''],
|
156
|
+
['--except-PROPERTY PATTERN', 'Do not list branches with a given property'],
|
157
|
+
['', 'that matches a given pattern.']
|
158
|
+
]
|
159
|
+
custom_properties_desc = custom_properties_desc_lines.inject('') do |desc, line_parts|
|
160
|
+
desc + sprintf(' %-29s', line_parts[0]) + line_parts[1] + "\n"
|
161
|
+
end
|
162
|
+
help_separator(opts, custom_properties_desc, :trailing => "\n")
|
163
|
+
|
120
164
|
desc =
|
121
|
-
'Lists all branches regardless of
|
165
|
+
'Lists all branches regardless of other filtering options. ' +
|
122
166
|
'Useful for overriding options in ' +
|
123
167
|
File.basename(Twig::Options::CONFIG_FILE) + '.'
|
124
168
|
opts.on('--all', *help_description(desc)) do |pattern|
|
125
169
|
unset_option(:max_days_old)
|
126
|
-
unset_option(:
|
127
|
-
unset_option(:
|
170
|
+
unset_option(:property_except)
|
171
|
+
unset_option(:property_only)
|
128
172
|
end
|
129
173
|
|
130
174
|
|
@@ -171,7 +215,11 @@ class Twig
|
|
171
215
|
|
172
216
|
option_parser.parse!(args)
|
173
217
|
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => exception
|
174
|
-
|
218
|
+
abort_for_option_exception(exception)
|
219
|
+
end
|
220
|
+
|
221
|
+
def abort_for_option_exception(exception)
|
222
|
+
puts exception.message
|
175
223
|
puts 'For a list of options, run `twig --help`.'
|
176
224
|
exit
|
177
225
|
end
|
@@ -200,36 +248,49 @@ class Twig
|
|
200
248
|
# Get/set branch property
|
201
249
|
if property_value
|
202
250
|
# `$ twig <key> <value>`
|
203
|
-
|
204
|
-
puts set_branch_property(branch_name, property_name, property_value)
|
205
|
-
rescue ArgumentError, RuntimeError => exception
|
206
|
-
abort exception.message
|
207
|
-
end
|
251
|
+
set_branch_property_for_cli(branch_name, property_name, property_value)
|
208
252
|
else
|
209
253
|
# `$ twig <key>`
|
210
|
-
|
211
|
-
value = get_branch_property(branch_name, property_name)
|
212
|
-
if value
|
213
|
-
puts value
|
214
|
-
else
|
215
|
-
abort %{The branch "#{branch_name}" does not have the property "#{property_name}".}
|
216
|
-
end
|
217
|
-
rescue ArgumentError => exception
|
218
|
-
abort exception.message
|
219
|
-
end
|
254
|
+
get_branch_property_for_cli(branch_name, property_name)
|
220
255
|
end
|
221
256
|
elsif property_to_unset
|
222
257
|
# `$ twig --unset <key>`
|
223
|
-
|
224
|
-
puts unset_branch_property(branch_name, property_to_unset)
|
225
|
-
rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
|
226
|
-
abort exception.message
|
227
|
-
end
|
258
|
+
unset_branch_property_for_cli(branch_name, property_to_unset)
|
228
259
|
else
|
229
260
|
# `$ twig`
|
230
261
|
puts list_branches
|
231
262
|
end
|
232
263
|
end
|
233
264
|
|
265
|
+
def get_branch_property_for_cli(branch_name, property_name)
|
266
|
+
begin
|
267
|
+
value = get_branch_property(branch_name, property_name)
|
268
|
+
if value
|
269
|
+
puts value
|
270
|
+
else
|
271
|
+
raise Twig::Branch::MissingPropertyError,
|
272
|
+
%{The branch "#{branch_name}" does not have the property "#{property_name}".}
|
273
|
+
end
|
274
|
+
rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
|
275
|
+
abort exception.message
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def set_branch_property_for_cli(branch_name, property_name, property_value)
|
280
|
+
begin
|
281
|
+
puts set_branch_property(branch_name, property_name, property_value)
|
282
|
+
rescue ArgumentError, RuntimeError => exception
|
283
|
+
abort exception.message
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def unset_branch_property_for_cli(branch_name, property_name)
|
288
|
+
begin
|
289
|
+
puts unset_branch_property(branch_name, property_name)
|
290
|
+
rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
|
291
|
+
abort exception.message
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
234
295
|
end
|
235
296
|
end
|
data/lib/twig/display.rb
CHANGED
@@ -125,8 +125,8 @@ class Twig
|
|
125
125
|
"\e[#{string_options.join(';')}m#{string}\e[0m"
|
126
126
|
end
|
127
127
|
|
128
|
-
def
|
129
|
-
string.gsub(/\e\[[0-9]+(;[0-9]+)?m/, '')
|
128
|
+
def unformat_string(string)
|
129
|
+
string.gsub(/\e\[[0-9]+(;[0-9]+)?m/, '')
|
130
130
|
end
|
131
131
|
end # module Display
|
132
132
|
end
|
data/lib/twig/github.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
class Twig
|
2
|
+
class GithubRepo
|
3
|
+
def self.run(command)
|
4
|
+
# Duplicated from `Twig.run` for making lightweight subcommands.
|
5
|
+
`#{command}`.strip
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
if origin_url.empty? || username.empty? || repository.empty?
|
10
|
+
abort_for_non_github_repo
|
11
|
+
end
|
12
|
+
|
13
|
+
yield(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def origin_url
|
17
|
+
@origin_url ||= Twig::GithubRepo.run('git config remote.origin.url')
|
18
|
+
end
|
19
|
+
|
20
|
+
def origin_url_parts
|
21
|
+
@origin_url_parts ||= origin_url.split(/[\/:]/)
|
22
|
+
end
|
23
|
+
|
24
|
+
def username
|
25
|
+
@username ||= origin_url_parts[-2] || ''
|
26
|
+
end
|
27
|
+
|
28
|
+
def repository
|
29
|
+
@repo ||= origin_url_parts[-1].sub(/\.git$/, '') || ''
|
30
|
+
end
|
31
|
+
|
32
|
+
def abort_for_non_github_repo
|
33
|
+
abort 'This does not appear to be a GitHub repository.'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|