dart-tools 0.6.16__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.

Potentially problematic release.


This version of dart-tools might be problematic. Click here for more details.

Files changed (285) hide show
  1. dart/__init__.py +8 -6
  2. dart/dart.py +421 -698
  3. dart/generated/__init__.py +1 -1
  4. dart/generated/api/__init__.py +8 -0
  5. dart/generated/api/comment/__init__.py +1 -0
  6. dart/generated/api/{transactions/transactions_create.py → comment/create_comment.py} +57 -58
  7. dart/generated/api/config/__init__.py +1 -0
  8. dart/generated/api/config/get_config.py +141 -0
  9. dart/generated/api/dartboard/__init__.py +1 -0
  10. dart/generated/api/{attachments/attachments_list.py → dartboard/retrieve_dartboard.py} +58 -62
  11. dart/generated/api/doc/__init__.py +1 -0
  12. dart/generated/api/doc/create_doc.py +179 -0
  13. dart/generated/api/doc/delete_doc.py +165 -0
  14. dart/generated/api/{comments/comments_list.py → doc/list_docs.py} +115 -111
  15. dart/generated/api/{forms/forms_list.py → doc/retrieve_doc.py} +58 -62
  16. dart/generated/api/doc/update_doc.py +188 -0
  17. dart/generated/api/folder/__init__.py +1 -0
  18. dart/generated/api/{webhooks/webhooks_list.py → folder/retrieve_folder.py} +58 -62
  19. dart/generated/api/task/__init__.py +1 -0
  20. dart/generated/api/task/create_task.py +172 -0
  21. dart/generated/api/task/delete_task.py +165 -0
  22. dart/generated/api/task/list_tasks.py +497 -0
  23. dart/generated/api/{layouts/layouts_list.py → task/retrieve_task.py} +58 -62
  24. dart/generated/api/task/update_task.py +188 -0
  25. dart/generated/api/view/__init__.py +1 -0
  26. dart/generated/api/{tenants/tenants_list.py → view/retrieve_view.py} +58 -62
  27. dart/generated/client.py +13 -13
  28. dart/generated/models/__init__.py +34 -342
  29. dart/generated/models/comment.py +34 -150
  30. dart/generated/models/comment_create.py +18 -97
  31. dart/generated/models/concise_doc.py +84 -0
  32. dart/generated/models/concise_task.py +267 -0
  33. dart/generated/models/dartboard.py +37 -237
  34. dart/generated/models/doc.py +31 -183
  35. dart/generated/models/doc_create.py +22 -238
  36. dart/generated/models/doc_update.py +26 -233
  37. dart/generated/models/folder.py +39 -84
  38. dart/generated/models/list_docs_o_item.py +15 -0
  39. dart/generated/models/{paginated_attachment_list.py → paginated_concise_doc_list.py} +18 -17
  40. dart/generated/models/{paginated_dartboard_list.py → paginated_concise_task_list.py} +18 -17
  41. dart/generated/models/task.py +149 -406
  42. dart/generated/models/task_create.py +113 -428
  43. dart/generated/models/task_update.py +113 -421
  44. dart/generated/models/user.py +14 -192
  45. dart/generated/models/user_space_configuration.py +147 -0
  46. dart/generated/models/view.py +39 -149
  47. dart/generated/models/wrapped_comment.py +65 -0
  48. dart/generated/models/wrapped_comment_create.py +65 -0
  49. dart/generated/models/wrapped_dartboard.py +65 -0
  50. dart/generated/models/wrapped_doc.py +65 -0
  51. dart/generated/models/wrapped_doc_create.py +65 -0
  52. dart/generated/models/wrapped_doc_update.py +65 -0
  53. dart/generated/models/wrapped_folder.py +65 -0
  54. dart/generated/models/wrapped_task.py +65 -0
  55. dart/generated/models/wrapped_task_create.py +65 -0
  56. dart/generated/models/wrapped_task_update.py +65 -0
  57. dart/generated/models/wrapped_view.py +65 -0
  58. dart/generated/types.py +4 -3
  59. dart/old.py +216 -0
  60. dart/order_manager.py +4 -15
  61. dart/webhook.py +2 -7
  62. {dart_tools-0.6.16.dist-info → dart_tools-0.7.0.dist-info}/METADATA +15 -75
  63. dart_tools-0.7.0.dist-info/RECORD +71 -0
  64. {dart_tools-0.6.16.dist-info → dart_tools-0.7.0.dist-info}/WHEEL +1 -1
  65. dart/generated/api/attachments/__init__.py +0 -0
  66. dart/generated/api/comments/__init__.py +0 -0
  67. dart/generated/api/dartboards/__init__.py +0 -0
  68. dart/generated/api/dartboards/dartboards_list.py +0 -286
  69. dart/generated/api/dashboards/__init__.py +0 -0
  70. dart/generated/api/dashboards/dashboards_list.py +0 -199
  71. dart/generated/api/docs/__init__.py +0 -0
  72. dart/generated/api/docs/docs_list.py +0 -387
  73. dart/generated/api/folders/__init__.py +0 -0
  74. dart/generated/api/folders/folders_list.py +0 -249
  75. dart/generated/api/form_fields/__init__.py +0 -0
  76. dart/generated/api/form_fields/form_fields_list.py +0 -169
  77. dart/generated/api/forms/__init__.py +0 -0
  78. dart/generated/api/layouts/__init__.py +0 -0
  79. dart/generated/api/links/__init__.py +0 -0
  80. dart/generated/api/links/links_list.py +0 -169
  81. dart/generated/api/options/__init__.py +0 -0
  82. dart/generated/api/options/options_list.py +0 -244
  83. dart/generated/api/properties/__init__.py +0 -0
  84. dart/generated/api/properties/properties_list.py +0 -219
  85. dart/generated/api/reactions/__init__.py +0 -0
  86. dart/generated/api/reactions/reactions_list.py +0 -169
  87. dart/generated/api/relationship_kinds/__init__.py +0 -0
  88. dart/generated/api/relationship_kinds/relationship_kinds_list.py +0 -169
  89. dart/generated/api/relationships/__init__.py +0 -0
  90. dart/generated/api/relationships/relationships_list.py +0 -169
  91. dart/generated/api/spaces/__init__.py +0 -0
  92. dart/generated/api/spaces/spaces_list.py +0 -229
  93. dart/generated/api/statuses/__init__.py +0 -0
  94. dart/generated/api/statuses/statuses_list.py +0 -264
  95. dart/generated/api/task_doc_relationships/__init__.py +0 -0
  96. dart/generated/api/task_doc_relationships/task_doc_relationships_list.py +0 -169
  97. dart/generated/api/task_kinds/__init__.py +0 -0
  98. dart/generated/api/task_kinds/task_kinds_list.py +0 -219
  99. dart/generated/api/tasks/__init__.py +0 -0
  100. dart/generated/api/tasks/tasks_list.py +0 -461
  101. dart/generated/api/tenants/__init__.py +0 -0
  102. dart/generated/api/transactions/__init__.py +0 -0
  103. dart/generated/api/user_dartboard_layouts/__init__.py +0 -0
  104. dart/generated/api/user_dartboard_layouts/user_dartboard_layouts_list.py +0 -169
  105. dart/generated/api/user_data/__init__.py +0 -0
  106. dart/generated/api/user_data/user_data_entity_retrieve.py +0 -580
  107. dart/generated/api/users/__init__.py +0 -0
  108. dart/generated/api/users/users_list.py +0 -229
  109. dart/generated/api/views/__init__.py +0 -0
  110. dart/generated/api/views/views_list.py +0 -199
  111. dart/generated/api/webhooks/__init__.py +0 -0
  112. dart/generated/models/attachment.py +0 -112
  113. dart/generated/models/attachment_create.py +0 -121
  114. dart/generated/models/attachment_update.py +0 -125
  115. dart/generated/models/bar_chart_adtl.py +0 -98
  116. dart/generated/models/brainstorm.py +0 -149
  117. dart/generated/models/brainstorm_create.py +0 -134
  118. dart/generated/models/brainstorm_state.py +0 -10
  119. dart/generated/models/brainstorm_update.py +0 -153
  120. dart/generated/models/burn_up_chart_adtl.py +0 -103
  121. dart/generated/models/chart.py +0 -208
  122. dart/generated/models/chart_aggregation.py +0 -10
  123. dart/generated/models/chart_type.py +0 -14
  124. dart/generated/models/comment_reaction.py +0 -84
  125. dart/generated/models/comment_reaction_create.py +0 -82
  126. dart/generated/models/comment_reaction_update.py +0 -87
  127. dart/generated/models/comment_update.py +0 -148
  128. dart/generated/models/dartboard_create.py +0 -315
  129. dart/generated/models/dartboard_kind.py +0 -12
  130. dart/generated/models/dartboard_update.py +0 -317
  131. dart/generated/models/dartboards_list_kind.py +0 -12
  132. dart/generated/models/dashboard.py +0 -185
  133. dart/generated/models/dashboard_create.py +0 -171
  134. dart/generated/models/dashboard_update.py +0 -173
  135. dart/generated/models/discord_integration.py +0 -72
  136. dart/generated/models/doc_source_type.py +0 -13
  137. dart/generated/models/docs_list_o_item.py +0 -15
  138. dart/generated/models/entity_name.py +0 -22
  139. dart/generated/models/event.py +0 -420
  140. dart/generated/models/event_actor.py +0 -19
  141. dart/generated/models/event_create.py +0 -158
  142. dart/generated/models/event_kind.py +0 -88
  143. dart/generated/models/event_subscription.py +0 -74
  144. dart/generated/models/event_subscription_update.py +0 -173
  145. dart/generated/models/filter_applicability.py +0 -22
  146. dart/generated/models/filter_assignee.py +0 -116
  147. dart/generated/models/filter_connector.py +0 -9
  148. dart/generated/models/filter_group.py +0 -112
  149. dart/generated/models/filter_search.py +0 -82
  150. dart/generated/models/filter_set.py +0 -116
  151. dart/generated/models/folder_create.py +0 -150
  152. dart/generated/models/folder_kind.py +0 -10
  153. dart/generated/models/folder_update.py +0 -152
  154. dart/generated/models/folders_list_kind.py +0 -10
  155. dart/generated/models/form.py +0 -147
  156. dart/generated/models/form_create.py +0 -141
  157. dart/generated/models/form_field.py +0 -144
  158. dart/generated/models/form_field_create.py +0 -129
  159. dart/generated/models/form_field_update.py +0 -132
  160. dart/generated/models/form_update.py +0 -142
  161. dart/generated/models/github_integration.py +0 -163
  162. dart/generated/models/github_integration_tenant_extension_status.py +0 -11
  163. dart/generated/models/google_data.py +0 -94
  164. dart/generated/models/icon_kind.py +0 -10
  165. dart/generated/models/layout.py +0 -167
  166. dart/generated/models/layout_config.py +0 -70
  167. dart/generated/models/layout_create.py +0 -130
  168. dart/generated/models/layout_kind.py +0 -11
  169. dart/generated/models/layout_kind_config_map.py +0 -56
  170. dart/generated/models/layout_update.py +0 -130
  171. dart/generated/models/line_chart_adtl.py +0 -72
  172. dart/generated/models/models_response.py +0 -671
  173. dart/generated/models/notification.py +0 -120
  174. dart/generated/models/notification_update.py +0 -100
  175. dart/generated/models/notion_integration.py +0 -90
  176. dart/generated/models/notion_integration_tenant_extension_status.py +0 -10
  177. dart/generated/models/number_chart_adtl.py +0 -77
  178. dart/generated/models/operation.py +0 -874
  179. dart/generated/models/operation_kind.py +0 -12
  180. dart/generated/models/operation_model_kind.py +0 -36
  181. dart/generated/models/option.py +0 -118
  182. dart/generated/models/option_create.py +0 -105
  183. dart/generated/models/option_update.py +0 -107
  184. dart/generated/models/paginated_comment_list.py +0 -122
  185. dart/generated/models/paginated_comment_reaction_list.py +0 -122
  186. dart/generated/models/paginated_dashboard_list.py +0 -122
  187. dart/generated/models/paginated_doc_list.py +0 -122
  188. dart/generated/models/paginated_folder_list.py +0 -122
  189. dart/generated/models/paginated_form_field_list.py +0 -122
  190. dart/generated/models/paginated_form_list.py +0 -122
  191. dart/generated/models/paginated_layout_list.py +0 -122
  192. dart/generated/models/paginated_option_list.py +0 -122
  193. dart/generated/models/paginated_property_list.py +0 -122
  194. dart/generated/models/paginated_relationship_kind_list.py +0 -122
  195. dart/generated/models/paginated_relationship_list.py +0 -122
  196. dart/generated/models/paginated_space_list.py +0 -122
  197. dart/generated/models/paginated_status_list.py +0 -122
  198. dart/generated/models/paginated_task_doc_relationship_list.py +0 -122
  199. dart/generated/models/paginated_task_kind_list.py +0 -122
  200. dart/generated/models/paginated_task_link_list.py +0 -122
  201. dart/generated/models/paginated_task_list.py +0 -122
  202. dart/generated/models/paginated_tenant_list.py +0 -122
  203. dart/generated/models/paginated_user_dartboard_layout_list.py +0 -122
  204. dart/generated/models/paginated_user_list.py +0 -122
  205. dart/generated/models/paginated_view_list.py +0 -122
  206. dart/generated/models/paginated_webhook_list.py +0 -122
  207. dart/generated/models/pie_chart_adtl.py +0 -69
  208. dart/generated/models/pie_chart_display_metric.py +0 -9
  209. dart/generated/models/properties_list_kind.py +0 -32
  210. dart/generated/models/property_.py +0 -153
  211. dart/generated/models/property_create.py +0 -137
  212. dart/generated/models/property_kind.py +0 -32
  213. dart/generated/models/property_update.py +0 -146
  214. dart/generated/models/relationship.py +0 -74
  215. dart/generated/models/relationship_create.py +0 -93
  216. dart/generated/models/relationship_kind.py +0 -123
  217. dart/generated/models/relationship_kind_create.py +0 -117
  218. dart/generated/models/relationship_kind_kind.py +0 -12
  219. dart/generated/models/relationship_kind_update.py +0 -119
  220. dart/generated/models/report_kind.py +0 -9
  221. dart/generated/models/request_body.py +0 -83
  222. dart/generated/models/response_body.py +0 -72
  223. dart/generated/models/saml_config.py +0 -77
  224. dart/generated/models/saml_config_tenant_extension_status.py +0 -9
  225. dart/generated/models/slack_integration.py +0 -90
  226. dart/generated/models/slack_integration_tenant_extension_status.py +0 -10
  227. dart/generated/models/sort.py +0 -66
  228. dart/generated/models/space.py +0 -311
  229. dart/generated/models/space_create.py +0 -340
  230. dart/generated/models/space_kind.py +0 -10
  231. dart/generated/models/space_update.py +0 -341
  232. dart/generated/models/sprint_mode.py +0 -9
  233. dart/generated/models/status.py +0 -141
  234. dart/generated/models/status_create.py +0 -125
  235. dart/generated/models/status_kind.py +0 -12
  236. dart/generated/models/status_update.py +0 -135
  237. dart/generated/models/statuses_list_kind.py +0 -12
  238. dart/generated/models/subscription.py +0 -9
  239. dart/generated/models/subtask_display_mode.py +0 -10
  240. dart/generated/models/summary_statistic_kind.py +0 -14
  241. dart/generated/models/table_chart_adtl.py +0 -98
  242. dart/generated/models/task_detail_mode.py +0 -10
  243. dart/generated/models/task_doc_relationship.py +0 -96
  244. dart/generated/models/task_doc_relationship_create.py +0 -74
  245. dart/generated/models/task_kind.py +0 -149
  246. dart/generated/models/task_kind_create.py +0 -144
  247. dart/generated/models/task_kind_kind.py +0 -9
  248. dart/generated/models/task_kind_update.py +0 -153
  249. dart/generated/models/task_kinds_list_kind.py +0 -9
  250. dart/generated/models/task_link.py +0 -131
  251. dart/generated/models/task_link_create.py +0 -152
  252. dart/generated/models/task_link_kind.py +0 -19
  253. dart/generated/models/task_link_update.py +0 -155
  254. dart/generated/models/task_notion_document.py +0 -196
  255. dart/generated/models/task_notion_document_block_children_map_type_0.py +0 -43
  256. dart/generated/models/task_notion_document_block_map_type_0.py +0 -43
  257. dart/generated/models/task_notion_document_page_map_type_0.py +0 -43
  258. dart/generated/models/task_properties.py +0 -43
  259. dart/generated/models/task_source_type.py +0 -31
  260. dart/generated/models/tenant.py +0 -386
  261. dart/generated/models/tenant_update.py +0 -166
  262. dart/generated/models/theme.py +0 -10
  263. dart/generated/models/transaction.py +0 -158
  264. dart/generated/models/transaction_kind.py +0 -74
  265. dart/generated/models/transaction_response.py +0 -96
  266. dart/generated/models/user_dartboard_layout.py +0 -66
  267. dart/generated/models/user_dartboard_layout_create.py +0 -74
  268. dart/generated/models/user_data_entity_retrieve_entity_kind.py +0 -32
  269. dart/generated/models/user_role.py +0 -12
  270. dart/generated/models/user_status.py +0 -12
  271. dart/generated/models/user_update.py +0 -210
  272. dart/generated/models/validation_error_response.py +0 -64
  273. dart/generated/models/validation_error_response_items.py +0 -43
  274. dart/generated/models/view_create.py +0 -213
  275. dart/generated/models/view_kind.py +0 -11
  276. dart/generated/models/view_update.py +0 -215
  277. dart/generated/models/webhook.py +0 -96
  278. dart/generated/models/webhook_create.py +0 -77
  279. dart/generated/models/webhook_update.py +0 -78
  280. dart/generated/models/zapier_integration.py +0 -66
  281. dart_tools-0.6.16.dist-info/RECORD +0 -257
  282. dart_tools-0.6.16.dist-info/dist/dart-tools-0.3.3.tar.gz +0 -0
  283. {dart_tools-0.6.16.dist-info → dart_tools-0.7.0.dist-info}/entry_points.txt +0 -0
  284. {dart_tools-0.6.16.dist-info → dart_tools-0.7.0.dist-info/licenses}/LICENSE +0 -0
  285. {dart_tools-0.6.16.dist-info → dart_tools-0.7.0.dist-info}/top_level.txt +0 -0
