phable-cli 0.1.15__tar.gz → 0.1.17__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.
Files changed (30) hide show
  1. {phable_cli-0.1.15 → phable_cli-0.1.17}/PKG-INFO +6 -7
  2. {phable_cli-0.1.15 → phable_cli-0.1.17}/README.md +4 -3
  3. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cache.py +4 -2
  4. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/assign.py +7 -4
  5. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/create.py +3 -0
  6. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/list.py +17 -2
  7. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/main.py +2 -0
  8. phable_cli-0.1.17/phable/cli/set.py +58 -0
  9. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/status.py +5 -1
  10. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/tag.py +5 -1
  11. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/config.py +2 -1
  12. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/display.py +27 -9
  13. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/phabricator.py +4 -0
  14. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/task.py +10 -0
  15. {phable_cli-0.1.15 → phable_cli-0.1.17}/pyproject.toml +2 -2
  16. {phable_cli-0.1.15 → phable_cli-0.1.17}/LICENSE +0 -0
  17. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/__init__.py +0 -0
  18. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/cache.py +0 -0
  19. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/comment.py +0 -0
  20. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/config.py +0 -0
  21. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/move.py +0 -0
  22. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/parent.py +0 -0
  23. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/report.py +0 -0
  24. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/show.py +0 -0
  25. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/subscribe.py +0 -0
  26. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/cli/utils.py +0 -0
  27. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/tests/conftest.py +0 -0
  28. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/tests/fixtures/simple_task.json +0 -0
  29. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/tests/phabricator_test.py +0 -0
  30. {phable_cli-0.1.15 → phable_cli-0.1.17}/phable/utils.py +0 -0
@@ -1,16 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phable-cli
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: Manage Phabricator tasks from the comfort of your terminal
5
5
  License: MIT
6
6
  License-File: LICENSE
7
7
  Author: Balthazar Rouberol
8
8
  Author-email: br@imap.cc
9
- Requires-Python: >=3.9,<4.0
9
+ Requires-Python: >=3.11,<4.0
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
12
  Classifier: Programming Language :: Python :: 3.11
15
13
  Classifier: Programming Language :: Python :: 3.12
16
14
  Classifier: Programming Language :: Python :: 3.13
@@ -61,10 +59,11 @@ Commands:
61
59
  move Move one or several task on their current project board
62
60
  parent Manage task parents
63
61
  report-done-tasks Print the details of all tasks in the `from` column and move them to the `to` column.
62
+ set Set the fields of one or multiple tasks
64
63
  show Show task details
65
- status Set the status of one or multiple tasks
64
+ status Set the status of one or multiple tasks [DEPRECATED]
66
65
  subscribe Subscribe to one or multiple task ids
67
- tag Add a tag on one or multiple tasks
66
+ tag Add a tag on one or multiple tasks [DEPRECATED]
68
67
  ```
69
68
 
70
69
  ## Setup
@@ -105,7 +104,7 @@ $ phable config aliases list
105
104
  done = move --column 'Done' --milestone
106
105
  review = move --column 'Needs Review' --milestone
107
106
  wip = move --column 'In Progress' --milestone
108
- team-report = list --owner brouberol --column 'In Progress' --column 'Needs Review' --column 'Blocked/Waiting' --column Done --milestone --format html
107
+ team-report = list --owner self --column 'In Progress' --column 'Needs Review' --column 'Blocked/Waiting' --column Done --milestone --format html
109
108
  ```
110
109
 
111
110
  ### Phabricator task IDs as clickable links in iTerm2
@@ -39,10 +39,11 @@ Commands:
39
39
  move Move one or several task on their current project board
40
40
  parent Manage task parents
41
41
  report-done-tasks Print the details of all tasks in the `from` column and move them to the `to` column.
42
+ set Set the fields of one or multiple tasks
42
43
  show Show task details
43
- status Set the status of one or multiple tasks
44
+ status Set the status of one or multiple tasks [DEPRECATED]
44
45
  subscribe Subscribe to one or multiple task ids
