twig 1.4 → 1.5

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/HISTORY.md CHANGED
@@ -1,6 +1,21 @@
1
1
  Twig
2
2
  ====
3
3
 
4
+ 1.5 (2013-11-21)
5
+ ----------------
6
+ * ENHANCEMENT: Add `--format=json` option for printing branch data as JSON
7
+ instead of a list. Useful for integrating Twig data into other tools.
8
+ * ENHANCEMENT: Add tab completion for all subcommands, built-in (e.g., `twig
9
+ diff`, `twig gh-update`) and custom.
10
+ * ENHANCEMENT: Paginate help content where possible.
11
+ * ENHANCEMENT: Improve error messages `~/.twigconfig` isn't readable or contains
12
+ invalid lines.
13
+ * ENHANCEMENT: Include default option values for branch listing and GitHub
14
+ integration in `twig --help`. (GH-30, GH-31)
15
+ * FIX: Fix warnings when listing branches when a branch has UTF-8 characters in
16
+ its name. (GH-20)
17
+ * FIX: Fix showing relative time for very old branches (e.g., "2y ago"). (GH-29)
18
+
4
19
  1.4 (2013-08-07)
5
20
  ----------------
6
21
  * ENHANCEMENT: Speed up listing branches by 3–4x.
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  Twig: Your personal Git branch assistant.
2
2
  =========================================
3
3
 