dart/dart.py CHANGED
@@ -5,8 +5,7 @@
5
5
 
6
6
  # Required for type hinting compatibility when using Python 3.9
7
7
  from __future__ import annotations
8
- from argparse import ArgumentParser
9
- from functools import wraps
8
+
10
9
  import json
11
10
  import os
12
11
  import random
@@ -15,46 +14,47 @@ import signal
15
14
  import string
16
15
  import subprocess
17
16
  import sys
17
+ from argparse import ArgumentParser
18
18
  from collections import defaultdict
19
19
  from datetime import timezone
20
+ from functools import wraps
20
21
  from importlib.metadata import version
21
- from typing import Any, Callable, NoReturn
22
+ from typing import Callable, NoReturn, TypeVar
22
23
  from webbrowser import open_new_tab
23
24
 
24
25
  import dateparser
25
- from pick import pick
26
- import requests
26
+ import httpx
27
27
  import platformdirs
28
+ from pick import pick
29
+
30
+ from dart.generated.types import Response
28
31
 
29
32
  from .exception import DartException
30
- from .generated import Client
33
+ from .generated import Client, api
31
34
  from .generated.models import (
32
- Dartboard,
33
- DartboardKind,
34
- DartboardUpdate,
35
- Folder,
36
- FolderKind,
37
- FolderUpdate,
38
- Operation,
39
- OperationKind,
40
- OperationModelKind,
35
+ Comment,
36
+ CommentCreate,
37
+ ConciseTask,
38
+ Doc,
39
+ DocCreate,
40
+ DocUpdate,
41
+ PaginatedConciseDocList,
42
+ PaginatedConciseTaskList,
41
43
  Priority,
42
- PropertyKind,
43
- RequestBody,
44
- SpaceKind,
45
- StatusKind,
46
- SprintMode,
47
44
  Task,
48
45
  TaskCreate,
49
- TaskSourceType,
50
46
  TaskUpdate,
51
- Transaction,
52
- TransactionKind,
47
+ UserSpaceConfiguration,
48
+ WrappedComment,
49
+ WrappedCommentCreate,
50
+ WrappedDoc,
51
+ WrappedDocCreate,
52
+ WrappedDocUpdate,
53
+ WrappedTask,
54
+ WrappedTaskCreate,
55
+ WrappedTaskUpdate,
53
56
  )
