phable-cli 0.1.3__tar.gz → 0.1.4__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: phable-cli
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Manage Phabricator tasks from the comfort of your terminal
5
5
  License: MIT
6
6
  Author: Balthazar Rouberol
@@ -1,6 +1,7 @@
1
1
  import atexit
2
2
  import json
3
3
  import re
4
+ from pathlib import Path
4
5
  from typing import Optional
5
6
 
6
7
  import click
@@ -41,7 +42,7 @@ class Task(int):
41
42
  default="plain",
42
43
  help="Output format",
43
44
  )
44
- @click.argument("task-id", type=Task.from_str)
45
+ @click.argument("task-id", type=Task.from_str, required=True)
45
46
  def show_task(task_id: int, format: str = "plain"):
46
47
  """Show task details
47
48
 
@@ -112,6 +113,14 @@ def show_task(task_id: int, format: str = "plain"):
112
113
  "--description",
113
114
  help="Task description or path to a file containing the description body. If not provided, an editor will be opened.",
114
115
  )
116
+ @click.option(
117
+ "--template",
118
+ type=Path,
119
+ help=(
120
+ "Task description template file. If provided, the --description flag will be ignored "
121
+ "and an editor will be opened, pre-filled with the template file content"
122
+ ),
123
+ )
115
124
  @click.option(
116
125
  "--priority",
117
126
  type=click.Choice(["unbreaknow", "high", "normal", "low", "needs-triage"]),
@@ -120,15 +129,18 @@ def show_task(task_id: int, format: str = "plain"):
120
129
  )
121
130
  @click.option("--parent-id", type=Task.from_str, help="ID of parent task")
122
131
  @click.option("--tags", multiple=True, help="Tags to associate to the task")
132
+ @click.option("--cc", multiple=True, help="Subscribers to associate to the task")
123
133
  @click.option("--owner", help="The username of the task owner")
124
134
  @click.pass_context
125
135
  def create_task(
126
136
  ctx,
127
137
  title: str,
128
138
  description: Optional[str],
139
+ template: Path,
129
140
  priority: str,
130
141
  parent_id: Optional[str],
131
142
  tags: list[str],
143
+ cc: list[str],
132
144
  owner: Optional[str],
133
145
  ):
134
146
  """Create a new task
@@ -145,6 +157,9 @@ def create_task(
145
157
  # Create a task with associated title, priority and desription
146
158
  $ phable create --title 'Do the thing!' --priority high --description 'Address the thing right now'
147
159
  \b
160
+ # Create a task with associated description template
161
+ $ phable create --title 'Do the thing!' --template ./template.md
162
+ \b
148
163
  # Create a task with a given parent
149
164
  $ phable create --title 'A subtask' --description 'Subtask description' --parent-id T123456
150
165
  \b
@@ -156,10 +171,24 @@ def create_task(
156
171
  \b
157
172
  # Create a task with an associated owner
158
173
  $ phable create --title 'A task' --owner brouberol
174
+ \b
175
+ # Create a task with an associated subscriber
176
+ $ phable create --title 'A task' --cc brouberol
159
177
 
160
178
  """
161
179
  client = PhabricatorClient()
162
- description = text_from_cli_arg_or_fs_or_editor(description)
180
+ if template:
181
+ if template.exists():
182
+ description = template
183
+ force_editor = True
184
+ else:
185
+ ctx.fail(f"Template file {template} does not exist")
186
+ else:
187
+ force_editor = False
188
+ description = text_from_cli_arg_or_fs_or_editor(
189
+ description, force_editor=force_editor
190
+ )
191
+
163
192
  task_params = {
164
193
  "title": title,
165
194
  "description": description,
@@ -191,7 +220,6 @@ def create_task(
191
220
  tag_projects_phids.append(project["phid"])
192
221
  else:
193
222
  ctx.fail(f"Project {tag} not found")
194
-
195
223
  if tag_projects_phids:
196
224
  task_params["projects.add"] = tag_projects_phids
197
225
 
@@ -205,6 +233,15 @@ def create_task(
205
233
  parent = client.show_task(parent_id)
206
234
  task_params["parents.set"] = [parent["phid"]]
207
235
 
236
+ cc_phids = []
237
+ for username in cc:
238
+ if user := client.find_user_by_username(username=username):
239
+ cc_phids.appedn(user["phid"])
240
+ else:
241
+ ctx.fail(f"User {owner} not found")
242
+ if cc_phids:
243
+ task_params["subscribers.set"] = cc_phids
244
+
208
245
  task = client.create_or_edit_task(task_params)
209
246
  ctx.invoke(show_task, task_id=task["result"]["object"]["id"])
210
247
 
@@ -215,7 +252,7 @@ def create_task(
215
252
  required=False,
216
253
  help="The username to assign the task to. Self-assign the task if not provided.",
217
254
  )
218
- @click.argument("task-ids", type=Task.from_str, nargs=VARIADIC)
255
+ @click.argument("task-ids", type=Task.from_str, nargs=VARIADIC, required=True)
219
256
  @click.pass_context
220
257
  def assign_task(ctx, task_ids: list[int], username: Optional[str]):
221
258
  """Assign one or multiple task ids to a username
@@ -252,7 +289,7 @@ def assign_task(ctx, task_ids: list[int], username: Optional[str]):
252
289
  "milestone board, instead of the project board itself"
253
290
  ),
254
291
  )
