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.
- {phable_cli-0.1.3 → phable_cli-0.1.4}/PKG-INFO +1 -1
- {phable_cli-0.1.3 → phable_cli-0.1.4}/phable_cli/cli.py +48 -7
- {phable_cli-0.1.3 → phable_cli-0.1.4}/phable_cli/config.py +10 -4
- {phable_cli-0.1.3 → phable_cli-0.1.4}/phable_cli/utils.py +10 -3
- {phable_cli-0.1.3 → phable_cli-0.1.4}/pyproject.toml +2 -2
- {phable_cli-0.1.3 → phable_cli-0.1.4}/LICENSE +0 -0
- {phable_cli-0.1.3 → phable_cli-0.1.4}/README.md +0 -0
- {phable_cli-0.1.3 → phable_cli-0.1.4}/phable_cli/cache.py +0 -0
- {phable_cli-0.1.3 → phable_cli-0.1.4}/phable_cli/phabricator.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
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:
|
|
15
|
+
phable = 'phable_cli.cli:runcli'
|
|
16
16
|
|
|
17
17
|
[build-system]
|
|
18
18
|
requires = ["poetry-core"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|