54
- from .generated.api.dartboards import dartboards_list
55
- from .generated.api.folders import folders_list
56
- from .generated.api.transactions import transactions_create
57
- from .order_manager import get_orders_between
57
+ from .generated.types import UNSET, Unset
58
58
 
59
59
  _APP = "dart-tools"
60
60
  _PROG = "dart"
@@ -64,49 +64,71 @@ _STAG_HOST = "https://stag.itsdart.com"
64
64
  _DEV_HOST = "http://localhost:5173"
65
65
  _HOST_MAP = {"prod": _PROD_HOST, "stag": _STAG_HOST, "dev": _DEV_HOST}
66
66
 
67
- _VERSION_CMD = "--version"
68
- _SET_HOST_CMD = "sethost"
67
+ # Service commands
69
68
  _LOGIN_CMD = "login"
70
- _CREATE_TASK_CMD = "createtask"
71
- _UPDATE_TASK_CMD = "updatetask"
72
- _BEGIN_TASK_CMD = "begintask"
69
+ _SET_HOST_CMD = "sethost"
70
+ _VERSION_CMD = "--version"
71
+ # Task commands
72
+ _CREATE_TASK_CMD = "task-create"
73
+ _UPDATE_TASK_CMD = "task-update"
74
+ _DELETE_TASK_CMD = "task-delete"
75
+ _BEGIN_TASK_CMD = "task-begin"
76
+ # Doc commands
77
+ _CREATE_DOC_CMD = "doc-create"
78
+ _UPDATE_DOC_CMD = "doc-update"
79
+ _DELETE_DOC_CMD = "doc-delete"
80
+ # Comment commands
81
+ _CREATE_COMMENT_CMD = "comment-create"
73
82
 
74
83
  _PROFILE_SETTINGS_URL_FRAG = "/?settings=account"
75
- _ROOT_API_URL_FRAG = "/api/v0"
76
- _USER_STATUS_URL_FRAG = _ROOT_API_URL_FRAG + "/user-status"
77
- _USER_DATA_URL_FRAG = _ROOT_API_URL_FRAG + "/user-data?mode=auto"
78
- _COPY_BRANCH_URL_FRAG = _ROOT_API_URL_FRAG + "/vcs/copy-branch-link"
79
- _REPLICATE_SPACE_URL_FRAG_FMT = _ROOT_API_URL_FRAG + "/spaces/replicate/{duid}"
80
- _REPLICATE_DARTBOARD_URL_FRAG_FMT = _ROOT_API_URL_FRAG + "/dartboards/replicate/{duid}"
84
+ _ROOT_PRIVATE_API_URL_FRAG = "/api/v0"
85
+ _ROOT_PUBLIC_API_URL_FRAG = f"{_ROOT_PRIVATE_API_URL_FRAG}/public"
86
+ _COPY_BRANCH_URL_FRAG = "/vcs/copy-branch-link"
81
87
 
82
88
  _AUTH_TOKEN_ENVVAR_KEY = "DART_TOKEN"
83
89
  _CONFIG_FPATH = platformdirs.user_config_path(_APP)
84
- _CLIENT_DUID_KEY = "clientDuid"
90
+ _CLIENT_ID_KEY = "clientId"
85
91
  _HOST_KEY = "host"
86
92
  _HOSTS_KEY = "hosts"
87
93
  _AUTH_TOKEN_KEY = "authToken"
88
94
 
89
- _DUID_CHARS = string.ascii_lowercase + string.ascii_uppercase + string.digits
95
+ _ID_CHARS = string.ascii_lowercase + string.ascii_uppercase + string.digits
90
96
  _NON_ALPHANUM_RE = re.compile(r"[^a-zA-Z0-9-]+")
91
97
  _REPEATED_DASH_RE = re.compile(r"-{2,}")
