twig 1.5 → 1.6

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.
@@ -1,12 +1,22 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Usage:
3
+ # Synopsis:
4
4
  #
5
- # - `twig gh-open-issue`:
6
- # Opens a browser window for the GitHub issue, if any, for the current branch.
5
+ # twig gh-open-issue [-b|--branch <branch>]
7
6
  #
8
- # - `twig gh-open-issue -b <branch>`:
9
- # Opens the GitHub issue, if any, for the specified branch.
7
+ # Description:
8
+ #
9
+ # Opens a browser window the GitHub issue, if any, for the current branch.
10
+ #
11
+ # To customize the GitHub URI prefix (e.g., for GitHub Enterprise
12
+ # installations), set GitHub options in `~/.twigconfig`:
13
+ #
14
+ # github-uri-prefix: http://example-enterprise.github.com
15
+ # github-api-uri-prefix: http://example-enterprise.github.com
16
+ #
17
+ # Options:
18
+ #
19
+ # `-b` or `--branch`: Opens the GitHub issue, if any, for the given branch.
10
20
  #
11
21
  # Subcommand for Twig: <http://rondevera.github.io/twig/>
12
22
  # Author: Ron DeVera <http://rondevera.com>
@@ -16,15 +26,12 @@ require 'twig'
16
26
  require 'launchy'
17
27
 
18
28
  Twig::GithubRepo.new do |gh_repo|
19
- twig = Twig.new
20
- twig.read_config_file!
21
- twig.read_cli_options!(ARGV)
22
-
23
- branch_name = twig.options[:branch] || twig.current_branch_name
24
- issue_id = twig.get_branch_property(branch_name, 'issue')
29
+ twig = Twig.new(:read_options => true)
30
+ target_branch_name = twig.target_branch_name
31
+ issue_id = twig.get_branch_property(target_branch_name, 'issue')
25
32
 
26
33
  unless issue_id