255
- @click.argument("task-ids", type=Task.from_str, nargs=VARIADIC)
292
+ @click.argument("task-ids", type=Task.from_str, nargs=VARIADIC, required=True)
256
293
  @click.pass_context
257
294
  def move_task(
258
295
  ctx: Context, task_ids: list[int], column: Optional[str], milestone: bool
@@ -306,7 +343,7 @@ def comment_on_task(task_id: int, comment: Optional[str]):
306
343
 
307
344
 
308
345
  @cli.command(name="subscribe")
309
- @click.argument("task-ids", type=Task.from_str, nargs=VARIADIC)
346
+ @click.argument("task-ids", type=Task.from_str, nargs=VARIADIC, required=True)
310
347
  @click.pass_context
311
348
  def subscribe_to_task(ctx, task_ids: list[int]):
312
349
  """Subscribe to one or multiple task ids
@@ -325,5 +362,9 @@ def subscribe_to_task(ctx, task_ids: list[int]):
325
362
  client.add_user_to_task_subscribers(task_id=task_id, user_phid=user["phid"])
326
363
 
327
364
 
365
+ def runcli():
366
+ cli(max_content_width=120)
367
+
368
+
328
369
  if __name__ == "__main__":
329
- cli()
370
+ runcli()
@@ -1,15 +1,16 @@
1
1
  import os
2
- import sys
3
2
  from dataclasses import dataclass, field
4
3
  from functools import partial
5
4
 
5
+ import click
6
+
7
+ _warnings = []
8
+
6
9
 
7
10
  def os_getenv_or_raise(env_var_name: str):
8
11
  if val := os.getenv(env_var_name):
9
12
  return val
10
- sys.stderr.write(f"{env_var_name} is not set and is required\n")
11
- sys.stderr.flush()
12
- sys.exit(1)
13
+ _warnings.append(f"Required environment variable {env_var_name} is not set")
13
14
 
14
15
 
15
16
  def field_with_default_from_env(env_var_name):
@@ -24,5 +25,10 @@ class Config:
24
25
  "PHABRICATOR_DEFAULT_PROJECT_PHID"
25
26
  )
26
27
 
28
+ def __post_init__(self):
29
+ for warn in _warnings:
30
+ click.echo(click.style(warn, fg="yellow"), err=True)
31
+ return len(_warnings) == 0
32
+
27
33
 
28
34
  config = Config()
@@ -4,7 +4,7 @@ import os
4
4
  from pathlib import Path
5
5
 
6
6
 
7
- def text_from_cli_arg_or_fs_or_editor(body_or_path: str) -> str:
7
+ def text_from_cli_arg_or_fs_or_editor(body_or_path: str, force_editor: bool) -> str:
8
8
  """Return argument text/file content, or return prompted input text.
9
9
 
10
10
  If some argument text is passed, and it matches a file path, return the file content.
@@ -14,15 +14,22 @@ def text_from_cli_arg_or_fs_or_editor(body_or_path: str) -> str:
14
14
 
15
15
  """
16
16
  try:
17
- if body_or_path is not None and (local_file := Path(body_or_path)).exists():
17
+ if (
18
+ not force_editor
19
+ and body_or_path is not None
20
+ and (local_file := Path(body_or_path)).exists()
21
+ ):
18
22
  return local_file.read_text()
19
23
  except OSError:
20
24
  pass
21
25
 
22
- if not body_or_path:
26
+ if not body_or_path or force_editor:
23
27
  txt_tmpfile = tempfile.NamedTemporaryFile(
24
28
  encoding="utf-8", mode="w", suffix=".md"
25
29
  )
30
+ if force_editor:
31
+ txt_tmpfile.write(body_or_path.read_text())
32
+ txt_tmpfile.flush()
26
33
  subprocess.run([os.environ["EDITOR"], txt_tmpfile.name])
27
34
  return Path(txt_tmpfile.name).read_text()
28
35
  return body_or_path
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "phable-cli"
3
- version = "0.1.3"
3
+ version = "0.1.4"
4
4
  description = "Manage Phabricator tasks from the comfort of your terminal"
5
5
  authors = ["Balthazar Rouberol <br@imap.cc>"]
6
6
  license = "MIT"
@@ -12,7 +12,7 @@ requests = "^2.32.3"
12
12
  click = "^8.1.8"
13
13
 
14
14
  [tool.poetry.scripts]
15
- phable = 'phable_cli.cli:cli'
15
+ phable = 'phable_cli.cli:runcli'
16
16
 
17
17
  [build-system]
18
18
  requires = ["poetry-core"]
File without changes
File without changes