92
- _PRIORITY_MAP = {
98
+ _PRIORITY_MAP: dict[int, str] = {
93
99
  0: Priority.CRITICAL,
94
100
  1: Priority.HIGH,
95
101
  2: Priority.MEDIUM,
96
102
  3: Priority.LOW,
97
103
  }
98
104
  _SIZES = {1, 2, 3, 5, 8}
99
- _COMPLETED_STATUS_KINDS = {"Finished", "Canceled"}
100
105
 
101
106
  _VERSION = version(_APP)
102
107
  _AUTH_TOKEN_ENVVAR = os.environ.get(_AUTH_TOKEN_ENVVAR_KEY)
103
108
 
109
+
110
+ def _get_help_text(fn: Callable) -> str:
111
+ if fn.__doc__ is None:
112
+ raise ValueError(f"Function {fn.__name__} has no docstring.")
113
+ return fn.__doc__.split("\n")[0].lower()
114
+
115
+
116
+ _HELP_TEXT_TO_COMMAND = {
117
+ _CREATE_TASK_CMD: _get_help_text(api.create_task.sync_detailed),
118
+ _UPDATE_TASK_CMD: _get_help_text(api.update_task.sync_detailed),
119
+ _DELETE_TASK_CMD: _get_help_text(api.delete_task.sync_detailed),
120
+ _CREATE_DOC_CMD: _get_help_text(api.create_doc.sync_detailed),
121
+ _UPDATE_DOC_CMD: _get_help_text(api.update_doc.sync_detailed),
122
+ _DELETE_DOC_CMD: _get_help_text(api.delete_doc.sync_detailed),
123
+ _CREATE_COMMENT_CMD: _get_help_text(api.create_comment.sync_detailed),
124
+ }
125
+
104
126
  _is_cli = False
105
127
 
106
128
 
107
129
  # TODO dedupe these functions with other usages elsewhere
108
- def _make_duid() -> str:
109
- return "".join(random.choices(_DUID_CHARS, k=12))
130
+ def _make_id() -> str:
131
+ return "".join(random.choices(_ID_CHARS, k=12))
110
132
 
111
133
 
112
134
  def trim_slug_str(s: str, length: int, max_under: int | None = None) -> str:
@@ -123,33 +145,13 @@ def slugify_str(s: str, lower: bool = False, trim_kwargs: dict | None = None) ->
123
145
  lowered = s.lower() if lower else s
124
146
  formatted = _NON_ALPHANUM_RE.sub("-", lowered.replace("'", ""))
125
147
  formatted = _REPEATED_DASH_RE.sub("-", formatted).strip("-")
126
- return (
127
- trim_slug_str(formatted, **trim_kwargs)
128
- if trim_kwargs is not None
129
- else formatted
130
- )
148
+ return trim_slug_str(formatted, **trim_kwargs) if trim_kwargs is not None else formatted
131
149
 
132
150
 
133
151
  def _run_cmd(cmd: str) -> str:
134
152
  return subprocess.check_output(cmd, shell=True).decode()
135
153
 
136
154
 
137
- def _get_space_url(host: str, duid: str) -> str:
138
- return f"{host}/s/{duid}"
139
-
140
-
141
- def _get_dartboard_url(host: str, duid: str) -> str:
142
- return f"{host}/d/{duid}"
143
-
144
-
145
- def _get_task_url(host: str, duid: str) -> str:
146
- return f"{host}/t/{duid}"
147
-
148
-
149
- def _get_folder_url(host: str, duid: str) -> str:
150
- return f"{host}/f/{duid}"
151
-
152
-
153
155
  def _suppress_exception(fn: Callable) -> Callable:
154
156
  @wraps(fn)
155
157
  def wrapper(*args, **kwargs):
@@ -177,6 +179,37 @@ def _log(s: str) -> None:
177
179
  print(s)
178
180
 
179
181
 
182
+ T = TypeVar("T")
183
+
184
+
185
+ def _get_response_parsed(response: Response[T], not_found_message="Not found.") -> T:
186
+ if response.parsed is not None:
187
+ return response.parsed
188
+ if response.status_code in {401, 403}:
189
+ _auth_failure_exit()
190
+ elif response.status_code == 404:
191
+ _dart_exit(not_found_message)
192
+ try:
193
+ response_content = json.loads(response.content)
194
+ error_message = response_content.get("detail") or " ".join(response_content.get("errors", []))
195
+ _dart_exit(error_message)
196
+ except (json.JSONDecodeError, AttributeError):
197
+ _unknown_failure_exit()
198
+
199
+
200
+ def _handle_request_errors(fn: Callable) -> Callable:
201
+ @wraps(fn)
202
+ def wrapper(*args, **kwargs):
203
+ try:
204
+ return fn(*args, **kwargs)
205
+ except DartException as ex:
206
+ _dart_exit(ex)
207
+ except (httpx.TimeoutException, httpx.RequestError, httpx.ConnectError) as ex:
208
+ _dart_exit(f"Failed to execute API call. Reason: {ex}.")
209
+
210
+ return wrapper
211
+
212
+
180
213
  class _Config:
181
214
  def __init__(self):
182
215
  self._content = {}
@@ -187,14 +220,14 @@ class _Config:
187
220
  except OSError:
188
221
  pass
189
222
  self._content = {
190
- _CLIENT_DUID_KEY: _make_duid(),
223
+ _CLIENT_ID_KEY: _make_id(),
191
224
  _HOST_KEY: _PROD_HOST,
192
225
  _HOSTS_KEY: {},
193
226
  } | self._content
194
227
  self._content[_HOSTS_KEY] = defaultdict(dict, self._content[_HOSTS_KEY])
195
228
  self._write()
196
229
 
197
- def _write(self):
230
+ def _write(self) -> None:
198
231
  try:
199
232
  with open(_CONFIG_FPATH, "w+", encoding="UTF-8") as fout:
200
233
  json.dump(self._content, fout, indent=2)
@@ -202,176 +235,140 @@ class _Config:
202
235
  pass
203
236
 
204
237
  @property
205
- def client_duid(self):
206
- return self._content[_CLIENT_DUID_KEY]
238
+ def client_id(self) -> str:
239
+ return self._content[_CLIENT_ID_KEY]
207
240
 
208
241
  @property
209
- def host(self):
242
+ def host(self) -> str:
210
243
  return self._content[_HOST_KEY]
211
244
 
212
245
  @host.setter
213
- def host(self, v):
246
+ def host(self, v: str) -> None:
214
247
  self._content[_HOST_KEY] = v
215
248
  self._write()
216
249
 
217
- def get(self, k):
250
+ def get(self, k: str) -> str | None:
218
251
  return self._content[_HOSTS_KEY][self.host].get(k)
219
252
 
220
- def set(self, k, v):
253
+ def set(self, k: str, v: str) -> None:
221
254
  self._content[_HOSTS_KEY][self.host][k] = v
222
255
  self._write()
223
256
 
224
257
 
225
- class _Session:
258
+ class Dart:
226
259
  def __init__(self, config=None):
227
260
  self._config = config or _Config()
228
- self._session = requests.Session()
261
+ self._init_clients()
229
262
 
230
- def get_base_url(self):
263
+ def _init_clients(self) -> None:
264
+ self._private_api = Client(
265
+ base_url=self.get_base_url() + _ROOT_PRIVATE_API_URL_FRAG,
266
+ headers=self.get_headers(),
267
+ )
268
+ self._public_api = Client(
269
+ base_url=self.get_base_url() + _ROOT_PUBLIC_API_URL_FRAG,
270
+ headers=self.get_headers(),
271
+ )
272
+
273
+ def get_base_url(self) -> str:
231
274
  return self._config.host
232
275
 
233
- def get_client_duid(self):
234
- return self._config.client_duid
276
+ def get_client_id(self) -> str:
277
+ return self._config.client_id
235
278
 
236
- def get_auth_token(self):
279
+ def get_auth_token(self) -> str | None:
237
280
  result = self._config.get(_AUTH_TOKEN_KEY)
238
281
  if result is not None:
239
282
  return result
240
283
  return _AUTH_TOKEN_ENVVAR
241
284
 
242
- def get_headers(self):
285
+ def get_headers(self) -> dict[str, str]:
243
286
  result = {
244
287
  "Origin": self._config.host,
245
- "client-duid": self.get_client_duid(),
288
+ "client-duid": self.get_client_id(),
246
289
  }
247
290
  if (auth_token := self.get_auth_token()) is not None:
248
291
  result["Authorization"] = f"Bearer {auth_token}"
249
292
  return result
250
293
 
251
- def get(self, url_frag, *args, **kwargs):
252
- kwargs["headers"] = self.get_headers() | kwargs.get("headers", {})
253
- return self._session.get(self._config.host + url_frag, *args, **kwargs)
254
-
255
- def post(self, url_frag, *args, **kwargs):
256
- kwargs["headers"] = self.get_headers() | kwargs.get("headers", {})
257
- result = self._session.post(self._config.host + url_frag, *args, **kwargs)
258
- if result.status_code != 403:
259
- return result
260
- kwargs["headers"] = self.get_headers() | kwargs.get("headers", {})
261
- return self._session.post(self._config.host + url_frag, *args, **kwargs)
262
-
263
-
264
- class Dart(Client):
265
- def __init__(self, session=None):
266
- self._session = session or _Session()
267
- super().__init__(
268
- base_url=self._session.get_base_url(),
269
- headers=self._session.get_headers(),
270
- )
271
-
272
- def transact(self, operations: list[Operation], kind: TransactionKind):
273
- transaction = Transaction(
274
- duid=_make_duid(),
275
- kind=kind,
276
- operations=operations,
277
- )
278
- request_body = RequestBody(
279
- client_duid=self._session.get_client_duid(),
280
- items=[transaction],
281
- )
282
- return transactions_create.sync(
283
- client=self,
284
- body=request_body,
285
- )
286
-
287
-
288
- class UserBundle:
289
- def __init__(self, session):
290
- _log("Loading active tasks")
291
- response = session.get(_USER_DATA_URL_FRAG)
292
- _check_request_response_and_maybe_exit(response)
293
- self._raw = response.json()
294
- if not self.is_logged_in:
295
- _auth_failure_exit()
296
-
297
- @property
298
- def is_logged_in(self):
299
- return self._raw["isLoggedIn"]
300
-
301
- @property
302
- def user(self):
303
- return self._raw["user"]
304
-
305
- @property
306
- def users(self):
307
- return self._raw["users"]
308
-
309
- @property
310
- def properties(self):
311
- return self._raw["properties"]
312
-
313
- @property
314
- def task_kinds(self):
315
- return self._raw["taskKinds"]
316
-
317
- @property
318
- def default_dartboard(self):
319
- space = next(
320
- (e for e in self.spaces if e["kind"] == SpaceKind.WORKSPACE),
321
- self.spaces[0],
322
- )
323
- space_duid = space["duid"]
324
- dartboard_kind = (
325
- DartboardKind.ACTIVE
326
- if space["sprintMode"] == SprintMode.ANBA
327
- else DartboardKind.CUSTOM
328
- )
329
- return next(
330
- e
331
- for e in self.dartboards
332
- if e["spaceDuid"] == space_duid and e["kind"] == dartboard_kind
333
- )
334
-
335
- @property
336
- def default_statuses(self):
337
- default_status_property_duid = next(
338
- e["duid"]
339
- for e in self.properties
340
- if e["kind"] == PropertyKind.DEFAULT_STATUS
341
- )
342
- return [
343
- e
344
- for e in self._raw["statuses"]
345
- if e["propertyDuid"] == default_status_property_duid
346
- ]
347
-
348
- @property
349
- def default_tags(self):
350
- default_tags_property_duid = next(
351
- e["duid"] for e in self.properties if e["kind"] == PropertyKind.DEFAULT_TAGS
352
- )
353
- return [
354
- e
355
- for e in self._raw["options"]
356
- if e["propertyDuid"] == default_tags_property_duid
357
- ]
358
-
359
- @property
360
- def spaces(self):
361
- return self._raw["spaces"]
362
-
363
- @property
364
- def dartboards(self):
365
- return self._raw["dartboards"]
294
+ def is_logged_in(self) -> bool:
295
+ self._init_clients()
296
+ try:
297
+ config = api.get_config.sync(client=self._public_api)
298
+ if config is None:
299
+ return False
300
+ except:
301
+ return False
302
+ return True
366
303
 
367
- @property
368
- def tasks(self):
369
- return self._raw["tasks"]
304
+ @_handle_request_errors
305
+ def get_config(self) -> UserSpaceConfiguration:
306
+ response = api.get_config.sync_detailed(client=self._public_api)
307
+ return _get_response_parsed(response)
308
+
309
+ @_handle_request_errors
310
+ def create_task(self, body: WrappedTaskCreate) -> WrappedTask:
311
+ response = api.create_task.sync_detailed(client=self._public_api, body=body)
312
+ return _get_response_parsed(response)
313
+
314
+ @_handle_request_errors
315
+ def retrieve_task(self, id: str) -> WrappedTask:
316
+ response = api.retrieve_task.sync_detailed(id, client=self._public_api)
317
+ return _get_response_parsed(response, not_found_message=f"Task with ID {id} not found.")
318
+
319
+ @_handle_request_errors
320
+ def update_task(self, id: str, body: WrappedTaskUpdate) -> WrappedTask:
321
+ response = api.update_task.sync_detailed(id, client=self._public_api, body=body)
322
+ return _get_response_parsed(response, not_found_message=f"Task with ID {id} not found.")
323
+
324
+ @_handle_request_errors
325
+ def delete_task(self, id: str) -> WrappedTask:
326
+ response = api.delete_task.sync_detailed(id, client=self._public_api)
327
+ return _get_response_parsed(response, not_found_message=f"Task with ID {id} not found.")
328
+
329
+ @_handle_request_errors
330
+ def list_tasks(self, **kwargs) -> PaginatedConciseTaskList:
331
+ response = api.list_tasks.sync_detailed(client=self._public_api, **kwargs)
332
+ return _get_response_parsed(response)
333
+
334
+ @_handle_request_errors
335
+ def create_comment(self, body: WrappedCommentCreate) -> WrappedComment:
336
+ response = api.create_comment.sync_detailed(client=self._public_api, body=body)
337
+ return _get_response_parsed(response)
338
+
339
+ @_handle_request_errors
340
+ def create_doc(self, body: WrappedDocCreate) -> WrappedDoc:
341
+ response = api.create_doc.sync_detailed(client=self._public_api, body=body)
342
+ return _get_response_parsed(response)
343
+
344
+ @_handle_request_errors
345
+ def retrieve_doc(self, id: str) -> WrappedDoc:
346
+ response = api.retrieve_doc.sync_detailed(id, client=self._public_api)
347
+ return _get_response_parsed(response, not_found_message=f"Doc with ID {id} not found.")
348
+
349
+ @_handle_request_errors
350
+ def update_doc(self, id: str, body: WrappedDocUpdate) -> WrappedDoc:
351
+ response = api.update_doc.sync_detailed(id, client=self._public_api, body=body)
352
+ return _get_response_parsed(response, not_found_message=f"Doc with ID {id} not found.")
353
+
354
+ @_handle_request_errors
355
+ def delete_doc(self, id: str) -> WrappedDoc:
356
+ response = api.delete_doc.sync_detailed(id, client=self._public_api)
357
+ return _get_response_parsed(response, not_found_message=f"Doc with ID {id} not found.")
358
+
359
+ @_handle_request_errors
360
+ def list_docs(self, **kwargs) -> PaginatedConciseDocList:
361
+ response = api.list_docs.sync_detailed(client=self._public_api, **kwargs)
362
+ return _get_response_parsed(response)
363
+
364
+ @_handle_request_errors
365
+ def copy_branch_link(self, id: str) -> None:
366
+ self._private_api.get_httpx_client().post(_COPY_BRANCH_URL_FRAG, json={"duid": id})
370
367
 
371
368
 
372
369
  class _Git:
373
370
  @staticmethod
374
- def _cmd_succeeds(cmd):
371
+ def _cmd_succeeds(cmd: str) -> bool:
375
372
  try:
376
373
  _run_cmd(f"{cmd} 2>&1")
377
374
  except subprocess.CalledProcessError as ex:
@@ -381,29 +378,29 @@ class _Git:
381
378
  return True
382
379
 
383
380
  @staticmethod
384
- def make_task_name(email, task):
381
+ def make_task_name(email: str, task: ConciseTask | Task) -> str:
385
382
  username = slugify_str(email.split("@")[0], lower=True)
386
383
  title = slugify_str(task.title, lower=True)
387
- return trim_slug_str(f"{username}/{task.duid}-{title}", length=60)
384
+ return trim_slug_str(f"{username}/{task.id}-{title}", length=60)
388
385
 
389
386
  @staticmethod
390
- def get_current_branch():
387
+ def get_current_branch() -> str:
391
388
  return _run_cmd("git rev-parse --abbrev-ref HEAD").strip()
392
389
 
393
390
  @staticmethod
394
- def ensure_in_repo():
391
+ def ensure_in_repo() -> None:
395
392
  if _Git._cmd_succeeds("git rev-parse --is-inside-work-tree"):
396
393
  return
397
394
  _dart_exit("You are not in a git repo.")
398
395
 
399
396
  @staticmethod
400
- def ensure_no_unstaged_changes():
397
+ def ensure_no_unstaged_changes() -> None:
401
398
  if _run_cmd("git status --porcelain") == "":
402
399
  return
403
400
  _dart_exit("You have uncommitted changes. Please commit or stash them.")
404
401
 
405
402
  @staticmethod
406
- def ensure_on_main_or_intended():
403
+ def ensure_on_main_or_intended() -> None:
407
404
  branch = _Git.get_current_branch()
408
405
  if branch == "main":
409
406
  return
@@ -419,11 +416,11 @@ class _Git:
419
416
  _run_cmd("git checkout main")
420
417
 
421
418
  @staticmethod
422
- def branch_exists(branch):
419
+ def branch_exists(branch: str) -> bool:
423
420
  return _Git._cmd_succeeds(f"git rev-parse --verify {branch}")
424
421
 
425
422
  @staticmethod
426
- def checkout_branch(branch):
423
+ def checkout_branch(branch: str) -> None:
427
424
  if _Git.branch_exists(branch):
428
425
  _run_cmd(f"git checkout {branch}")
429
426
  return
@@ -456,39 +453,17 @@ def set_host(host: str) -> bool:
456
453
 
457
454
 
458
455
  def _auth_failure_exit() -> NoReturn:
459
- _dart_exit(f"Not logged in, run\n\n {_PROG} {_LOGIN_CMD}\n\nto log in.")
456
+ _dart_exit(
457
+ f"Not logged in, run\n\n {_PROG} {_LOGIN_CMD}\n\nto log in."
458
+ if _is_cli
459
+ else "Not logged in, either run dart.login(token) or save the token into the DART_TOKEN environment variable."
460
+ )
460
461
 
461
462
 
462
463
  def _unknown_failure_exit() -> NoReturn:
463
464
  _dart_exit("Unknown failure, email\n\n support@itsdart.com\n\nfor help.")
464
465
 
465
466
 
466
- def _check_request_response_and_maybe_exit(response) -> None:
467
- try:
468
- response.raise_for_status()
469
- except requests.exceptions.HTTPError:
470
- if response.status_code in {401, 403}:
471
- _auth_failure_exit()
472
- _unknown_failure_exit()
473
-
474
-
475
- def _parse_transaction_response_and_maybe_exit(
476
- response, model_kind: str, duid: str
477
- ) -> Any:
478
- if (
479
- response is None
480
- or not hasattr(response, "results")
481
- or len(response.results) == 0
482
- or not response.results[0].success
483
- ):
484
- _unknown_failure_exit()
485
- models = getattr(response.results[0].models, f"{model_kind}s")
486
- model = next((e for e in models if e.duid == duid), None)
487
- if model is None:
488
- _unknown_failure_exit()
489
- return model
490
-
491
-
492
467
  def print_version() -> str:
493
468
  result = f"dart-tools version {_VERSION}"
494
469
  _log(result)
@@ -503,9 +478,7 @@ def print_version_update_message_maybe() -> None:
503
478
  .split("\n", maxsplit=1)[0]
504
479
  .strip()
505
480
  )
