|
1 | 1 | defmodule LinearCli.CLI.Commands do |
2 | 2 | @moduledoc """ |
3 | | - The logic behind each subcommand: fetch via `LinearCli.Linear`, display the |
4 | | - result. Ported from vendor/ruby-linear-cli/lib/linear/commands/**. |
| 3 | + Issue command implementations: fetch via `LinearCli.Linear`, display the |
| 4 | + result. Ported from vendor/ruby-linear-cli/lib/linear/commands/issue/**. |
| 5 | +
|
| 6 | + Non-issue command families live in their own focused modules: |
| 7 | + `LinearCli.CLI.Commands.System`, `LinearCli.CLI.Commands.Teams`, |
| 8 | + `LinearCli.CLI.Commands.Projects`, and `LinearCli.CLI.Commands.Profiles`. |
5 | 9 | """ |
6 | 10 |
|
7 | 11 | alias LinearCli.Browser |
8 | 12 | alias LinearCli.CLI.{Display, IssueHelpers, Projects, Prompt, WhatFor} |
9 | 13 | alias LinearCli.CLI.Issue.{Actions, Assignment, Creation, Identifiers} |
10 | | - alias LinearCli.{Favorites, Git, Linear, Profiles} |
| 14 | + alias LinearCli.{Git, Linear, Profiles} |
11 | 15 |
|
12 | 16 | @max_concurrent_issue_updates 20 |
13 | 17 |
|
14 | | - @doc "Ported from commands/whoami.rb." |
15 | | - def whoami(%{flags: flags, options: options}) do |
16 | | - with {:ok, user} <- Linear.me() do |
17 | | - Display.show(user, %{output: options.output, teams: flags.teams}) |
18 | | - :ok |
19 | | - end |
20 | | - end |
21 | | - |
22 | | - @doc """ |
23 | | - Ported from commands/version.rb, extended to respect the global |
24 | | - `--output json` option like every other command does - previously |
25 | | - ignored it and always printed plain text. The hidden Markdown renders make |
26 | | - this command a complete release smoke test for the MDEx and Syntect NIFs, |
27 | | - Marcli's syntax-highlighting integration, and the application boot path. |
28 | | - """ |
29 | | - def version(%{options: options}) do |
30 | | - verify_markdown_runtime!() |
31 | | - version = to_string(Application.spec(:linear_cli, :vsn)) |
32 | | - |
33 | | - if options.output == "json" do |
34 | | - IO.puts(Jason.encode!(%{version: version})) |
35 | | - else |
36 | | - IO.puts(version) |
37 | | - end |
38 | | - |
39 | | - :ok |
40 | | - end |
41 | | - |
42 | | - defp verify_markdown_runtime! do |
43 | | - theme = Marcli.Theme.default() |
44 | | - elixir = Marcli.render("```elixir\ndef smoke, do: :ok\n```") |
45 | | - ruby = Marcli.render("```ruby\ndef smoke; :ok; end\n```") |
46 | | - |
47 | | - elixir_keyword = theme.syntax.keyword_declaration <> "def" <> theme.reset |
48 | | - ruby_keyword = theme.syntax.keyword_type <> "def" <> theme.reset |
49 | | - |
50 | | - unless String.contains?(elixir, elixir_keyword) and String.contains?(ruby, ruby_keyword) do |
51 | | - raise "Markdown syntax-highlighting runtime is unavailable" |
52 | | - end |
53 | | - |
54 | | - :ok |
55 | | - end |
56 | | - |
57 | | - @doc "Ported from commands/team/list.rb. Ruby's `--mine` defaults true." |
58 | | - def team_list(%{flags: flags, options: options}) do |
59 | | - result = if flags.no_mine, do: Linear.teams(), else: Linear.my_teams() |
60 | | - |
61 | | - with {:ok, teams} <- result do |
62 | | - Display.show(filter_favorites(teams, flags.all, "team", & &1.key), %{ |
63 | | - output: options.output |
64 | | - }) |
65 | | - |
66 | | - :ok |
67 | | - end |
68 | | - end |
69 | | - |
70 | | - @doc "Ported from commands/project/list.rb. Ruby's `--mine` defaults false." |
71 | | - def project_list(%{flags: flags, options: options}) do |
72 | | - with {:ok, projects} <- projects_for(flags, options) do |
73 | | - Display.show(filter_favorites(projects, flags.all, "project", & &1.id), %{ |
74 | | - output: options.output |
75 | | - }) |
76 | | - |
77 | | - :ok |
78 | | - end |
79 | | - end |
80 | | - |
81 | | - defp projects_for(_flags, %{team: team_key}) when is_binary(team_key) do |
82 | | - with {:ok, team} <- Linear.find_team(team_key) do |
83 | | - Linear.projects_by_team(team.id) |
84 | | - end |
85 | | - end |
86 | | - |
87 | | - defp projects_for(%{mine: true}, _options), do: Linear.my_projects() |
88 | | - defp projects_for(_flags, _options), do: Linear.projects() |
89 | | - |
90 | | - @doc """ |
91 | | - New in this port - Ruby has no equivalent. Favorites a team |
92 | | - (`LinearCli.Favorites`) - once any team is favorited, `team list` |
93 | | - defaults to showing just favorites (`--all` overrides). |
94 | | - """ |
95 | | - def team_favorite(%{args: %{team: key}}) do |
96 | | - with {:ok, team} <- Linear.find_team(key) do |
97 | | - Favorites.add("team", team.key) |
98 | | - Prompt.ok("Favorited team #{team.key}") |
99 | | - :ok |
100 | | - end |
101 | | - end |
102 | | - |
103 | | - @doc "New in this port - Ruby has no equivalent. Un-favorites a team." |
104 | | - def team_unfavorite(%{args: %{team: key}}) do |
105 | | - with {:ok, team} <- Linear.find_team(key) do |
106 | | - Favorites.remove("team", team.key) |
107 | | - Prompt.ok("Un-favorited team #{team.key}") |
108 | | - :ok |
109 | | - end |
110 | | - end |
111 | | - |
112 | | - @doc """ |
113 | | - New in this port - Ruby has no equivalent. Favorites a project |
114 | | - (`LinearCli.Favorites`), resolved against the active team's projects, |
115 | | - prompting if ambiguous. Team is resolved via `--team`, the active |
116 | | - profile, or an interactive prompt. Once any project is favorited, |
117 | | - `project list` defaults to showing just favorites (`--all` overrides). |
118 | | - """ |
119 | | - def project_favorite(%{args: %{project: search}, options: options}) do |
120 | | - team = WhatFor.team_for(options.team || Profiles.default_team()) |
121 | | - |
122 | | - with {:ok, projects} <- Linear.projects_by_team(team.id, %{search: search}), |
123 | | - project when not is_nil(project) <- Projects.project_for(projects, search) do |
124 | | - Favorites.add("project", project.id) |
125 | | - Prompt.ok("Favorited project #{project.name}") |
126 | | - :ok |
127 | | - else |
128 | | - nil -> {:error, {:smells_bad, "No project found matching #{search}"}} |
129 | | - {:error, reason} -> {:error, reason} |
130 | | - end |
131 | | - end |
132 | | - |
133 | | - @doc "New in this port - Ruby has no equivalent. Un-favorites a project." |
134 | | - def project_unfavorite(%{args: %{project: search}, options: options}) do |
135 | | - team = WhatFor.team_for(options.team || Profiles.default_team()) |
136 | | - |
137 | | - with {:ok, projects} <- Linear.projects_by_team(team.id, %{search: search}), |
138 | | - project when not is_nil(project) <- Projects.project_for(projects, search) do |
139 | | - Favorites.remove("project", project.id) |
140 | | - Prompt.ok("Un-favorited project #{project.name}") |
141 | | - :ok |
142 | | - else |
143 | | - nil -> {:error, {:smells_bad, "No project found matching #{search}"}} |
144 | | - {:error, reason} -> {:error, reason} |
145 | | - end |
146 | | - end |
147 | | - |
148 | | - # Once any favorite of `kind` exists, narrows `records` down to just |
149 | | - # those (matched via `key_fun`) - invisible to anyone who's never |
150 | | - # favorited anything, since an empty favorites list leaves `records` |
151 | | - # untouched. `all?` (the new `--all` flag) always shows everything, |
152 | | - # bypassing the favorites lookup entirely. |
153 | | - defp filter_favorites(records, true, _kind, _key_fun), do: records |
154 | | - |
155 | | - defp filter_favorites(records, _all?, kind, key_fun) do |
156 | | - case Favorites.list(kind) do |
157 | | - [] -> records |
158 | | - favorite_values -> Enum.filter(records, &(key_fun.(&1) in favorite_values)) |
159 | | - end |
160 | | - end |
161 | | - |
162 | | - @doc """ |
163 | | - New in this port - Ruby has no equivalent. Posts a status update |
164 | | - (Linear's own "Project Update" feature - a journal-style status post, |
165 | | - not an edit to the project's own fields) via the projectUpdateCreate |
166 | | - mutation. `PROJECT` is resolved against the active team's projects, |
167 | | - prompting if ambiguous. Team is resolved via `--team`, the active |
168 | | - profile, or an interactive prompt. |
169 | | - """ |
170 | | - def project_update(%{args: %{project: search}, options: options}) do |
171 | | - team = WhatFor.team_for(options.team || Profiles.default_team()) |
172 | | - |
173 | | - with {:ok, projects} <- Linear.projects_by_team(team.id, %{search: search}), |
174 | | - project when not is_nil(project) <- Projects.project_for(projects, search), |
175 | | - {:ok, update} <- |
176 | | - Linear.post_project_update(project.id, options.body, %{health: options.health}) do |
177 | | - Display.show(update, %{output: options.output}) |
178 | | - :ok |
179 | | - else |
180 | | - nil -> {:error, {:smells_bad, "No project found matching #{search}"}} |
181 | | - {:error, reason} -> {:error, reason} |
182 | | - end |
183 | | - end |
184 | | - |
185 | | - @doc """ |
186 | | - New in this port - Ruby has no equivalent. Saves a new named |
187 | | - team/project bundle (`LinearCli.Profiles.create/2`) that `profile use` |
188 | | - can later switch to. |
189 | | - """ |
190 | | - def profile_create(%{args: %{name: name}, options: options}) do |
191 | | - case Profiles.create(name, team: options.team, project: options.project) do |
192 | | - {:ok, profile} -> |
193 | | - Display.show(profile, %{output: options.output}) |
194 | | - :ok |
195 | | - |
196 | | - {:error, reason} -> |
197 | | - {:error, reason} |
198 | | - end |
199 | | - end |
200 | | - |
201 | | - @doc "New in this port - Ruby has no equivalent. Lists every saved profile." |
202 | | - def profile_list(%{options: options}) do |
203 | | - Display.show(Profiles.list(), %{output: options.output}) |
204 | | - :ok |
205 | | - end |
206 | | - |
207 | | - @doc """ |
208 | | - New in this port - Ruby has no equivalent. Switches the active profile - |
209 | | - its team/project become the defaults `issue create`/`issue list` fall |
210 | | - back to when `--team`/`--project` are omitted. |
211 | | - """ |
212 | | - def profile_use(%{args: %{name: name}}) do |
213 | | - case Profiles.activate(name) do |
214 | | - :ok -> |
215 | | - Prompt.ok("Switched to profile #{name}") |
216 | | - :ok |
217 | | - |
218 | | - {:error, :not_found} -> |
219 | | - {:error, {:smells_bad, "No profile named #{name}"}} |
220 | | - end |
221 | | - end |
222 | | - |
223 | | - @doc "New in this port - Ruby has no equivalent. Shows the active profile, if any." |
224 | | - def profile_show(%{options: options}) do |
225 | | - case Profiles.active() do |
226 | | - nil -> Prompt.warn("No active profile") |
227 | | - profile -> Display.show(profile, %{output: options.output}) |
228 | | - end |
229 | | - |
230 | | - :ok |
231 | | - end |
232 | | - |
233 | | - @doc "New in this port - Ruby has no equivalent. Deactivates the active profile without deleting it." |
234 | | - def profile_clear(_result) do |
235 | | - Profiles.clear() |
236 | | - Prompt.ok("Cleared active profile") |
237 | | - :ok |
238 | | - end |
239 | | - |
240 | | - @doc "New in this port - Ruby has no equivalent. Deletes a saved profile." |
241 | | - def profile_delete(%{args: %{name: name}}) do |
242 | | - case Profiles.delete(name) do |
243 | | - :ok -> |
244 | | - Prompt.ok("Deleted profile #{name}") |
245 | | - :ok |
246 | | - |
247 | | - {:error, :not_found} -> |
248 | | - {:error, {:smells_bad, "No profile named #{name}"}} |
249 | | - end |
250 | | - end |
251 | | - |
252 | 18 | @doc """ |
253 | 19 | Ported from commands/issue/list.rb + operations/issue/list.rb. |
254 | 20 |
|
|
0 commit comments