27
- abort %{The branch "#{branch_name}" doesn't have an "issue" property.}
34
+ abort %{The branch "#{target_branch_name}" doesn't have an "issue" property.}
28
35
  end
29
36
 
30
37
  gh_uri_prefix = twig.options[:github_uri_prefix]
@@ -1,6 +1,18 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Updates each branch with the latest issue status on GitHub.
3
+ # Synopsis:
4
+ #
5
+ # twig gh-update
6
+ #
7
+ # Description:
8
+ #
9
+ # Updates each branch with the latest issue status on GitHub.
10
+ #
11
+ # To customize the GitHub URI prefix (e.g., for GitHub Enterprise
12
+ # installations), set GitHub options in `~/.twigconfig`:
13
+ #
14
+ # github-uri-prefix: http://example-enterprise.github.com
15
+ # github-api-uri-prefix: http://example-enterprise.github.com
4
16
  #
5
17
  # Subcommand for Twig: <http://rondevera.github.io/twig/>
6
18
  # Author: Ron DeVera <http://rondevera.com>
@@ -11,9 +23,7 @@ require 'json'
11
23
  require 'net/https'
12
24
  require 'uri'
13
25
 
14
- twig = Twig.new
15
- twig.read_config_file!
16
- twig.read_cli_options!(ARGV)
26
+ twig = Twig.new(:read_options => true)
17
27
 
18
28
  Twig::GithubRepo.new do |gh_repo|
19
29
  $stdout.sync = true
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Synopsis:
4
+ #
5
+ # twig init
6
+ #
7
+ # Description:
8
+ #
9
+ # Runs all initialization commands. This is only needed once after installing
10
+ # Twig, and affects all Git repositories.
11
+ #
12
+ # Subcommand for Twig: <http://rondevera.github.io/twig/>
13
+ # Author: Ron DeVera <http://rondevera.com>
14
+
15
+ exec('twig init-completion')
@@ -1,7 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Initializes tab completion for Twig. To use this, run
4
- # `twig init-completion` and follow the instructions.
3
+ # Synopsis:
4
+ #
5
+ # twig init-completion [--force]
6
+ #
7
+ # Description:
8
+ #
9
+ # Initializes tab completion for Twig. Instead of running this directly,
10
+ # run `twig init` to run all setup tasks.
11
+ #
12
+ # Options:
13
+ #
14
+ # `--force`: By default, `twig init-completion` preserves the existing tab
15
+ # completion script, if any. If this option is used, the command will
16
+ # overwrite any existing tab completion script.
5
17
  #
6
18
  # Subcommand for Twig: <http://rondevera.github.io/twig/>
7
19
  # Author: Ron DeVera <http://rondevera.com>
@@ -1,7 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Initializes bash tab completion for Twig. To use this, run
4
- # `twig init-completion` and follow the instructions.
3
+ # Synopsis:
4
+ #
5
+ # twig init-completion-bash
6
+ #
7
+ # Description:
8
+ #
9
+ # Initializes bash tab completion for Twig. Instead of running this directly,
10
+ # run `twig init` to run all setup tasks for the current shell. Uses all
11
+ # options available for `twig init-completion`.
5
12
  #
6
13
  # Subcommand for Twig: <http://rondevera.github.io/twig/>
7
14
  # Author: Ron DeVera <http://rondevera.com>
@@ -84,7 +91,8 @@ full_script_path = File.expand_path(script_path)
84
91
  script_exists = File.exists?(full_script_path)
85
92
 
86
93
  if script_exists && !force
87
- puts "The file `#{script_path}` already exists."
94
+ twig = Twig.new
95
+ puts twig.format_string("The file `#{script_path}` already exists.", :color => :red)
88
96
  puts "To overwrite it, run `twig init-completion --force`."
89
97
  else
90
98
  File.open(full_script_path, 'w') do |file|
@@ -1,24 +1,27 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Summary:
3
+ # Synopsis:
4
4
  #
5
- # `twig rebase [<branch>] [<options>]`
5
+ # twig rebase [<branch>] [<options>]
6
6
  #
7
- # Usage:
7
+ # Description:
8
8
  #
9
- # - `twig rebase`:
10
9
  # Rebases the current branch onto the branch in its `diff-branch` property.
10
+ # All options are passed through to `git-rebase`.
11
11
  #
12
- # - `twig rebase <options>`:
13
- # Rebases the current branch onto its `diff-branch`, and passes options
14
- # through to `git-rebase`, e.g., `twig rebase -i`.
12
+ # Examples:
15
13
  #
16
- # - `twig rebase <branch>`:
17
- # Rebases the given branch onto its `diff-branch`.
14
+ # Rebase the current branch onto its `diff-branch` interactively:
18
15
  #
19
- # - `twig rebase <branch> <options>`:
20
- # Rebases the given branch and its `diff-branch`, and passes options
21
- # through to `git-rebase`, e.g., `twig rebase my_branch -i`.
16
+ # twig rebase -i
17
+ #
18
+ # Rebase the given branch onto its `diff-branch`:
19
+ #
20
+ # twig rebase my_branch
21
+ #
22
+ # Rebase the given branch onto its `diff-branch` interactively:
23
+ #
24
+ # twig rebase my_branch -i
22
25
  #
23
26
  # Subcommand for Twig: <http://rondevera.github.io/twig/>
24
27
  # Author: Ron DeVera <http://rondevera.com>
@@ -1,4 +1,15 @@
1
- Dir[File.join(File.dirname(__FILE__), 'twig', '*.rb')].each { |file| require file }
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__)) # For gem development
2
+ require 'twig/branch'
3
+ require 'twig/cli'
4
+ require 'twig/commit_time'
5
+ require 'twig/display'
6
+ require 'twig/github'
7
+ require 'twig/homepage'
8
+ require 'twig/options'
9
+ require 'twig/subcommands'
10
+ require 'twig/system'
11
+ require 'twig/util'
12
+ require 'twig/version'
2
13
  require 'time'
3
14
 
4
15
  # The main class.
@@ -26,37 +37,32 @@ class Twig
26
37
  $?.success?
27
38
  end
28
39
 
29
- def initialize
40
+ def initialize(options = {})
30
41
  self.options = {}
31
42
 