506
- if latest == _VERSION or [int(e) for e in latest.split(".")] <= [
507
- int(e) for e in _VERSION.split(".")
508
- ]:
481
+ if latest == _VERSION or [int(e) for e in latest.split(".")] <= [int(e) for e in _VERSION.split(".")]:
509
482
  return
510
483
 
511
484
  _log(
@@ -513,15 +486,9 @@ def print_version_update_message_maybe() -> None:
513
486
  )
514
487
 
515
488
 
516
- def _get_is_logged_in(session: _Session) -> bool:
517
- response = session.get(_USER_STATUS_URL_FRAG)
518
- return response.json().get("isLoggedIn", False)
519
-
520
-
521
489
  def is_logged_in(should_raise: bool = False) -> bool:
522
- session = _Session()
523
-
524
- result = _get_is_logged_in(session)
490
+ dart = Dart()
491
+ result = dart.is_logged_in()
525
492
 
526
493
  if not result and should_raise:
527
494
  _auth_failure_exit()
@@ -531,21 +498,19 @@ def is_logged_in(should_raise: bool = False) -> bool:
531
498
 
532
499
  def login(token: str | None = None) -> bool:
533
500
  config = _Config()
534
- session = _Session(config)
501
+ dart = Dart(config=config)
535
502
 
536
503
  _log("Log in to Dart")
537
504
  if token is None:
538
505
  if not _is_cli:
539
506
  _dart_exit("Login failed, token is required.")
540
- _log(
541
- "Dart is opening in your browser, log in if needed and copy your authentication token from the page"
542
- )
507
+ _log("Dart is opening in your browser, log in if needed and copy your authentication token from the page")
543
508
  open_new_tab(config.host + _PROFILE_SETTINGS_URL_FRAG)
544
509
  token = input("Token: ")
545
510
 
546
511
  config.set(_AUTH_TOKEN_KEY, token)
547
512
 
548
- worked = _get_is_logged_in(session)
513
+ worked = dart.is_logged_in()
549
514
  if not worked:
550
515
  _dart_exit("Invalid token.")
551
516
 
@@ -553,507 +518,207 @@ def login(token: str | None = None) -> bool:
553
518
  return True
554
519
 
555
520
 
556
- def _begin_task(
557
- config: _Config, session: _Session, user_email: str, get_task: Callable
558
- ) -> bool:
521
+ def _begin_task(dart: Dart, email: str, task: ConciseTask | Task) -> bool:
559
522
  _Git.ensure_in_repo()
560
523
  _Git.ensure_no_unstaged_changes()
561
524
  _Git.ensure_on_main_or_intended()
562
525
 
563
- task = get_task()
564
-
565
- response = session.post(_COPY_BRANCH_URL_FRAG, json={"duid": task.duid})
566
- _check_request_response_and_maybe_exit(response)
526
+ dart = Dart()
527
+ dart.copy_branch_link(task.id)
567
528
 
568
- branch_name = _Git.make_task_name(user_email, task)
529
+ branch_name = _Git.make_task_name(email, task)
569
530
  _Git.checkout_branch(branch_name)
570
531
 
571
- _log(
572
- f"Started work on\n\n {task.title}\n {_get_task_url(config.host, task.duid)}\n"
573
- )
532
+ _log(f"Started work on\n\n {task.title}\n {task.html_url}\n")
574
533
  return True
575
534
 
576
535
 
577
536
  def begin_task() -> bool:
578
- config = _Config()
579
- session = _Session(config)
580
-
581
- user_bundle = UserBundle(session)
582
- user = user_bundle.user
583
-
584
- def _get_task():
585
- user_duid = user["duid"]
586
- active_duid = user_bundle.default_dartboard["duid"]
587
- incomplete_status_duids = {
588
- e["duid"]
589
- for e in user_bundle.default_statuses
590
- if e["kind"] not in _COMPLETED_STATUS_KINDS
591
- }
592
- filtered_tasks = [
593
- e
594
- for e in user_bundle.tasks
595
- if not e["inTrash"]
596
- and e["dartboardDuid"] == active_duid
597
- and user_duid in e["assigneeDuids"]
598
- and e["statusDuid"] in incomplete_status_duids
599
- and e["drafterDuid"] is None
600
- ]
601
- filtered_tasks.sort(key=lambda e: e["order"])
537
+ dart = Dart()
538
+ config = dart.get_config()
539
+ user = config.user
540
+ filtered_tasks = dart.list_tasks(assignee=user.email, is_completed=False).results
602
541
 
603
- if len(filtered_tasks) == 0:
604
- _dart_exit("No active, incomplete tasks found.")
542
+ if not filtered_tasks:
543
+ _dart_exit("No active, incomplete tasks found.")
605
544
 
606
- picked_idx = pick(
607
- [e["title"] for e in filtered_tasks],
608
- "Which of your active, incomplete tasks are you beginning work on?",
609
- "→",
610
- )[1]
611
- assert isinstance(picked_idx, int)
612
- return TaskCreate.from_dict(filtered_tasks[picked_idx])
545
+ picked_idx = pick(
546
+ [e.title for e in filtered_tasks],
547
+ "Which of your active, incomplete tasks are you beginning work on?",
548
+ "→",
549
+ )[1]
550
+ assert isinstance(picked_idx, int)
613
551
 