45
- tag Add a tag on one or multiple tasks
46
+ tag Add a tag on one or multiple tasks [DEPRECATED]
46
47
  ```
47
48
 
48
49
  ## Setup
@@ -83,7 +84,7 @@ $ phable config aliases list
83
84
  done = move --column 'Done' --milestone
84
85
  review = move --column 'Needs Review' --milestone
85
86
  wip = move --column 'In Progress' --milestone
86
- team-report = list --owner brouberol --column 'In Progress' --column 'Needs Review' --column 'Blocked/Waiting' --column Done --milestone --format html
87
+ team-report = list --owner self --column 'In Progress' --column 'Needs Review' --column 'Blocked/Waiting' --column Done --milestone --format html
87
88
  ```
88
89
 
89
90
  ### Phabricator task IDs as clickable links in iTerm2
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  import json
3
3
  import os
4
+ import getpass
4
5
  import sys
5
6
  import tempfile
6
7
  import time
@@ -12,7 +13,7 @@ if not os.getenv("GITHUB_ACTIONS"):
12
13
  CACHE_HOME_PER_PLATFORM = {
13
14
  "darwin": Path.home() / "Library" / "Caches",
14
15
  "linux": Path(os.getenv("XDG_CACHE_HOME", f"{Path.home()}/.config")),
15
- "windows": Path("c:/", "Users", os.getlogin(), "AppData", "Local", "Temp"),
16
+ "windows": Path("c:/", "Users", getpass.getuser(), "AppData", "Local", "Temp"),
16
17
  }
17
18
  else:
18
19
  CACHE_HOME_PER_PLATFORM = {}
@@ -27,7 +28,8 @@ class Cache:
27
28
  )
28
29
  self.cache_dir = cache_parent_dir / "phind"
29
30
  if not self.cache_dir.exists():
30
- self.cache_dir.mkdir()
31
+ # create entire path if it doesn't exist
32
+ os.makedirs(self.cache_dir, exist_ok=True)
31
33
  self.cache_filepath = self.cache_dir / "cache.json"
32
34
  if self.cache_filepath.exists():
33
35
  try:
