Module: Rubyists::Linear::CLI::WhatFor

Included in:
SubCommands
Defined in:
lib/linear/cli/what_for.rb

Overview

Module for the _for methods

Constant Summary collapse

PR_TYPES =

TODO: Make this configurable

{
  fix: 'Bug fixes',
  feat: 'New feature work',
  chore: 'Chores and maintenance',
  eyes: 'Observability, metrics',
  test: 'Testing code',
  perf: 'Performance related work',
  refactor: 'Code refactoring',
  docs: 'Documentation Updates',
  sec: 'Security-related, including dependency updates',
  style: 'Style updates',
  ci: 'Continuous integration related',
  db: 'Database-Related (migrations, models, etc)'
}.freeze
PR_TYPE_SELECTIONS =
PR_TYPES.invert
ALLOWED_PR_TYPES =
/#{PR_TYPES.keys.join("|")}/

Instance Method Summary collapse

Instance Method Details

#ask_for_projects(projects, search: true) ⇒ Object



90
91
92
93
94
95
# File 'lib/linear/cli/what_for.rb', line 90

def ask_for_projects(projects, search: true)
  prompt.warn("No project found matching #{search}.") if search
  return projects.first if projects.size == 1

  prompt.select('Project:', projects.to_h { |p| [p.name, p] })
end

#ask_or_edit(thing, question) ⇒ Object

Raises:



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/linear/cli/what_for.rb', line 68

def ask_or_edit(thing, question)
  return thing if thing && thing != '-'

  answer = prompt.ask("#{question}: ('-' to open an editor)", default: '-')
  return answer unless answer == '-'

  answer = editor_for [question.downcase, '.md']
  raise SmellsBad, "No content provided for #{question}" if answer.empty?

  answer
end

#cancelled_state_for(thingy) ⇒ Object



52
53
54
55
56
57
58
# File 'lib/linear/cli/what_for.rb', line 52

def cancelled_state_for(thingy)
  states = thingy.cancelled_states
  return states.first if states.size == 1

  selection = prompt.select('Choose a cancelled state', states.to_h { |s| [s.name, s.id] })
  Rubyists::Linear::WorkflowState.find selection
end

#comment_for(issue, comment) ⇒ Object



37
38
39
# File 'lib/linear/cli/what_for.rb', line 37

def comment_for(issue, comment)
  ask_or_edit comment, "Comment for #{issue.identifier} - #{issue.title}"
end

#completed_state_for(thingy) ⇒ Object



60
61
62
63
64
65
66
# File 'lib/linear/cli/what_for.rb', line 60

def completed_state_for(thingy)
  states = thingy.completed_states
  return states.first if states.size == 1

  selection = prompt.select('Choose a completed state', states.to_h { |s| [s.name, s.id] })
  Rubyists::Linear::WorkflowState.find selection
end

#description_for(description = nil) ⇒ Object



80
81
82
# File 'lib/linear/cli/what_for.rb', line 80

def description_for(description = nil)
  ask_or_edit description, 'Description'
end

#editor_for(prefix) ⇒ Object



28
29
30
31
32
33
34
35
# File 'lib/linear/cli/what_for.rb', line 28

def editor_for(prefix)
  file = Tempfile.open(prefix, Rubyists::Linear.tmpdir)
  TTY::Editor.open(file.path)
  file.close
  File.readlines(file.path).map(&:chomp).join('\\n')
ensure
  file&.close
end

#labels_for(team, labels = nil) ⇒ Object



155
156
157
158
159
# File 'lib/linear/cli/what_for.rb', line 155

def labels_for(team, labels = nil)
  return Rubyists::Linear::Label.find_all_by_name(labels.map(&:strip)) if labels

  prompt.multi_select('Labels:', team.labels.to_h { |t| [t.name, t] })
end

#pr_description_for(issue) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/linear/cli/what_for.rb', line 124

def pr_description_for(issue)
  tmpfile = Tempfile.new([issue.identifier, '.md'], Rubyists::Linear.tmpdir)
  # TODO: Look up templates
  proposed = "# Context\n\n#{issue.description}\n\n## Issue\n\n#{issue.identifier}\n\n# Solution\n\n# Testing\n\n# Notes\n\n" # rubocop:disable Layout/LineLength
  tmpfile.write(proposed) && tmpfile.close
  desc = TTY::Editor.open(tmpfile.path)
  return tmpfile if desc

  File.open(tmpfile.path, 'w+') do |file|
    file.puts prompt.ask("Description for PR for #{issue.identifier} - #{issue.title}", default: proposed)
  end
  tmpfile
end

#pr_scope_for(title) ⇒ Object



145
146
147
148
149
150
151
152
153
# File 'lib/linear/cli/what_for.rb', line 145

def pr_scope_for(title)
  proposed_scope = title.match(/^\w+\(([^\)]+)\)/)
  return proposed_scope[1].downcase if proposed_scope

  scope = prompt.ask('What is the scope of this PR?', default: 'none')
  return nil if scope.empty? && scope == 'none'

  scope
end

#pr_title_for(issue) ⇒ Object



115
116
117
118
119
120
121
122
# File 'lib/linear/cli/what_for.rb', line 115

def pr_title_for(issue)
  proposed = [pr_type_for(issue)]
  proposed_scope = pr_scope_for(issue.title)
  proposed << "(#{proposed_scope})" if proposed_scope
  summary = issue.title.sub(/(?:#{ALLOWED_PR_TYPES})(\([^)]+\))? /, '')
  proposed << ": #{issue.identifier} - #{summary}"
  prompt.ask("Title for PR for #{issue.identifier} - #{summary}", default: proposed.join)
end

#pr_type_for(issue) ⇒ Object



138
139
140
141
142
143
# File 'lib/linear/cli/what_for.rb', line 138

def pr_type_for(issue)
  proposed_type = issue.title.match(/^(#{ALLOWED_PR_TYPES})/i)
  return proposed_type[1].downcase if proposed_type

  prompt.select('What type of PR is this?', PR_TYPE_SELECTIONS)
end

#project_for(team, project = nil) ⇒ Object

rubocop:disable Metrics/AbcSize



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/linear/cli/what_for.rb', line 101

def project_for(team, project = nil) # rubocop:disable Metrics/AbcSize
  projects = team.projects
  return nil if projects.empty?

  possibles = project ? project_scores(projects, project) : []
  return ask_for_projects(projects, search: project) if possibles.empty?

  first = possibles.first
  return first if first.match_score?(project) == 100

  selections = possibles + (projects - possibles)
  prompt.select('Project:', selections.to_h { |p| [p.name, p] }) if possibles.size.positive?
end

#project_scores(projects, search_term) ⇒ Object



97
98
99
# File 'lib/linear/cli/what_for.rb', line 97

def project_scores(projects, search_term)
  projects.select { |p| p.match_score?(search_term).positive? }.sort_by { |p| p.match_score?(search_term) }
end

#reason_for(reason = nil, four: nil) ⇒ Object



47
48
49
50
# File 'lib/linear/cli/what_for.rb', line 47

def reason_for(reason = nil, four: nil)
  question = four ? "Reason for #{TTY::Markdown.parse(four)}" : 'Reason'
  ask_or_edit reason, question
end

#team_for(key = nil) ⇒ Object



41
42
43
44
45
# File 'lib/linear/cli/what_for.rb', line 41

def team_for(key = nil)
  return Rubyists::Linear::Team.find(key) if key

  ask_for_team
end

#title_for(title = nil) ⇒ Object



84
85
86
87
88
# File 'lib/linear/cli/what_for.rb', line 84

def title_for(title = nil)
  return title if title

  prompt.ask('Title:')
end