614
- _begin_task(config, session, user["email"], _get_task)
552
+ _begin_task(dart, user.email, filtered_tasks[picked_idx])
615
553
 
616
554
  _log("Done.")
617
555
  return True
618
556
 
619
557
 
558
+ def _normalize_priority(priority_int: int | None | Unset) -> str | None | Unset:
559
+ if priority_int in (None, UNSET):
560
+ return priority_int
561
+
562
+ if priority_int not in _PRIORITY_MAP:
563
+ _dart_exit(f"Invalid priority {priority_int}. Valid values are {list(_PRIORITY_MAP.keys())}.")
564
+
565
+ return _PRIORITY_MAP[priority_int]
566
+
567
+
568
+ def _get_due_at_from_str_arg(due_at_str: str | None | Unset) -> str | None | Unset:
569
+ if due_at_str in (None, UNSET):
570
+ return due_at_str
571
+
572
+ due_at = dateparser.parse(due_at_str)
573
+ if not due_at:
574
+ _dart_exit(f"Could not parse due date '{due_at_str}'.")
575
+ due_at = due_at.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=timezone.utc).isoformat()
576
+
577
+ return due_at
578
+
579
+
620
580
  def create_task(
621
581
  title: str,
622
582
  *,
623
- dartboard_duid: str | None = None,
624
- dartboard_title: str | None = None,
625
- kind_title: str | None = None,
626
- status_title: str | None = None,
627
- assignee_emails: list[str] | None = None,
628
- tag_titles: list[str] | None = None,
629
- priority_int: int | None = None,
630
- size_int: int | None = None,
631
- due_at_str: str | None = None,
583
+ dartboard_title: str | Unset = UNSET,
584
+ status_title: str | Unset = UNSET,
585
+ assignee_emails: list[str] | Unset = UNSET,
586
+ tag_titles: list[str] | Unset = UNSET,
587
+ priority_int: int | None | Unset = UNSET,
588
+ size_int: int | None | Unset = UNSET,
589
+ due_at_str: str | None | Unset = UNSET,
632
590
  should_begin: bool = False,
633
591
  ) -> Task:
634
- config = _Config()
635
- session = _Session(config)
636
- dart = Dart(session)
637
-
638
- user_bundle = UserBundle(session)
639
-
640
- user = user_bundle.user
641
- user_duid = user["duid"]
642
-
643
- dartboards = user_bundle.dartboards
644
- if dartboard_duid is None:
645
- if dartboard_title is not None:
646
- dartboard_title_norm = dartboard_title.strip().lower()
647
- dartboard = next(
648
- (
649
- e
650
- for e in dartboards
651
- if dartboard_title_norm in {e["title"].lower(), e["kind"].lower()}
652
- ),
653
- None,
654
- )
655
- if dartboard is None:
656
- _dart_exit(f"No dartboard found with title '{dartboard_title}'.")
657
- else:
658
- dartboard = user_bundle.default_dartboard
659
- dartboard_duid = dartboard["duid"]
660
-
661
- orders = [
662
- e["order"] for e in user_bundle.tasks if e["dartboardDuid"] == dartboard_duid
663
- ]
664
- first_order = min(orders) if len(orders) > 0 else None
665
- order = get_orders_between(None, first_order, 1)[0]
666
-
667
- kinds = user_bundle.task_kinds
668
- if kind_title is not None:
669
- kind_title_norm = kind_title.strip().lower()
670
- kind = next((e for e in kinds if e["title"].lower() == kind_title_norm), None)
671
- if kind is None:
672
- _dart_exit(f"No status found with title '{status_title}'.")
673
- else:
674
- kind = next(e for e in kinds if e["locked"])
675
- kind_duid = kind["duid"]
676
-
677
- statuses = user_bundle.default_statuses
678
- if status_title is not None:
679
- status_title_norm = status_title.strip().lower()
680
- status = next(
681
- (e for e in statuses if e["title"].lower() == status_title_norm), None
682
- )
683
- if status is None:
684
- _dart_exit(f"No status found with title '{status_title}'.")
685
- else:
686
- status = next(
687
- e for e in statuses if e["kind"] == StatusKind.UNSTARTED and e["locked"]
688
- )
689
- status_duid = status["duid"]
690
-
691
- users = user_bundle.users
692
- user_emails_to_duids = {e["email"]: e["duid"] for e in users}
693
- assignee_duids = []
694
- subscriber_duids = []
695
- if assignee_emails is not None:
696
- for assignee_email in assignee_emails:
697
- assignee_email_norm = assignee_email.strip().lower()
698
- if assignee_email_norm not in user_emails_to_duids:
699
- _dart_exit(f"No user found with email '{assignee_email}'.")
700
- assignee_duids.append(user_emails_to_duids[assignee_email_norm])
701
- subscriber_duids.append(user_emails_to_duids[assignee_email_norm])
702
- else:
703
- assignee_duids.append(user_duid)
704
- assignee_duids = list(set(assignee_duids))
705
- subscriber_duids.append(user_duid)
706
- subscriber_duids = list(set(subscriber_duids))
707
-
708
- tags = user_bundle.default_tags
709
- tag_titles_to_duids = {e["title"]: e["duid"] for e in tags}
710
- tag_duids = []
711
- if tag_titles is not None:
712
- for tag_title in tag_titles:
713
- tag_title_norm = tag_title.strip().lower()
714
- if tag_title_norm not in tag_titles_to_duids:
715
- _dart_exit(f"No tag found with title '{tag_title}'.")
716
- tag_duids.append(tag_titles_to_duids[tag_title_norm])
717
-
718
- priority = None
719
- if priority_int is not None:
720
- priority = _PRIORITY_MAP[priority_int]
721
-
722
- size = size_int
723
-
724
- due_at = None
725
- if due_at_str is not None:
726
- due_at = dateparser.parse(due_at_str)
727
- if due_at is None:
728
- _dart_exit(f"Could not parse due date '{due_at_str}'.")
729
- due_at = due_at.replace(
730
- hour=9, minute=0, second=0, microsecond=0, tzinfo=timezone.utc
592
+ dart = Dart()
593
+ task_create = WrappedTaskCreate(
594
+ item=TaskCreate(
595
+ title,
596
+ dartboard=dartboard_title,
597
+ status=status_title,
598
+ assignees=assignee_emails if assignee_emails is not None else UNSET,
599
+ tags=tag_titles if tag_titles is not None else UNSET,
600
+ priority=_normalize_priority(priority_int),
601
+ size=size_int,
602
+ due_at=_get_due_at_from_str_arg(due_at_str),
731
603
  )
732
-
733
- task_create = TaskCreate(
734
- duid=_make_duid(),
735
- source_type=TaskSourceType.CLI,
736
- drafter_duid=None,
737
- dartboard_duid=dartboard_duid,
738
- order=order,
739
- kind_duid=kind_duid,
740
- title=title,
741
- status_duid=status_duid,
742
- assignee_duids=assignee_duids,
743
- subscriber_duids=subscriber_duids,
744
- tag_duids=tag_duids,
745
- priority=priority,
746
- size=size,
747
- due_at=due_at,
748
- )
749
- task_create_op = Operation(
750
- model=OperationModelKind.TASK,
751
- kind=OperationKind.CREATE,
752
- data=task_create,
753
604
  )
754
- response = dart.transact([task_create_op], TransactionKind.TASK_CREATE)
755
- task = _parse_transaction_response_and_maybe_exit(
756
- response, OperationModelKind.TASK, task_create.duid
757
- )
758
-
759
- _log(f"Created task {task.title} at {_get_task_url(config.host, task.duid)}")
605
+ task = dart.create_task(task_create).item
606
+ _log(f"Created task (ID: {task.id}) {task.title} at {task.html_url}")
760
607
 
761
608
  if should_begin:
762
- _begin_task(config, session, user["email"], lambda: task_create)
609
+ user = dart.get_config().user
610
+ _begin_task(dart, user.email, task)
763
611
 
764
612
  _log("Done.")
765
613
  return task
766
614
 
767
615
 
768
616
  def update_task(
769
- duid: str,
617
+ id: str,
770
618
  *,
771
- title: str | None = None,
772
- dartboard_duid: str | None = None,
773
- dartboard_title: str | None = None,
774
- status_title: str | None = None,
775
- assignee_emails: list[str] | None = None,
776
- tag_titles: list[str] | None = None,
777
- priority_int: int | None = None,
778
- size_int: int | None = None,
779
- due_at_str: str | None = None,
619
+ title: Unset | str = UNSET,
620
+ dartboard_title: str | Unset = UNSET,
621
+ status_title: str | Unset = UNSET,
622
+ assignee_emails: list[str] | Unset = UNSET,
623
+ tag_titles: list[str] | Unset = UNSET,
624
+ priority_int: int | None | Unset = UNSET,
625
+ size_int: int | None | Unset = UNSET,
626
+ due_at_str: str | None | Unset = UNSET,
780
627
  ) -> Task:
781
- config = _Config()
782
- session = _Session(config)
783
- dart = Dart(session)
784
-
785
- user_bundle = UserBundle(session)
786
-
787
- user = user_bundle.user
788
- user_duid = user["duid"]
789
-
790
- tasks = user_bundle.tasks
791
- old_task = next((e for e in tasks if e["duid"] == duid), None)
792
- if old_task is None:
793
- _dart_exit(f"No task found with DUID '{duid}'.")
794
-
795
- task_update_kwargs = {"duid": duid}
796
-
797
- if title is not None:
798
- task_update_kwargs["title"] = title
799
-
800
- dartboards = user_bundle.dartboards
801
- if dartboard_duid is not None:
802
- task_update_kwargs["dartboard_duid"] = dartboard_duid
803
- elif dartboard_title is not None:
804
- dartboard_title_norm = dartboard_title.strip().lower()
805
- dartboard = next(
806
- (
807
- e
808
- for e in dartboards
809
- if dartboard_title_norm in {e["title"].lower(), e["kind"].lower()}
810
- ),
811
- None,
812
- )
813
- if dartboard is None:
814
- _dart_exit(f"No dartboard found with title '{dartboard_title}'.")
815
- dartboard_duid = dartboard["duid"]
816
- if dartboard_duid != old_task["dartboardDuid"]:
817
- task_update_kwargs["dartboard_duid"] = dartboard_duid
818
-
819
- statuses = user_bundle.default_statuses
820
- if status_title is not None:
821
- status_title_norm = status_title.strip().lower()
822
- status = next(
823
- (e for e in statuses if e["title"].lower() == status_title_norm), None
824
- )
825
- if status is None:
826
- _dart_exit(f"No status found with title '{status_title}'.")
827
- status_duid = status["duid"]
828
- if status_duid != old_task["statusDuid"]:
829
- task_update_kwargs["status_duid"] = status_duid
830
-
831
- users = user_bundle.users
832
- user_emails_to_duids = {e["email"]: e["duid"] for e in users}
833
- subscriber_duids = []
834
- if assignee_emails is not None:
835
- assignee_duids = []
836
- for assignee_email in assignee_emails:
837
- assignee_email_norm = assignee_email.strip().lower()
838
- if assignee_email_norm not in user_emails_to_duids:
839
- _dart_exit(f"No user found with email '{assignee_email}'.")
840
- assignee_duids.append(user_emails_to_duids[assignee_email_norm])
841
- subscriber_duids.append(user_emails_to_duids[assignee_email_norm])
842
- assignee_duids = sorted(set(assignee_duids))
843
- if assignee_duids != old_task["assigneeDuids"]:
844
- task_update_kwargs["assignee_duids"] = assignee_duids
845
-
846
- # TODO do add to list operation rather than replace
847
- subscriber_duids = list(
848
- set(old_task["subscriberDuids"]) | set(subscriber_duids) | {user_duid}
849
- )
850
- if subscriber_duids != old_task["subscriberDuids"]:
851
- task_update_kwargs["subscriber_duids"] = subscriber_duids
852
-
853
- tags = user_bundle.default_tags
854
- tag_titles_to_duids = {e["title"]: e["duid"] for e in tags}
855
- if tag_titles is not None:
856
- tag_duids = []
857
- for tag_title in tag_titles:
858
- tag_title_norm = tag_title.strip().lower()
859
- if tag_title_norm not in tag_titles_to_duids:
860
- _dart_exit(f"No tag found with title '{tag_title}'.")
861
- tag_duids.append(tag_titles_to_duids[tag_title_norm])
862
- task_update_kwargs["tag_duids"] = tag_duids
863
-
864
- # TODO add a way for optional stuff to be removed
865
- if priority_int is not None:
866
- priority = _PRIORITY_MAP[priority_int]
867
- if priority != old_task["priority"]:
868
- task_update_kwargs["priority"] = priority
869
-
870
- if size_int is not None:
871
- size = size_int
872
- if size != old_task["size"]:
873
- task_update_kwargs["size"] = size
874
-
875
- if due_at_str is not None:
876
- due_at = dateparser.parse(due_at_str)
877
- if due_at is None:
878
- _dart_exit(f"Could not parse due date '{due_at_str}'.")
879
- due_at = due_at.replace(
880
- hour=9, minute=0, second=0, microsecond=0, tzinfo=timezone.utc
628
+ dart = Dart()
629
+ task_update = WrappedTaskUpdate(
630
+ item=TaskUpdate(
631
+ id,
632
+ title=title,
633
+ dartboard=dartboard_title,
634
+ status=status_title,
635
+ assignees=assignee_emails if assignee_emails is not None else UNSET,
636
+ tags=tag_titles if tag_titles is not None else UNSET,
637
+ priority=_normalize_priority(priority_int),
638
+ size=size_int,
639
+ due_at=_get_due_at_from_str_arg(due_at_str),
881
640
  )
882
- due_at = due_at.isoformat()[:-6] + ".000Z"
883
- if due_at != old_task["dueAt"]:
884
- task_update_kwargs["due_at"] = due_at
885
-
886
- task_update = TaskUpdate(**task_update_kwargs)
887
- task_update_op = Operation(
888
- model=OperationModelKind.TASK,
889
- kind=OperationKind.UPDATE,
890
- data=task_update,
891
- )
892
- response = dart.transact([task_update_op], TransactionKind.TASK_UPDATE)
893
- task = _parse_transaction_response_and_maybe_exit(
894
- response, OperationModelKind.TASK, task_update.duid
895
641
  )
642
+ task = dart.update_task(id, task_update).item
896
643
 
897
- _log(f"Updated task {task.title} at {_get_task_url(config.host, task.duid)}")
644
+ _log(f"Updated task (ID: {task.id}) {task.title} at {task.html_url}")
898
645
  _log("Done.")
899
646
  return task
900
647
 
901
648
 
902
- def replicate_space(
903
- duid: str,
904
- *,
905
- title: str | None = None,
906
- abrev: str | None = None,
907
- color_hex: str | None = None,
908
- accessible_by_team: bool | None = None,
909
- accessor_duids: list[str] | None = None,
910
- ) -> str:
911
- config = _Config()
912
- session = _Session(config)
913
-
914
- content = {}
915
- if title is not None:
916
- content["title"] = title
917
- if abrev is not None:
918
- content["abrev"] = abrev
919
- if color_hex is not None:
920
- content["colorHex"] = color_hex
921
- if accessible_by_team is not None:
922
- content["accessibleByTeam"] = accessible_by_team
923
- if accessor_duids is not None:
924
- content["accessorIds"] = accessor_duids
925
- response = session.post(
926
- _REPLICATE_SPACE_URL_FRAG_FMT.format(duid=duid), json=content
927
- )
928
- _check_request_response_and_maybe_exit(response)
929
-
930
- space_duid = response.json()["duid"]
931
-
932
- _log(f"Replicated space at {_get_space_url(config.host, space_duid)}")
933
- _log("Done.")
934
- return space_duid
935
-
936
-
937
- def get_dartboards(space_duid: str, include_special: bool = False) -> list[Dartboard]:
649
+ def delete_task(id: str) -> Task:
938
650
  dart = Dart()
651
+ task = dart.delete_task(id).item
939
652
 
940
- response = dartboards_list.sync(client=dart, space_duid=space_duid)
941
- dartboards = response.results if response is not None else []
942
- if not include_special:
943
- dartboards = [e for e in dartboards if e.kind == DartboardKind.CUSTOM]
944
-
945
- _log(f"Got {len(dartboards)} dartboards")
653
+ _log(f"Deleted task (ID: {task.id}) {task.title} at {task.html_url}")
946
654
  _log("Done.")
947
- return dartboards
948
-
949
-
950
- def replicate_dartboard(duid: str, *, title: str | None = None) -> str:
951
- config = _Config()
952
- session = _Session(config)
655
+ return task
953
656
 
954
- content = {}
955
- if title is not None:
956
- content["title"] = title
957
- response = session.post(
958
- _REPLICATE_DARTBOARD_URL_FRAG_FMT.format(duid=duid), json=content
959
- )
960
- _check_request_response_and_maybe_exit(response)
961
657
 
962
- dartboard_duid = response.json()["duid"]
658
+ def create_doc(
659
+ title: str,
660
+ *,
661
+ folder: str | Unset = UNSET,
662
+ text: str | Unset = UNSET,
663
+ ) -> Doc:
664
+ dart = Dart()
665
+ doc_create = WrappedDocCreate(item=DocCreate(title=title, folder=folder, text=text))
666
+ doc = dart.create_doc(doc_create).item
963
667
 
964
- _log(f"Replicated dartboard at {_get_dartboard_url(config.host, dartboard_duid)}")
668
+ _log(f"Created doc (ID: {doc.id}) {doc.title} at {doc.html_url}")
965
669
  _log("Done.")
966
- return dartboard_duid
670
+ return doc
967
671
 
968
672
 
969
- def update_dartboard(
970
- duid: str,
673
+ def update_doc(
674
+ id: str,
971
675
  *,
972
- title: str | None = None,
973
- color_hex: str | None = None,
974
- ) -> Dartboard:
975
- config = _Config()
976
- session = _Session(config)
977
- dart = Dart(session)
978
-
979
- dartboard_update_kwargs = {"duid": duid}
980
-
981
- if title is not None:
982
- dartboard_update_kwargs["title"] = title
983
- if color_hex is not None:
984
- dartboard_update_kwargs["color_hex"] = color_hex
985
-
986
- dartboard_update = DartboardUpdate(**dartboard_update_kwargs)
987
- dartboard_update_op = Operation(
988
- model=OperationModelKind.DARTBOARD,
989
- kind=OperationKind.UPDATE,
990
- data=dartboard_update,
991
- )
992
- response = dart.transact([dartboard_update_op], TransactionKind.DARTBOARD_UPDATE)
993
- dartboard = _parse_transaction_response_and_maybe_exit(
994
- response, OperationModelKind.DARTBOARD, dartboard_update.duid
995
- )
676
+ title: str,
677
+ folder: str | Unset = UNSET,
678
+ text: str | Unset = UNSET,
679
+ ) -> Doc:
680
+ dart = Dart()
681
+ doc_update = WrappedDocUpdate(item=DocUpdate(id, title=title, folder=folder, text=text))
682
+ doc = dart.update_doc(id, doc_update).item
996
683
 
997
- _log(
998
- f"Updated dartboard {dartboard.title} at {_get_dartboard_url(config.host, dartboard.duid)}"
999
- )
684
+ _log(f"Updated doc (ID: {doc.id}) {doc.title} at {doc.html_url}")
1000
685
  _log("Done.")
1001
- return dartboard
686
+ return doc
1002
687
 
1003
688
 
1004
- def get_folders(space_duid: str, *, include_special: bool = False) -> list[Folder]:
689
+ def delete_doc(id: str) -> Doc:
1005
690
  dart = Dart()
691
+ doc = dart.delete_doc(id).item
1006
692
 
1007
- response = folders_list.sync(client=dart, space_duid=space_duid)
1008
- folders = response.results if response is not None else []
1009
- if not include_special:
1010
- folders = [e for e in folders if e.kind == FolderKind.OTHER]
1011
-
1012
- _log(f"Got {len(folders)} folders")
693
+ _log(f"Deleted doc (ID: {doc.id}) {doc.title} at {doc.html_url}")
1013
694
  _log("Done.")
1014
- return folders
1015
-
1016
-
1017
- def update_folder(
1018
- duid: str,
1019
- *,
1020
- title: str | None = None,
1021
- color_hex: str | None = None,
1022
- ) -> Folder:
1023
- config = _Config()
1024
- session = _Session(config)
1025
- dart = Dart(session)
695
+ return doc
1026
696
 
1027
- folder_update_kwargs = {"duid": duid}
1028
697
 
1029
- if title is not None:
1030
- folder_update_kwargs["title"] = title
1031
- if color_hex is not None:
1032
- folder_update_kwargs["color_hex"] = color_hex
1033
-
1034
- folder_update = FolderUpdate(**folder_update_kwargs)
1035
- folder_update_op = Operation(
1036
- model=OperationModelKind.FOLDER,
1037
- kind=OperationKind.UPDATE,
1038
- data=folder_update,
1039
- )
1040
- response = dart.transact([folder_update_op], TransactionKind.FOLDER_UPDATE)
1041
- folder = _parse_transaction_response_and_maybe_exit(
1042
- response, OperationModelKind.FOLDER, folder_update.duid
1043
- )
1044
-
1045
- _log(
1046
- f"Updated folder {folder.title} at {_get_folder_url(config.host, folder.duid)}"
1047
- )
698
+ def create_comment(id: str, text: str) -> Comment:
699
+ dart = Dart()
700
+ comment_create = WrappedCommentCreate(item=CommentCreate(task_id=id, text=text))
701
+ comment = dart.create_comment(comment_create).item
702
+ _log(f"Created comment (ID: {comment.id}) at {comment.html_url}")
1048
703
  _log("Done.")
1049
- return folder
704
+ return comment
1050
705
 
1051
706
 
1052
707
  def _add_standard_task_arguments(parser: ArgumentParser) -> None:
1053
708
  parser.add_argument(
1054
- "-d", "--dartboard", dest="dartboard_title", help="dartboard title"
709
+ "-d",
710
+ "--dartboard",
711
+ dest="dartboard_title",
712
+ help="dartboard title",
713
+ default=UNSET,
714
+ )
715
+ parser.add_argument(
716
+ "-s",
717
+ "--status",
718
+ dest="status_title",
719
+ help="status title",
720
+ default=UNSET,
1055
721
  )
1056
- parser.add_argument("-s", "--status", dest="status_title", help="status title")
1057
722
  parser.add_argument(
1058
723
  "-a",
1059
724
  "--assignee",
@@ -1077,11 +742,24 @@ def _add_standard_task_arguments(parser: ArgumentParser) -> None:
1077
742
  type=int,
1078
743
  choices=_PRIORITY_MAP.keys(),
1079
744
  help="priority",
745
+ default=UNSET,
746
+ )
747
+ parser.add_argument(
748
+ "-i",
749
+ "--size",
750
+ dest="size_int",
751
+ type=int,
752
+ choices=_SIZES,
753
+ help="size",
754
+ default=UNSET,
1080
755
  )
1081
756
  parser.add_argument(
1082
- "-i", "--size", dest="size_int", type=int, choices=_SIZES, help="size"
757
+ "-r",
758
+ "--duedate",
759
+ dest="due_at_str",
760
+ help="due date",
761
+ default=UNSET,
1083
762
  )
1084
- parser.add_argument("-r", "--duedate", dest="due_at_str", help="due date")
1085
763
 
1086
764
 
1087
765
  def cli() -> None:
@@ -1096,24 +774,35 @@ def cli() -> None:
1096
774
  return
1097
775
 
1098
776
  parser = ArgumentParser(prog=_PROG, description="A CLI to interact with Dart")
777
+ metavar = ",".join(
778
+ [
779
+ _LOGIN_CMD,
780
+ _CREATE_TASK_CMD,
781
+ _UPDATE_TASK_CMD,
782
+ _DELETE_TASK_CMD,
783
+ _BEGIN_TASK_CMD,
784
+ _CREATE_DOC_CMD,
785
+ _UPDATE_DOC_CMD,
786
+ _DELETE_DOC_CMD,
787
+ _CREATE_COMMENT_CMD,
788
+ ]
789
+ )
1099
790
  subparsers = parser.add_subparsers(
1100
791
  title="command",
1101
792
  required=True,
1102
- metavar=f"{{{_LOGIN_CMD},{_CREATE_TASK_CMD},{_UPDATE_TASK_CMD},{_BEGIN_TASK_CMD}}}",
793
+ metavar=f"{{{metavar}}}",
1103
794
  )
1104
795
 
1105
- set_host_parser = subparsers.add_parser(_SET_HOST_CMD, aliases="s")
796
+ set_host_parser = subparsers.add_parser(_SET_HOST_CMD, aliases=["h"])
1106
797
  set_host_parser.add_argument("host", help="the new host: {prod|stag|dev|[URL]}")
1107
798
  set_host_parser.set_defaults(func=set_host)
1108
799
 
1109
- login_parser = subparsers.add_parser(_LOGIN_CMD, aliases="l", help="login")
1110
- login_parser.add_argument(
1111
- "-t", "--token", dest="token", help="your authentication token"
1112
- )
800
+ login_parser = subparsers.add_parser(_LOGIN_CMD, aliases=["l"], help="login")
801
+ login_parser.add_argument("-t", "--token", dest="token", help="your authentication token")
1113
802
  login_parser.set_defaults(func=login)
1114
803
 
1115
804
  create_task_parser = subparsers.add_parser(
1116
- _CREATE_TASK_CMD, aliases="c", help="create a new task"
805
+ _CREATE_TASK_CMD, aliases=["tc"], help=_HELP_TEXT_TO_COMMAND[_CREATE_TASK_CMD]
1117
806
  )
1118
807
  create_task_parser.add_argument("title", help="title of the task")
1119
808
  create_task_parser.add_argument(
@@ -1127,18 +816,52 @@ def cli() -> None:
1127
816
  create_task_parser.set_defaults(func=create_task)
1128
817
 
1129
818
  update_task_parser = subparsers.add_parser(
1130
- _UPDATE_TASK_CMD, aliases="u", help="update an existing task"
819
+ _UPDATE_TASK_CMD, aliases=["tu"], help=_HELP_TEXT_TO_COMMAND[_UPDATE_TASK_CMD]
1131
820
  )
1132
- update_task_parser.add_argument("duid", help="Dart ID (DUID) of the task")
1133
- update_task_parser.add_argument("-e", "--title", dest="title", help="task title")
821
+ update_task_parser.add_argument("id", help="ID of the task")
822
+ update_task_parser.add_argument("-e", "--title", dest="title", help="task title", default=UNSET)
1134
823
  _add_standard_task_arguments(update_task_parser)
1135
824
  update_task_parser.set_defaults(func=update_task)
1136
825
 
1137
- begin_task_parser = subparsers.add_parser(
1138
- _BEGIN_TASK_CMD, aliases="b", help="begin work on a task"
826
+ delete_task_parser = subparsers.add_parser(
827
+ _DELETE_TASK_CMD, aliases=["td"], help=_HELP_TEXT_TO_COMMAND[_DELETE_TASK_CMD]
1139
828
  )
829
+ delete_task_parser.add_argument("id", help="ID of the task")
830
+ delete_task_parser.set_defaults(func=delete_task)
831
+
832
+ begin_task_parser = subparsers.add_parser(_BEGIN_TASK_CMD, aliases=["tb"], help="begin work on a task")
1140
833
  begin_task_parser.set_defaults(func=begin_task)
1141
834
 
835
+ create_doc_parser = subparsers.add_parser(
836
+ _CREATE_DOC_CMD, aliases=["dc"], help=_HELP_TEXT_TO_COMMAND[_CREATE_DOC_CMD]
837
+ )
838
+ create_doc_parser.add_argument("title", help="title of the doc")
839
+ create_doc_parser.add_argument("-f", "--folder", dest="folder", help="doc folder", default=UNSET)
840
+ create_doc_parser.add_argument("-t", "--text", dest="text", help="doc text", default=UNSET)
841
+ create_doc_parser.set_defaults(func=create_doc)
842
+
843
+ update_doc_parser = subparsers.add_parser(
844
+ _UPDATE_DOC_CMD, aliases=["du"], help=_HELP_TEXT_TO_COMMAND[_UPDATE_DOC_CMD]
845
+ )
846
+ update_doc_parser.add_argument("id", help="ID of the doc")
847
+ update_doc_parser.add_argument("-e", "--title", dest="title", help="doc title", default=UNSET)
848
+ update_doc_parser.add_argument("-f", "--folder", dest="folder", help="doc folder", default=UNSET)
849
+ update_doc_parser.add_argument("-t", "--text", dest="text", help="doc text", default=UNSET)
850
+ update_doc_parser.set_defaults(func=update_doc)
851
+
852
+ delete_doc_parser = subparsers.add_parser(
853
+ _DELETE_DOC_CMD, aliases=["dd"], help=_HELP_TEXT_TO_COMMAND[_DELETE_DOC_CMD]
854
+ )
855
+ delete_doc_parser.add_argument("id", help="ID of the doc")
856
+ delete_doc_parser.set_defaults(func=delete_doc)
857
+
858
+ create_comment_parser = subparsers.add_parser(
859
+ _CREATE_COMMENT_CMD, aliases=["cc"], help=_HELP_TEXT_TO_COMMAND[_CREATE_COMMENT_CMD]
860
+ )
861
+ create_comment_parser.add_argument("id", help="ID of the task")
862
+ create_comment_parser.add_argument("text", help="text of the comment")
863
+ create_comment_parser.set_defaults(func=create_comment)
864
+
1142
865
  args = vars(parser.parse_args())
1143
866
  func = args.pop("func")
1144
867
  func(**args)