@@ -38,13 +38,16 @@ def assign_task(
38
38
  $ phable assign T123456
39
39
  \b
40
40
  # assign to username
41
- $ phable assign T123456 --usernamme brouberol
41
+ $ phable assign T123456 --username brouberol
42
42
  \b
43
- # assign to a secondary owner
44
- $ phable assign T123456 --usernamme brouberol --secondary
43
+ # assign current user as a secondary owner
44
+ $ phable assign T123456 --username self --secondary
45
+ \b
46
+ # assign multiple tasks to current user
47
+ $ phable assign T123456 T234567 --usernamme self
45
48
 
46
49
  """
47
- if not username:
50
+ if not username or (username == "self"):
48
51
  user = client.current_user()
49
52
  else:
50
53
  user = client.find_user_by_username(username)
@@ -78,6 +78,9 @@ def create_task(
78
78
  # Create a task with an associated owner
79
79
  $ phable create --title 'A task' --owner brouberol
80
80
  \b
81
+ # Create a task with an associated owner set to the current user
82
+ $ phable create --title 'A task' --owner self
83
+ \b
81
84
  # Create a task with an associated subscriber
82
85
  $ phable create --title 'A task' --cc brouberol
83
86
 
@@ -6,7 +6,7 @@ from phable.cli.utils import find_project_phid_by_title, project_phid_option
6
6
  from phable.config import config
7
7
  from phable.display import TaskFormat, display_tasks
8
8
  from phable.phabricator import PhabricatorClient
9
-
9
+ from phable.task import TaskStatus
10
10
 
11
11
  @click.command(name="list")
12
12
  @click.option(
@@ -30,6 +30,13 @@ from phable.phabricator import PhabricatorClient
30
30
  "milestone board, instead of the project board itself"
31
31
  ),
32
32
  )
33
+ @click.option(
34
+ "--status",
35
+ required=False,
36
+ type=click.Choice(TaskStatus._member_names_), help="Task(s) status",
37
+ default=[TaskStatus.open, TaskStatus.progress, TaskStatus.stalled],
38
+ multiple=True,
39
+ )
33
40
  @click.option(
34
41
  "--format",
35
42
  required=False,
@@ -46,6 +53,7 @@ def list_tasks(
46
53
  project: Optional[str],
47
54
  owner: Optional[str] = None,
48
55
  milestone: bool = False,
56
+ status: list[str] = None,
49
57
  format: TaskFormat = TaskFormat.PLAIN,
50
58
  ):
51
59
  """Lists and filter tasks
@@ -60,6 +68,9 @@ def list_tasks(
60
68
  \b
61
69
  # List all tasks owner by brouberol in the Done column of the default board latest milestone
62
70
  $ phable list --milestone --owner brouberol --column Done
71
+ \b
72
+ # List all tasks owner by the current user in the Done column of the default board latest milestone
73
+ $ phable list --milestone --owner self --column Done
63
74
 
64
75
  """
65
76
  if owner:
@@ -85,12 +96,16 @@ def list_tasks(
85
96
  else:
86
97
  column_phids = []
87
98
  tasks = client.find_tasks(
88
- column_phids=column_phids, owner_phid=owner_user, project_phid=project_phid
99
+ column_phids=column_phids,
100
+ owner_phid=owner_user,
101
+ project_phid=project_phid,
102
+ status=status,
89
103
  )
90
104
  tasks += client.find_tasks(
91
105
  column_phids=column_phids,
92
106
  backup_owner_phid=owner_user,
93
107
  project_phid=project_phid,
108
+ status=status,
94
109
  )
95
110
  tasks = [client.enrich_task(task) for task in tasks]
96
111
  display_tasks(tasks=tasks, format=format)
@@ -13,6 +13,7 @@ from phable.cli.list import list_tasks
13
13
  from phable.cli.move import move_task
14
14
  from phable.cli.parent import parent
15
15
  from phable.cli.report import report_done_tasks
16
+ from phable.cli.set import set_task_fields
16
17
  from phable.cli.show import show_task
17
18
  from phable.cli.status import set_task_status
18
19
  from phable.cli.subscribe import subscribe_to_task
@@ -102,6 +103,7 @@ cli.add_command(list_tasks)
102
103
  cli.add_command(tag_task)
103
104
  cli.add_command(parent)
104
105
  cli.add_command(set_task_status)
106
+ cli.add_command(set_task_fields)
105
107
 
106
108
 
107
109
  def runcli():
@@ -0,0 +1,58 @@
1
+ from typing import Optional
2
+
3
+ import click
4
+
5
+ from phable.cli.utils import VARIADIC
6
+ from phable.phabricator import PhabricatorClient
7
+ from phable.task import TASK_ID, TaskPriority, TaskStatus
8
+
9
+
10
+ @click.command(name="set")
11
+ @click.option(
12
+ "--priority",
13
+ type=click.Choice(TaskPriority._member_names_),
14
+ help="Task(s) priority",
15
+ )
16
+ @click.option(
17
+ "--status", type=click.Choice(TaskStatus._member_names_), help="Task(s) status"
18
+ )
19
+ @click.option("--tags", type=str, multiple=True, help="Task(s) tag(s)")
20
+ @click.argument("task-ids", type=TASK_ID, nargs=VARIADIC)
21
+ @click.pass_context
22
+ @click.pass_obj
23
+ def set_task_fields(
24
+ client: PhabricatorClient,
25
+ ctx: click.Context,
26
+ task_ids: list[int],
27
+ priority: Optional[str],
28
+ status: Optional[str],
29
+ tags: list[str],
30
+ ):
31
+ """Set the fields of one or multiple tasks
32
+
33
+ \b
34
+ Example:
35
+ # Set the priority for a single task
36
+ $ phable set T123456 --priority high
37
+ \b
38
+ # Set the priority for multiple tasks
39
+ $ phable set T123456 T123457 --priority medium
40
+ \b
41
+ # Set the status for a single task
42
+ $ phable set T123456 --status resolved
43
+ \b
44
+ # Set the tags for a single task
45
+ $ phable set T123456 --tags 'Epic' 'OKR'
46
+
47
+ """
48
+ tag_phids = []
49
+ for tag in tags:
50
+ if tag_meta := client.find_project_by_title(title=tag):
51
+ tag_phids.append(tag_meta["phid"])
52
+ else:
53
+ ctx.fail(f"Tag '{tag}' not found")
54
+
55
+ for task_id in task_ids:
56
+ params = {"priority": priority, "status": status}
57
+ params = {param: value for param, value in params.values() if value is not None}
58
+ client.create_or_edit_task(task_id=task_id, params=params)
@@ -16,7 +16,7 @@ from phable.task import TASK_ID, TaskStatus
16
16
  def set_task_status(
17
17
  client: PhabricatorClient, task_ids: list[int], status: Optional[str]
18
18
  ):
19
- """Set the status of one or multiple tasks
19
+ """Set the status of one or multiple tasks [DEPRECATED]
20
20
 
21
21
  \b
22
22
  Example:
@@ -27,5 +27,9 @@ def set_task_status(
27
27
  $ phable status T123456 T123457 --status declined
28
28
 
29
29
  """
30
+ click.secho(
31
+ "[DEPRECATION NOTICE] This command has been replaced by `phable set --status` and will soon be removed",
32
+ fg="yellow",
33
+ )
30
34
  for task_id in task_ids:
31
35
  client.set_task_status(task_id=task_id, status=status)
@@ -12,7 +12,7 @@ from phable.task import TASK_ID
12
12
  @click.argument("task-ids", type=TASK_ID, nargs=VARIADIC)
13
13
  @click.pass_obj
14
14
  def tag_task(client: PhabricatorClient, task_ids: list[int], tag: Optional[str]):
15
- """Add a tag on one or multiple tasks
15
+ """Add a tag on one or multiple tasks [DEPRECATED]
16
16
 
17
17
  \b
18
18
  Example:
@@ -20,6 +20,10 @@ def tag_task(client: PhabricatorClient, task_ids: list[int], tag: Optional[str])
20
20
  $ phable tag T123456 T123457 --tag 'Essential work' # add multiple tags to a task
21
21
 
22
22
  """
23
+ click.secho(
24
+ "[DEPRECATION NOTICE] This command has been replaced by `phable set --tag` and will soon be removed",
25
+ fg="yellow",
26
+ )
23
27
  if tag := client.find_project_by_title(title=tag):
24
28
  for task_id in task_ids:
25
29
  client.assign_tag_to_task(task_id=task_id, tag_phid=tag["phid"])
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import getpass
2
3
  import sys
3
4
  from configparser import ConfigParser
4
5
  from dataclasses import dataclass, field
@@ -11,7 +12,7 @@ _warnings = []
11
12
  CONFIG_HOME_PER_PLATFORM = {
12
13
  "darwin": Path.home() / "Library" / "Preferences",
13
14
  "linux": Path(os.getenv("XDG_CACHE_HOME", f"{Path.home()}/.config")),
14
- "windows": Path("c:/", "Users", os.getlogin(), "AppData", "Local", "Programs"),
15
+ "windows": Path("c:/", "Users", getpass.getuser(), "AppData", "Local", "Programs"),
15
16
  }
16
17
  config_filepath = CONFIG_HOME_PER_PLATFORM[sys.platform] / "phable" / "config.ini"
17
18
 
@@ -11,6 +11,8 @@ class TaskFormat(StrEnum):
11
11
  HTML = auto()
12
12
  MARKDOWN = auto()
13
13
  WIKITEXT = auto()
14
+ ONELINE = auto()
15
+ IDS = auto()
14
16
 
15
17
 
16
18
  def display_tasks(
@@ -34,7 +36,8 @@ class TaskPrinter:
34
36
  pass
35
37
 
36
38
  def print_list(self, tasks: list[dict]) -> None:
37
- pass
39
+ for task in tasks:
40
+ self.print(task)
38
41
 
39
42
  def title(self, task: dict) -> str:
40
43
  return f"{Task.from_int(task['id'])} {task['fields']['name']}"
@@ -55,19 +58,11 @@ class MarkdownTaskPrinter(TaskPrinter):
55
58
  def print(self, task: dict) -> None:
56
59
  self._printer(f"* [{self.title(task)}]({task['url']}) {self.status(task)}")
57
60
 
58
- def print_list(self, tasks: list[dict]) -> None:
59
- for task in tasks:
60
- self.print(task)
61
-
62
61
 
63
62
  class WikitextTaskPrinter(TaskPrinter):
64
63
  def print(self, task: dict) -> None:
65
64
  self._printer(f"* [{task['url']} {self.title(task)}] {self.status(task)}")
66
65
 
67
- def print_list(self, tasks: list[dict]) -> None:
68
- for task in tasks:
69
- self.print(task)
70
-
71
66
 
72
67
  class HtmlTaskPrinter(TaskPrinter):
73
68
  def print(self, task: dict) -> None:
@@ -112,6 +107,25 @@ class PlainTaskPrinter(TaskPrinter):
112
107
  self._printer("=" * 50)
113
108
 
114
109
 
110
+ class OneLineTaskPrinter(TaskPrinter):
111
+ def print(self, task: dict) -> None:
112
+ self._printer(
113
+ " ".join(
114
+ [
115
+ f"{Task.from_int(task['id'])}",
116
+ f"{task['fields']['status']['name']:<12}",
117
+ f"{task['fields']['priority']['name']:<12}",
118
+ task["fields"]["name"],
119
+ ]
120
+ )
121
+ )
122
+
123
+
124
+ class IdsTaskPrinter(TaskPrinter):
125
+ def print(self, task: dict) -> None:
126
+ self._printer(f"{Task.from_int(task['id'])}")
127
+
128
+
115
129
  def get_printer(format: TaskFormat) -> TaskPrinter:
116
130
  if format == TaskFormat.PLAIN:
117
131
  return PlainTaskPrinter(print)
@@ -123,5 +137,9 @@ def get_printer(format: TaskFormat) -> TaskPrinter:
123
137
  return MarkdownTaskPrinter(print)
124
138
  elif format == TaskFormat.WIKITEXT:
125
139
  return WikitextTaskPrinter(print)
140
+ elif format == TaskFormat.ONELINE:
141
+ return OneLineTaskPrinter(print)
142
+ elif format == TaskFormat.IDS:
143
+ return IdsTaskPrinter(print)
126
144
  else:
127
145
  raise ValueError(f"Unknown format: {format}")
@@ -173,6 +173,7 @@ class PhabricatorClient:
173
173
  owner_phid: Optional[str] = None,
174
174
  backup_owner_phid: Optional[str] = None,
175
175
  project_phid: Optional[str] = None,
176
+ status: Optional[list[str]] = None,
176
177
  ) -> list[dict[str, Any]]:
177
178
  params = {
178
179
  "attachments[subscribers]": "true",
@@ -187,6 +188,9 @@ class PhabricatorClient:
187
188
  params["constraints[custom.train.backup][0]"] = backup_owner_phid
188
189
  if project_phid:
189
190
  params["constraints[projects][0]"] = project_phid
191
+ if status:
192
+ for i, value in enumerate(status):
193
+ params[f"constraints[statuses][{i}]"] = value
190
194
  return self._make_request("maniphest.search", params=params)["result"]["data"]
191
195
 
192
196
  def find_subtasks(self, parent_id: int) -> list[dict[str, Any]]:
@@ -30,3 +30,13 @@ class TaskStatus(StrEnum):
30
30
  stalled = "stalled"
31
31
  invalid = "invalid"
32
32
  declined = "declined"
33
+
34
+
35
+ # TODO these are hard-coded for Wikimedia's Phab, we should do something better
36
+ class TaskPriority(StrEnum):
37
+ lowest = "lowest"
38
+ low = "low"
39
+ medium = "medium"
40
+ high = "high"
41
+ unbreak = "unbreak"
42
+ triage = "triage"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "phable-cli"
3
- version = "0.1.15"
3
+ version = "0.1.17"
4
4
  description = "Manage Phabricator tasks from the comfort of your terminal"
5
5
  authors = ["Balthazar Rouberol <br@imap.cc>"]
6
6
  license = "MIT"
@@ -8,7 +8,7 @@ readme = "README.md"
8
8
  packages = [{ include = "phable" }]
9
9
 
10
10
  [tool.poetry.dependencies]
11
- python = "^3.9"
11
+ python = "^3.11"
12
12
  requests = "^2.32.3"
13
13
  click = "^8.1.8"
14
14
  pytest = "^8.3.5"
File without changes
File without changes