32
43
  # Set defaults
33
44
  set_option(:github_api_uri_prefix, DEFAULT_GITHUB_API_URI_PREFIX)
34
45
  set_option(:github_uri_prefix, DEFAULT_GITHUB_URI_PREFIX)
35
46
  set_option(:header_style, DEFAULT_HEADER_COLOR.to_s)
47
+
48
+ if options[:read_options]
49
+ read_config_file!
50
+ read_cli_options!(ARGV)
51
+ end
36
52
  end
37
53
 
38
54
  def current_branch_name
55
+ # Returns the name of the branch that is currently checked out in Git.
39
56
  @_current_branch_name ||= Twig.run('git rev-parse --abbrev-ref HEAD')
40
57
  end
41
58
 
42
- def all_branches
43
- @_all_branches ||= begin
44
- branch_tuples = Twig.
45
- run(%{git for-each-ref #{ REF_PREFIX } --format="#{ REF_FORMAT }"}).
46
- split("\n")
47
-
48
- branch_tuples.inject([]) do |result, branch_tuple|
49
- ref, time_string, time_ago = branch_tuple.split(REF_FORMAT_SEPARATOR)
50
- time = Time.parse(time_string)
51
- commit_time = Twig::CommitTime.new(time, time_ago)
52
- branch = Branch.new(ref, :last_commit_time => commit_time)
53
- result << branch
54
- end
55
- end
59
+ def target_branch_name
60
+ # Returns the name of the branch to work on, e.g., for setting a property.
61
+ options[:branch] || current_branch_name
56
62
  end
57
63
 
58
64
  def branches
59
- branches = all_branches
65
+ branches = Twig::Branch.all_branches
60
66
  now = Time.now
61
67
  max_days_old = options[:max_days_old]
62
68
  max_seconds_old = max_days_old * 86400 if max_days_old
@@ -99,8 +105,22 @@ class Twig
99
105
  branches
100
106
  end
101
107
 
102
- def all_branch_names
103
- all_branches.map { |branch| branch.name }
108
+ def property_names
109
+ @_property_names ||= begin
110
+ property_names = Twig::Branch.all_property_names
111
+ only_name = options[:property_only_name]
112
+ except_name = options[:property_except_name]
113
+
114
+ if only_name
115
+ property_names = property_names.select { |name| name =~ only_name }
116
+ end
117
+
118
+ if except_name
119
+ property_names = property_names.select { |name| name !~ except_name }
120
+ end
121
+
122
+ property_names
123
+ end
104
124
  end
105
125
 
106
126
 
@@ -109,7 +129,7 @@ class Twig
109
129
 
110
130
  def list_branches
111
131
  if branches.empty?
112
- if all_branches.any?
132
+ if Twig::Branch.all_branches.any?
113
133
  return 'There are no branches matching your selected options.'
114
134
  else
115
135
  return 'This repository has no branches.'
@@ -5,7 +5,7 @@ class Twig
5
5
 
6
6
  EMPTY_PROPERTY_NAME_ERROR = 'Branch property names cannot be empty strings.'
7
7
  PROPERTY_NAME_FROM_GIT_CONFIG = /^branch\.[^.]+\.([^=]+)=.*$/
8
- RESERVED_BRANCH_PROPERTY_NAMES = %w[branch merge rebase remote]
8
+ RESERVED_BRANCH_PROPERTY_NAMES = %w[branch merge property rebase remote]
9
9
 
10
10
  class EmptyPropertyNameError < ArgumentError
11
11
  def initialize(message = nil)
@@ -17,14 +17,34 @@ class Twig
17
17
 
18
18
  attr_accessor :name, :last_commit_time
19
19
 
20
+ def self.all_branches
21
+ @_all_branches ||= begin
22
+ branch_tuples = Twig.
23
+ run(%{git for-each-ref #{ REF_PREFIX } --format="#{ REF_FORMAT }"}).
24
+ split("\n")
25
+
26
+ branch_tuples.inject([]) do |result, branch_tuple|
27
+ name, time_string, time_ago = branch_tuple.split(REF_FORMAT_SEPARATOR)
28
+ time = Time.parse(time_string)
29
+ commit_time = Twig::CommitTime.new(time, time_ago)
30
+ branch = Branch.new(name, :last_commit_time => commit_time)
31
+ result << branch
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.all_branch_names
37
+ @_all_branch_names ||= self.all_branches.map { |branch| branch.name }
38
+ end
39
+
20
40
  def self.all_property_names
21
41
  @_all_property_names ||= begin
22
42
  config_lines = Twig.run('git config --list').split("\n")
23
43
 
24
44
  properties = config_lines.map do |line|
25
- # Split by rightmost `=`, allowing branch names to contain `=`:
26
- key = value = nil
27
- line.match(/(.+)=(.+)/).tap { |md| key, value = md[1..2] if md }
45
+ # Split by rightmost `=`, allowing branch names to contain `=`
46
+ match_data = line.match(/(.+)=(.+)/)
47
+ key, value = match_data[1..2] if match_data
28
48
  next unless key
29
49
 
30
50
  key_parts = key.split('.')
@@ -35,6 +55,10 @@ class Twig
35
55
  end
36
56
  end
37
57
 
58
+ def self.validate_property_name(property_name)
59
+ raise EmptyPropertyNameError if property_name.empty?
60
+ end
61
+
38
62
  def initialize(name, attrs = {})
39
63
  self.name = name
40
64
  raise ArgumentError, '`name` is required' if name.empty?
@@ -44,13 +68,11 @@ class Twig
44
68
 
45
69
  def to_s ; name ; end
46
70
 
47
- def to_hash
48
- all_property_names = Twig::Branch.all_property_names
49
-
71
+ def to_hash(property_names)
50
72
  {
51
73
  'name' => name,
52
74
  'last-commit-time' => last_commit_time.iso8601,
53
- 'properties' => get_properties(all_property_names)
75
+ 'properties' => get_properties(property_names)
54
76
  }
55
77
  end
56
78
 
@@ -61,7 +83,7 @@ class Twig
61
83
  def escaped_property_names(property_names)
62
84
  property_names.map do |property_name|
63
85
  property_name = sanitize_property(property_name)
64
- raise EmptyPropertyNameError if property_name.empty?
86
+ Branch.validate_property_name(property_name)
65
87
  Regexp.escape(property_name)
66
88
  end
67
89
  end
@@ -103,10 +125,9 @@ class Twig
103
125
  def set_property(property_name, value)
104
126
  property_name = sanitize_property(property_name)
105
127
  value = value.to_s.strip
128
+ Branch.validate_property_name(property_name)
106
129
 
107
- if property_name.empty?
108
- raise EmptyPropertyNameError
109
- elsif RESERVED_BRANCH_PROPERTY_NAMES.include?(property_name)
130
+ if RESERVED_BRANCH_PROPERTY_NAMES.include?(property_name)
110
131
  raise ArgumentError,
111
132
  %{Can't modify the reserved property "#{property_name}".}
112
133
  elsif value.empty?
@@ -126,7 +147,7 @@ class Twig
126
147
 
127
148
  def unset_property(property_name)
128
149
  property_name = sanitize_property(property_name)
129
- raise EmptyPropertyNameError if property_name.empty?
150
+ Branch.validate_property_name(property_name)
130
151
 
131
152
  value = get_property(property_name)
132
153
 
@@ -4,13 +4,33 @@ require 'rbconfig'
4
4
  class Twig
5
5
  module Cli
6
6
 
7
+ def self.prompt_with_choices(prompt, choices)
8
+ # Prints the given string `prompt` and the array `choices` numbered, and
9
+ # prompts the user to enter a number. Returns the matching value, or nil
10
+ # if the user input is invalid.
11
+
12
+ if choices.size < 2
13
+ raise ArgumentError, 'At least two choices required'
14
+ end
15
+
16
+ puts prompt
17
+ choices.each_with_index do |choice, index|
18
+ puts "#{sprintf('%3s', index + 1)}. #{choice}"
19
+ end
20
+ print '> '
21
+
22
+ input = $stdin.gets.to_i
23
+ choices[input - 1]
24
+ end
25
+
7
26
  def help_intro
8
27
  version_string = "Twig v#{Twig::VERSION}"
9
28
 
10
29
  intro = help_paragraph(%{
11
- Twig is your personal Git branch assistant. It shows you your most
12
- recent branches, and tracks issue tracker ids, tasks, and other metadata
13
- for your Git branches.
30
+ Twig is your personal Git branch assistant. It's a command-line tool for
31
+ listing your most recent branches, and for remembering branch details
32
+ for you, like issue tracker ids and todos. It also supports subcommands,
33
+ like automatically fetching statuses from your issue tracking system.
14
34
  })
15
35
 
16
36
  intro = <<-BANNER.gsub(/^[ ]+/, '')
@@ -78,11 +98,13 @@ class Twig
78
98
  is_custom_property_except = (
79
99
  line.include?('--except-') &&
80
100
  !line.include?('--except-branch') &&
101
+ !line.include?('--except-property') &&
81
102
  !line.include?('--except-PROPERTY')
82
103
  )
83
104
  is_custom_property_only = (
84
105
  line.include?('--only-') &&
85
106
  !line.include?('--only-branch') &&
107
+ !line.include?('--only-property') &&
86
108
  !line.include?('--only-PROPERTY')
87
109
  )
88
110
  is_custom_property_width = (
@@ -212,7 +234,7 @@ class Twig
212
234
  end
213
235
  help_description_for_custom_property(opts, [
214
236
  ['--only-PROPERTY PATTERN', 'Only list branches with a given property'],
215
- ['', 'that matches a given pattern.'],
237
+ ['', 'that matches a given pattern.']
216
238
  ], :trailing => '')
217
239
  help_description_for_custom_property(opts, [
218
240
  ['--except-PROPERTY PATTERN', 'Do not list branches with a given property'],
@@ -260,6 +282,28 @@ class Twig
260
282
  ['', "(Default: #{Twig::DEFAULT_PROPERTY_COLUMN_WIDTH})"]
261
283
  ])
262
284
 
285
+ desc = <<-DESC
286
+ Only include properties where the property name matches the given
287
+ regular expression.
288
+ DESC
289
+ opts.on(
290
+ '--only-property PATTERN',
291
+ *help_description(desc, :add_separator => true)
292
+ ) do |pattern|
293
+ set_option(:property_only_name, pattern)
294
+ end
295
+
296
+ desc = <<-DESC
297
+ Exclude properties where the property name matches the given regular
298
+ expression.
299
+ DESC
300
+ opts.on(
301
+ '--except-property PATTERN',
302
+ *help_description(desc, :add_separator => true)
303
+ ) do |pattern|
304
+ set_option(:property_except_name, pattern)
305
+ end
306
+
263
307
  colors = Twig::Display::COLORS.keys.map do |value|
264
308
  format_string(value, :color => value)
265
309
  end.join(', ')
@@ -326,7 +370,7 @@ class Twig
326
370
  ' except-branch: staging',
327
371
  ' header-style: green bold',
328
372
  ' max-days-old: 30',
329
- ' reverse: true',
373
+ ' reverse: true'
330
374
  ].join("\n"), :trailing => '')
331
375
 
332
376
  help_separator(opts, help_paragraph(%{
@@ -343,9 +387,8 @@ class Twig
343
387
  end
344
388
 
345
389
  def abort_for_option_exception(exception)
346
- puts exception.message
347
- puts 'For a list of options, run `twig --help`.'
348
- exit
390
+ puts exception.message + "\nFor a list of options, run `twig --help`."
391
+ exit 1
349
392
  end
350
393
 
351
394
  def exec_subcommand_if_any(args)
@@ -362,7 +405,7 @@ class Twig
362
405
  exec_subcommand_if_any(args) if args.any?
363
406
 
364
407
  args = read_cli_options!(args)
365
- branch_name = options[:branch] || current_branch_name
408
+ branch_name = target_branch_name
366
409
  format = options.delete(:format)
367
410
  property_to_unset = options.delete(:unset_property)
368
411