4
+ [![Gem Version](https://badge.fury.io/rb/twig.png)](http://badge.fury.io/rb/twig)
5
+ [![Build Status](https://travis-ci.org/rondevera/twig.png?branch=development)](https://travis-ci.org/rondevera/twig)
6
+
4
7
  It's hard enough trying to remember the names of all of your Git branches. You
5
8
  also need those branches' issue tracker ids, issue statuses, and reminders of
6
9
  what to do next with each branch. `git branch` only lists them in alphabetical
@@ -76,10 +79,10 @@ them by age, name, and custom properties:
76
79
  * `twig --all`:
77
80
  List all branches regardless of other filtering options
78
81
 
79
- You can put your most frequently used options into `~/.twigrc`, and they'll be
80
- automatically included when you run `twig`. Example:
82
+ You can put your most frequently used options into `~/.twigconfig`, and they'll
83
+ be automatically included when you run `twig`. Example:
81
84
 
82
- # ~/.twigrc:
85
+ # ~/.twigconfig:
83
86
  except-branch: staging
84
87
  header-style: green bold
85
88
  max-days-old: 30
@@ -221,6 +224,9 @@ open a browser window if possible:
221
224
  $ twig gh-open
222
225
  GitHub URL: https://github.com/myname/myproject
223
226
 
227
+ For GitHub Enterprise or other installations, you can change
228
+ `https://github.com` by setting `github-uri-prefix` in `~/.twigrc`.
229
+
224
230
 
225
231
  twig gh-open-issue
226
232
  ------------------
@@ -236,6 +242,9 @@ subcommand to view that issue on GitHub:
236
242
  $ twig gh-open-issue -b <branch name>
237
243
  GitHub issue URL: https://github.com/myname/myproject/issues/222
238
244
 
245
+ For GitHub Enterprise or other installations, you can change
246
+ `https://github.com` by setting `github-uri-prefix` in `~/.twigrc`.
247
+
239
248
 
240
249
  twig gh-update
241
250
  --------------
@@ -272,6 +281,10 @@ subcommand syncs issue statuses with GitHub:
272
281
 
273
282
  Run `twig gh-update` periodically to keep up with GitHub issues locally.
274
283
 
284
+ For GitHub Enterprise or other installations, you can change the default
285
+ `https://api.github.com` endpoint prefix by setting `github-api-uri-prefix` in
286
+ `~/.twigrc`.
287
+
275
288
 
276
289
  Writing a subcommand
277
290
  --------------------
@@ -309,7 +322,7 @@ add it to the [Twig wiki][wiki]!
309
322
  More info
310
323
  =========
311
324
 
312
- * **Requirements:** Tested with Git 1.6.5 and Ruby 1.8.7/1.9.2/1.9.3. Probably
325
+ * **Requirements:** Tested with Git 1.6.5+ and Ruby 1.8.7/1.9.2/1.9.3. Probably
313
326
  works with older software, but it's not guaranteed.
314
327
  * **Contributing:** Found a bug or have a suggestion? [Please open an
315
328
  issue][issues] or ping [@ronalddevera on Twitter][twitter]. If you want to
@@ -14,7 +14,7 @@ Twig::GithubRepo.new do |gh_repo|
14
14
  twig.read_config_file!
15
15
  twig.read_cli_options!(ARGV)
16
16
 
17
- gh_uri_prefix = twig.options[:github_uri_prefix] || 'https://github.com'
17
+ gh_uri_prefix = twig.options[:github_uri_prefix]
18
18
  url = "#{gh_uri_prefix}/#{gh_repo.username}/#{gh_repo.repository}"
19
19
 
20
20
  puts "GitHub URL: #{url}"
@@ -27,7 +27,7 @@ Twig::GithubRepo.new do |gh_repo|
27
27
  abort %{The branch "#{branch_name}" doesn't have an "issue" property.}
28
28
  end
29
29
 
30
- gh_uri_prefix = twig.options[:github_uri_prefix] || 'https://github.com'
30
+ gh_uri_prefix = twig.options[:github_uri_prefix]
31
31
  url = "#{gh_uri_prefix}/#{gh_repo.username}/#{gh_repo.repository}"
32
32
  url << "/issues/#{issue_id}"
33
33
 
@@ -20,7 +20,7 @@ Twig::GithubRepo.new do |gh_repo|
20
20
  print 'Getting latest states for GitHub issues...'
21
21
 
22
22
  issues = {}
23
- issues_uri_prefix = twig.options[:github_api_uri_prefix] || 'https://api.github.com'
23
+ issues_uri_prefix = twig.options[:github_api_uri_prefix]
24
24
  issues_uri_base =
25
25
  "#{issues_uri_prefix}/repos/#{gh_repo.username}/#{gh_repo.repository}/issues"
26
26
  issues_uris = [
@@ -5,4 +5,4 @@
5
5
  # Subcommand for Twig: <http://rondevera.github.io/twig/>
6
6
  # Author: Ron DeVera <http://rondevera.com>
7
7
 
8
- puts `twig --help`
8
+ exec('twig --help')
@@ -7,19 +7,20 @@
7
7
  # Author: Ron DeVera <http://rondevera.com>
8
8
 
9
9
  require 'fileutils'
10
+ require 'rubygems'
11
+ require 'twig'
10
12
 
11
- version = `twig --version`.strip
12
13
  script = <<-SCRIPT
13
14
 
14
15
  #!/usr/bin/env bash
15
16
 
16
- # AUTO-GENERATED with Twig v#{version}. Regenerate with
17
+ # AUTO-GENERATED with Twig v#{Twig::VERSION}. Regenerate with
17
18
  # `twig init-completion --force`.
18
19
  #
19
20
  # Initializes bash tab completion for Twig. To use this, run
20
21
  # `twig init-completion` and follow the instructions.
21
22
  #
22
- # Twig: <http://rondevera.github.io/twig/>
23
+ # Twig: <#{Twig::HOMEPAGE}>
23
24
  # Author: Ron DeVera <http://rondevera.com>
24
25
 
25
26
  __twig_branches() {
@@ -30,6 +31,22 @@ __twig_branches() {
30
31
  return 0
31
32
  }
32
33
 
34
+ __twig_formats() {
35
+ local current words
36
+ current="${COMP_WORDS[COMP_CWORD]}"
37
+ words="json"
38
+ COMPREPLY=($(compgen -W "$words" -- "$current"))
39
+ return 0
40
+ }
41
+
42
+ __twig_subcommands() {
43
+ local current words
44
+ current="${COMP_WORDS[COMP_CWORD]}"
45
+ words="#{Twig::Subcommands.all_names.join(' ')}"
46
+ COMPREPLY=($(compgen -W "$words" -- "$current"))
47
+ return 0
48
+ }
49
+
33
50
  __twig() {
34
51
  if [ -z "$(git rev-parse HEAD 2>/dev/null)" ]; then
35
52
  return 0;
@@ -39,9 +56,11 @@ __twig() {
39
56
 
40
57
  case "${previous}" in
41
58
  -b|--branch) __twig_branches ;;
59
+ --format) __twig_formats ;;
42
60
  diff) __twig_branches ;;
43
61
  diff-branch) __twig_branches ;;
44
62
  rebase) __twig_branches ;;
63
+ twig) __twig_subcommands ;;
45
64
  esac
46
65
 
47
66
  return 0
@@ -35,7 +35,7 @@ rebase_options = args.join(' ') # Pass remaining options to `git-rebase`
35
35
  abort if base_branch.empty?
36
36
 
37
37
  print %{Rebase "#{topic_branch}" onto "#{base_branch}"? (y/n) }
38
- input = STDIN.gets.strip.downcase
38
+ input = $stdin.gets.strip.downcase
39
39
  if input == 'y' || input == 'yes'
40
40
  exec %{git rebase #{rebase_options} "#{base_branch}" "#{topic_branch}"}
41
41
  else
@@ -1,6 +1,7 @@
1
1
  Dir[File.join(File.dirname(__FILE__), 'twig', '*.rb')].each { |file| require file }
2
2
  require 'time'
3
3
 
4
+ # The main class.
4
5
  class Twig
5
6
  include Cli
6
7
  include Display
@@ -8,11 +9,13 @@ class Twig
8
9
 
9
10
  attr_accessor :options
10
11
 
11
- REF_FORMAT_SEPARATOR = ','
12
+ DEFAULT_GITHUB_API_URI_PREFIX = 'https://api.github.com'
13
+ DEFAULT_GITHUB_URI_PREFIX = 'https://github.com'
14
+ DEFAULT_HEADER_COLOR = :blue
15
+ REF_FORMAT_SEPARATOR = '|'
12
16
  REF_FORMAT = %w[refname:short committerdate committerdate:relative].
13
17
  map { |field| '%(' + field + ')' }.join(REF_FORMAT_SEPARATOR)
14
18
  REF_PREFIX = 'refs/heads/'
15
- DEFAULT_HEADER_COLOR = :blue
16
19
 
17
20
  def self.run(command)
18
21
  `#{command}`.strip
@@ -27,6 +30,8 @@ class Twig
27
30
  self.options = {}
28
31
 
29
32
  # Set defaults
33
+ set_option(:github_api_uri_prefix, DEFAULT_GITHUB_API_URI_PREFIX)
34
+ set_option(:github_uri_prefix, DEFAULT_GITHUB_URI_PREFIX)
30
35
  set_option(:header_style, DEFAULT_HEADER_COLOR.to_s)
31
36
  end
32
37
 
@@ -53,18 +58,21 @@ class Twig
53
58
  def branches
54
59
  branches = all_branches
55
60
  now = Time.now
56
- max_seconds_old = options[:max_days_old] * 86400 if options[:max_days_old]
61
+ max_days_old = options[:max_days_old]
62
+ max_seconds_old = max_days_old * 86400 if max_days_old
57
63
 
58
- branches.select do |branch|
64
+ branches = branches.select do |branch|
59
65
  catch :skip_branch do
60
66
  if max_seconds_old
61
67
  seconds_old = now.to_i - branch.last_commit_time.to_i
62
68
  next if seconds_old > max_seconds_old
63
69
  end
64
70
 
71
+ branch_name = branch.name
72
+
65
73
  (options[:property_except] || {}).each do |property_name, property_value|
66
74
  if property_name == :branch
67
- throw :skip_branch if branch.name =~ property_value
75
+ throw :skip_branch if branch_name =~ property_value
68
76
  elsif branch.get_property(property_name.to_s) =~ property_value
69
77
  throw :skip_branch
70
78
  end
@@ -72,7 +80,7 @@ class Twig
72
80
 
73
81
  (options[:property_only] || {}).each do |property_name, property_value|
74
82
  if property_name == :branch
75
- throw :skip_branch if branch.name !~ property_value
83
+ throw :skip_branch if branch_name !~ property_value
76
84
  elsif branch.get_property(property_name.to_s) !~ property_value
77
85
  throw :skip_branch
78
86
  end
@@ -81,6 +89,14 @@ class Twig
81
89
  true
82
90
  end
83
91
  end
92
+
93
+ # List least recently modified branches first
94
+ branches = branches.sort_by { |branch| branch.last_commit_time }
95
+ if options[:reverse] != true
96
+ branches.reverse! # List most recently modified branches first
97
+ end
98
+
99
+ branches
84
100
  end
85
101
 
86
102
  def all_branch_names
@@ -102,13 +118,7 @@ class Twig
102
118
 
103
119
  out = "\n" << branch_list_headers(options)
104
120
 
105
- # List least recently modified branches first
106
- listable_branches = branches.sort_by { |branch| branch.last_commit_time }
107
- if options[:reverse] != true
108
- listable_branches.reverse! # List most recently modified branches first
109
- end
110
-
111
- branch_lines = listable_branches.inject([]) do |result, branch|
121
+ branch_lines = branches.inject([]) do |result, branch|
112
122
  result << branch_list_line(branch)
113
123
  end
114
124
 
@@ -1,4 +1,6 @@
1
1
  class Twig
2
+
3
+ # Represents a Git branch.
2
4
  class Branch
3
5
 
4
6
  EMPTY_PROPERTY_NAME_ERROR = 'Branch property names cannot be empty strings.'
@@ -22,8 +24,8 @@ class Twig
22
24
  properties = config_lines.map do |line|
23
25
  # Split by rightmost `=`, allowing branch names to contain `=`:
24
26
  key = value = nil
25
- line.match(/(.+)=(.+)/).tap { |m| key, value = m[1..2] if m }
26
- next if key.nil?
27
+ line.match(/(.+)=(.+)/).tap { |md| key, value = md[1..2] if md }
28
+ next unless key
27
29
 
28
30
  key_parts = key.split('.')
29
31
  key_parts.last if key_parts[0] == 'branch' && key_parts.size > 2
@@ -42,27 +44,40 @@ class Twig
42
44
 
43
45
  def to_s ; name ; end
44
46
 
47
+ def to_hash
48
+ all_property_names = Twig::Branch.all_property_names
49
+
50
+ {
51
+ 'name' => name,
52
+ 'last-commit-time' => last_commit_time.iso8601,
53
+ 'properties' => get_properties(all_property_names)
54
+ }
55
+ end
56
+
45
57
  def sanitize_property(property_name)
46
58
  property_name.gsub(/[ _]+/, '')
47
59
  end
48
60
 
49
- def get_properties(property_names)
50
- return {} if property_names.empty?
51
-
52
- property_name_regexps = property_names.map do |property_name|
61
+ def escaped_property_names(property_names)
62
+ property_names.map do |property_name|
53
63
  property_name = sanitize_property(property_name)
54
64
  raise EmptyPropertyNameError if property_name.empty?
55
65
  Regexp.escape(property_name)
56
- end.join('|')
66
+ end
67
+ end
68
+
69
+ def get_properties(property_names)
70
+ return {} if property_names.empty?
57
71
 
58
- git_config_regexp = "branch\.#{name}\.(#{ property_name_regexps })$"
72
+ property_names_regexp = escaped_property_names(property_names).join('|')
73
+ git_config_regexp = "branch\.#{name}\.(#{ property_names_regexp })$"
59
74
  cmd = %{git config --get-regexp "#{git_config_regexp}"}
60
75
 
61
76
  git_result = Twig.run(cmd) || ''
62
77
  git_result_lines = git_result.split("\n")
63
78
 
64
79
  git_result_lines.inject({}) do |properties, line|
65
- match_data = line.match(/^branch\.#{name}\.([^\s]+)\s+(.*)$/)
80
+ match_data = line.match(/^branch\.#{Regexp.escape(name)}\.([^\s]+)\s+(.*)$/)
66
81
 
67
82
  if match_data
68
83
  property_name = match_data[1]
@@ -1,4 +1,5 @@
1
1
  require 'optparse'
2
+ require 'rbconfig'
2
3
 
3
4
  class Twig
4
5
  module Cli
@@ -25,24 +26,25 @@ class Twig
25
26
  intro + ' ' # Force extra blank line
26
27
  end
27
28
 
28
- def help_separator(option_parser, text, options={})
29
+ def help_separator(option_parser, text, options = {})
29
30
  options[:trailing] ||= "\n\n"
30
31
  option_parser.separator "\n#{text}#{options[:trailing]}"
31
32
  end
32
33
 
33
- def help_description(text, options={})
34
+ def help_description(text, options = {})
34
35
  width = options[:width] || 40
35
36
  words = text.gsub(/\n?\s+/, ' ').strip.split(' ')
36
37
  lines = []
37
38
 
38
39
  # Split words into lines
39
40
  while words.any?
40
- current_word = words.shift
41
+ current_word = words.shift
41
42
  current_word_size = unformat_string(current_word).size
42
- last_line_size = lines.last && unformat_string(lines.last).size
43
+ last_line = lines.last
44
+ last_line_size = last_line && unformat_string(last_line).size
43
45
 
44
46
  if last_line_size && (last_line_size + current_word_size + 1 <= width)
45
- lines.last << ' ' << current_word
47
+ last_line << ' ' << current_word
46
48
  elsif current_word_size >= width
47
49
  lines << current_word[0...width]
48
50
  words.unshift(current_word[width..-1])
@@ -55,7 +57,8 @@ class Twig
55
57
  lines
56
58
  end
57
59
 
58
- def help_description_for_custom_property(option_parser, desc_lines)
60
+ def help_description_for_custom_property(option_parser, desc_lines, options = {})
61
+ options[:trailing] ||= "\n"
59
62
  indent = ' '
60
63
  left_column_width = 29
61
64
 
@@ -64,7 +67,7 @@ class Twig
64
67
  sprintf("%-#{left_column_width}s", left_column) + right_column + "\n"
65
68
  end
66
69
 
67
- help_separator(option_parser, help_desc, :trailing => "\n")
70
+ help_separator(option_parser, help_desc, :trailing => options[:trailing])
68
71
  end
69
72
 
70
73
  def help_paragraph(text)
@@ -93,6 +96,36 @@ class Twig
93
96
  is_custom_property_width
94
97
  end
95
98
 
99
+ def run_pager
100
+ # Starts a pager so that all following STDOUT output is paginated.
101
+ # Based on: http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby
102
+
103
+ return if Twig::System.windows? || !$stdout.tty?
104
+
105
+ read_io, write_io = IO.pipe
106
+
107
+ # Create child process
108
+ unless Kernel.fork
109
+ # The following runs only in the child process:
110
+ $stdout.reopen(write_io)
111
+ $stderr.reopen(write_io) if $stderr.tty?
112
+ read_io.close
113
+ write_io.close
114
+ return
115
+ end
116
+
117
+ $stdin.reopen(read_io)
118
+ read_io.close
119
+ write_io.close
120
+
121
+ ENV['LESS'] = 'FSRX' # Don't page if the input fits on screen
122
+ Kernel.select([$stdin]) # Wait for input before starting pager
123
+
124
+ # Turn parent process into pager
125
+ pager = ENV['PAGER'] || 'less'
126
+ exec pager rescue exec '/bin/sh', '-c', pager
127
+ end
128
+
96
129
  def read_cli_options!(args)
97
130
  custom_properties = Twig::Branch.all_property_names
98
131
 
@@ -120,6 +153,7 @@ class Twig
120
153
  desc = 'Show this help content.'
121
154
  opts.on('--help', *help_description(desc)) do
122
155
  summary_lines = opts.to_s.split("\n")
156
+ run_pager
123
157
 
124
158
  # Filter out custom property lines
125
159
  prev_line = nil
@@ -166,23 +200,34 @@ class Twig
166
200
  end
167
201
 
168
202
  custom_properties.each do |property_name|
203
+ property_name_sym = property_name.to_sym
204
+
169
205
  opts.on("--only-#{property_name} PATTERN") do |pattern|
170
- set_option(:property_only, property_name.to_sym => pattern)
206
+ set_option(:property_only, property_name_sym => pattern)
171
207
  end
172
208
 
173
209
  opts.on("--except-#{property_name} PATTERN") do |pattern|
174
- set_option(:property_except, property_name.to_sym => pattern)
210
+ set_option(:property_except, property_name_sym => pattern)
175
211
  end
176
212
  end
177
213
  help_description_for_custom_property(opts, [
178
214
  ['--only-PROPERTY PATTERN', 'Only list branches with a given property'],
179
215
  ['', 'that matches a given pattern.'],
180
- ])
216
+ ], :trailing => '')
181
217
  help_description_for_custom_property(opts, [
182
218
  ['--except-PROPERTY PATTERN', 'Do not list branches with a given property'],
183
219
  ['', 'that matches a given pattern.']
184
220
  ])
185
221
 
222
+ desc =
223
+ 'Print branch properties in a format that can be used by other ' +
224
+ 'tools. Currently, the only supported value is `json`.'
225
+ opts.on(
226
+ '--format FORMAT', *help_description(desc, :add_separator => true)
227
+ ) do |format|
228
+ set_option(:format, format)
229
+ end
230
+
186
231
  desc =
187
232
  'Lists all branches regardless of other filtering options. ' +
188
233
  'Useful for overriding options in ' +
@@ -197,7 +242,10 @@ class Twig
197
242
 
198
243
  help_separator(opts, 'Listing branches:')
199
244
 
200
- desc = 'Set the width for the `branch` column.'
245
+ desc = <<-DESC
246
+ Set the width for the `branch` column.
247
+ (Default: #{Twig::DEFAULT_BRANCH_COLUMN_WIDTH})
248
+ DESC
201
249
  opts.on('--branch-width NUMBER', *help_description(desc)) do |width|
202
250
  set_option(:property_width, :branch => width)
203
251
  end
@@ -208,7 +256,8 @@ class Twig
208
256
  end
209
257
  end
210
258
  help_description_for_custom_property(opts, [
211
- ['--PROPERTY-width NUMBER', "Set the width for a given property's column."]
259
+ ['--PROPERTY-width NUMBER', "Set the width for a given property's column."],
260
+ ['', "(Default: #{Twig::DEFAULT_PROPERTY_COLUMN_WIDTH})"]
212
261
  ])
213
262
 
214
263
  colors = Twig::Display::COLORS.keys.map do |value|
@@ -217,14 +266,14 @@ class Twig
217
266
  weights = Twig::Display::WEIGHTS.keys.map do |value|
218
267
  format_string(value, :weight => value)
219
268
  end.join(' and ')
220
- default_color = format_string(
269
+ default_header_style = format_string(
221
270
  Twig::DEFAULT_HEADER_COLOR.to_s,
222
271
  :color => Twig::DEFAULT_HEADER_COLOR
223
272
  )
224
273
  desc = <<-DESC
225
274
  STYLE is a color, weight, or a space-separated pair of one of each.
226
275
  Valid colors are #{colors}. Valid weights are #{weights}.
227
- The default is "#{default_color}".
276
+ (Default: "#{default_header_style}")
228
277
  DESC
229
278
  opts.on(
230
279
  '--header-style "STYLE"',
@@ -233,7 +282,7 @@ class Twig
233
282
  set_option(:header_style, style)
234
283
  end
235
284
 
236
- desc = 'Show oldest branches first.'
285
+ desc = 'Show oldest branches first. (Default: false)'
237
286
  opts.on('--reverse', *help_description(desc)) do
238
287
  set_option(:reverse, true)
239
288
  end
@@ -245,6 +294,7 @@ class Twig
245
294
  desc = <<-DESC
246
295
  Set a custom GitHub API URI prefix, e.g.,
247
296
  https://github-enterprise.example.com/api/v3.
297
+ (Default: "#{Twig::DEFAULT_GITHUB_API_URI_PREFIX}")
248
298
  DESC
249
299
  opts.on(
250
300
  '--github-api-uri-prefix PREFIX',
@@ -256,6 +306,7 @@ class Twig
256
306
  desc = <<-DESC
257
307
  Set a custom GitHub URI prefix, e.g.,
258
308
  https://github-enterprise.example.com.
309
+ (Default: "#{Twig::DEFAULT_GITHUB_URI_PREFIX}")
259
310
  DESC
260
311
  opts.on(
261
312
  '--github-uri-prefix PREFIX',
@@ -287,6 +338,8 @@ class Twig
287
338
  option_parser.parse!(args)
288
339
  rescue OptionParser::InvalidOption, OptionParser::MissingArgument => exception
289
340
  abort_for_option_exception(exception)
341
+ ensure
342
+ args
290
343
  end
291
344
 
292
345
  def abort_for_option_exception(exception)
@@ -295,19 +348,22 @@ class Twig
295
348
  exit
296
349
  end
297
350
 
298
- def read_cli_args!(args)
299
- if args.any?
300
- # Run subcommand binary, if any, and exit here
301
- possible_subcommand_name = args[0]
302
- command_path = Twig.run("which twig-#{possible_subcommand_name} 2>/dev/null")
303
- unless command_path.empty?
304
- command = ([command_path] + args[1..-1]).join(' ')
305
- exec(command)
306
- end
351
+ def exec_subcommand_if_any(args)
352
+ # Run subcommand binary, if any, and exit here
353
+ possible_subcommand_name = Twig::Subcommands::BIN_PREFIX + args[0]
354
+ command_path = Twig.run("which #{possible_subcommand_name} 2>/dev/null")
355
+ unless command_path.empty?
356
+ command = ([command_path] + args[1..-1]).join(' ')
357
+ exec(command)
307
358
  end
359
+ end
360
+
361
+ def read_cli_args!(args)
362
+ exec_subcommand_if_any(args) if args.any?
308
363
 
309
- read_cli_options!(args)
364
+ args = read_cli_options!(args)
310
365
  branch_name = options[:branch] || current_branch_name
366
+ format = options.delete(:format)
311
367
  property_to_unset = options.delete(:unset_property)
312
368
 
313
369
  # Handle remaining arguments, if any
@@ -327,6 +383,9 @@ class Twig
327
383
  elsif property_to_unset
328
384
  # `$ twig --unset <key>`
329
385
  unset_branch_property_for_cli(branch_name, property_to_unset)
386
+ elsif format == :json
387
+ # `$ twig --format json`
388
+ puts branches_json
330
389
  else
331
390
  # `$ twig`
332
391
  puts list_branches