twig 1.4 → 1.5

Sign up to get free protection for your applications and to get access to all the features.
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