redtile 0.0.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.
- redtile-0.0.17/PKG-INFO +173 -0
- redtile-0.0.17/README.md +159 -0
- redtile-0.0.17/pyproject.toml +33 -0
- redtile-0.0.17/src/redi/__init__.py +0 -0
- redtile-0.0.17/src/redi/api/__init__.py +0 -0
- redtile-0.0.17/src/redi/api/attachment.py +110 -0
- redtile-0.0.17/src/redi/api/custom_field.py +72 -0
- redtile-0.0.17/src/redi/api/enumeration.py +44 -0
- redtile-0.0.17/src/redi/api/file.py +55 -0
- redtile-0.0.17/src/redi/api/group.py +166 -0
- redtile-0.0.17/src/redi/api/issue.py +367 -0
- redtile-0.0.17/src/redi/api/issue_category.py +123 -0
- redtile-0.0.17/src/redi/api/issue_relation.py +82 -0
- redtile-0.0.17/src/redi/api/issue_status.py +26 -0
- redtile-0.0.17/src/redi/api/me.py +59 -0
- redtile-0.0.17/src/redi/api/membership.py +113 -0
- redtile-0.0.17/src/redi/api/news.py +32 -0
- redtile-0.0.17/src/redi/api/project.py +189 -0
- redtile-0.0.17/src/redi/api/query.py +14 -0
- redtile-0.0.17/src/redi/api/role.py +45 -0
- redtile-0.0.17/src/redi/api/search.py +30 -0
- redtile-0.0.17/src/redi/api/time_entry.py +143 -0
- redtile-0.0.17/src/redi/api/tracker.py +26 -0
- redtile-0.0.17/src/redi/api/user.py +196 -0
- redtile-0.0.17/src/redi/api/version.py +144 -0
- redtile-0.0.17/src/redi/api/wiki.py +168 -0
- redtile-0.0.17/src/redi/cache.py +30 -0
- redtile-0.0.17/src/redi/cli/__init__.py +3 -0
- redtile-0.0.17/src/redi/cli/_common.py +214 -0
- redtile-0.0.17/src/redi/cli/attachment_command.py +60 -0
- redtile-0.0.17/src/redi/cli/config_command.py +100 -0
- redtile-0.0.17/src/redi/cli/enumerations_command.py +44 -0
- redtile-0.0.17/src/redi/cli/file_command.py +40 -0
- redtile-0.0.17/src/redi/cli/group_command.py +110 -0
- redtile-0.0.17/src/redi/cli/issue_category_command.py +104 -0
- redtile-0.0.17/src/redi/cli/issue_command.py +548 -0
- redtile-0.0.17/src/redi/cli/main.py +252 -0
- redtile-0.0.17/src/redi/cli/me_command.py +29 -0
- redtile-0.0.17/src/redi/cli/membership_command.py +115 -0
- redtile-0.0.17/src/redi/cli/news_command.py +15 -0
- redtile-0.0.17/src/redi/cli/project_command.py +153 -0
- redtile-0.0.17/src/redi/cli/relation_command.py +28 -0
- redtile-0.0.17/src/redi/cli/role_command.py +28 -0
- redtile-0.0.17/src/redi/cli/search_command.py +22 -0
- redtile-0.0.17/src/redi/cli/time_entry_command.py +99 -0
- redtile-0.0.17/src/redi/cli/user_command.py +148 -0
- redtile-0.0.17/src/redi/cli/version_command.py +289 -0
- redtile-0.0.17/src/redi/cli/wiki_command.py +198 -0
- redtile-0.0.17/src/redi/client.py +39 -0
- redtile-0.0.17/src/redi/config.py +206 -0
- redtile-0.0.17/src/redi/tui/__init__.py +24 -0
- redtile-0.0.17/src/redi/tui/app.py +226 -0
- redtile-0.0.17/src/redi/tui/issue_tab.py +173 -0
- redtile-0.0.17/src/redi/tui/render.py +20 -0
- redtile-0.0.17/src/redi/tui/state.py +54 -0
- redtile-0.0.17/src/redi/tui/tab.py +26 -0
- redtile-0.0.17/src/redi/tui/wiki_tab.py +180 -0
redtile-0.0.17/PKG-INFO
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: redtile
|
|
3
|
+
Version: 0.0.17
|
|
4
|
+
Summary: Redmine CLI tool
|
|
5
|
+
Author: kawagh
|
|
6
|
+
Author-email: kawagh <kawagh.dev@gmail.com>
|
|
7
|
+
Requires-Dist: argcomplete>=3.6.3
|
|
8
|
+
Requires-Dist: prompt-toolkit>=3.0.52
|
|
9
|
+
Requires-Dist: requests>=2.33.1
|
|
10
|
+
Requires-Dist: tomlkit>=0.14.0
|
|
11
|
+
Requires-Dist: wcwidth>=0.2.13
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# redi
|
|
16
|
+
|
|
17
|
+
redmine CLI tool
|
|
18
|
+
|
|
19
|
+
## install
|
|
20
|
+
|
|
21
|
+
Install via [uv](https://github.com/astral-sh/uv)
|
|
22
|
+
|
|
23
|
+
```shell
|
|
24
|
+
uv tool install https://github.com/kawagh/redi.git
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## install(for development)
|
|
28
|
+
|
|
29
|
+
In repository root
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
uv tool install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## setup
|
|
36
|
+
|
|
37
|
+
### config
|
|
38
|
+
|
|
39
|
+
To use redi, you need to set remdine url and redmine_api_key in one of below ways.
|
|
40
|
+
|
|
41
|
+
#### environment variable
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
export REDMINE_URL=xxx
|
|
45
|
+
export REDMINE_API_KEY=yyy
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### ~/.config/redi/config.toml
|
|
49
|
+
|
|
50
|
+
```toml
|
|
51
|
+
default_profile = "main"
|
|
52
|
+
|
|
53
|
+
["main"]
|
|
54
|
+
redmine_url = "xxx"
|
|
55
|
+
redmine_api_key = "yyy"
|
|
56
|
+
default_project_id = "1"
|
|
57
|
+
wiki_project_id = "2"
|
|
58
|
+
editor = "nvim"
|
|
59
|
+
|
|
60
|
+
["sub"]
|
|
61
|
+
redmine_url = "vvv"
|
|
62
|
+
redmine_api_key = "www"
|
|
63
|
+
default_project_id = "2"
|
|
64
|
+
wiki_project_id = "3"
|
|
65
|
+
editor = "code"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### setup completion
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
uv tool install argcomplete
|
|
72
|
+
echo 'eval "$(register-python-argcomplete redi)"' >> ~/.zshrc
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## usage(example)
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
# config (alias: c)
|
|
79
|
+
redi config
|
|
80
|
+
redi config create <profile_name> --url <url> --api_key <key> # create new profile
|
|
81
|
+
redi config create <profile_name> --url <url> --api_key <key> --set_default
|
|
82
|
+
redi config update --default_profile <profile_name> # switch profile
|
|
83
|
+
redi config update <profile_name> --editor nvim # update profile
|
|
84
|
+
redi --profile <profile_name> issue # 一時的にプロファイルを切り替えて実行
|
|
85
|
+
|
|
86
|
+
# project (alias: p)
|
|
87
|
+
redi project # list projects
|
|
88
|
+
redi project list # 同上 (`redi project l` / `redi p list` / `redi p l` / `redi p` も同じ)
|
|
89
|
+
redi project view <project_id> # view project
|
|
90
|
+
redi project view <project_id> --include trackers,issue_categories
|
|
91
|
+
redi project create <name> <identifier>
|
|
92
|
+
redi project create <name> <identifier> -d "description" --is_public true
|
|
93
|
+
redi project update <project_id> --name renamed_project
|
|
94
|
+
|
|
95
|
+
# issue (alias: i)
|
|
96
|
+
redi issue # list issues
|
|
97
|
+
redi issue -p <project_id> -a me -s open
|
|
98
|
+
redi issue -q <query_id>
|
|
99
|
+
redi issue view <issue_id>
|
|
100
|
+
redi issue view <issue_id> --web # view issue with web browser
|
|
101
|
+
redi issue view <issue_id> --include journals,attachments,relations
|
|
102
|
+
redi issue create # (interactive)
|
|
103
|
+
redi issue create "subject" -p <project_id> -t <tracker_id> -a <user_id> -d "description"
|
|
104
|
+
redi issue update <issue_id> # (interactive)
|
|
105
|
+
redi issue update <issue_id> --status_id <status_id> -n "notes"
|
|
106
|
+
redi issue update <issue_id> --relate relates --to <other_issue_id>
|
|
107
|
+
redi issue update <issue_id> --attach ./foo.png --attach ./bar.log
|
|
108
|
+
redi issue comment <issue_id> "hello~"
|
|
109
|
+
redi issue delete <issue_id> # (confirm before delete)
|
|
110
|
+
redi issue delete <issue_id> -y # skip confirmation
|
|
111
|
+
|
|
112
|
+
# version (alias: v)
|
|
113
|
+
redi version # list versions(fixed_versions)
|
|
114
|
+
redi version -p <project_id>
|
|
115
|
+
redi version view <version_id>
|
|
116
|
+
redi version create <name> -p <project_id> --due_date 2026-12-31 --status open
|
|
117
|
+
redi version update <version_id> --status closed
|
|
118
|
+
|
|
119
|
+
# wiki (alias: w)
|
|
120
|
+
redi wiki
|
|
121
|
+
redi wiki -p <project_id>
|
|
122
|
+
redi wiki view <page_title>
|
|
123
|
+
redi wiki create # (interactive)
|
|
124
|
+
redi wiki update # (interactive)
|
|
125
|
+
|
|
126
|
+
# file (プロジェクトファイル)
|
|
127
|
+
redi file -p <project_id> # list
|
|
128
|
+
redi file create ./foo.zip -p <project_id> -d "description"
|
|
129
|
+
|
|
130
|
+
# attachment
|
|
131
|
+
redi attachment view <attachment_id>
|
|
132
|
+
redi attachment update <attachment_id> -f new_name.png -d "desc"
|
|
133
|
+
redi attachment delete <attachment_id> # confirm before delete (-y to skip)
|
|
134
|
+
|
|
135
|
+
# relation (イシュー関係性詳細)
|
|
136
|
+
redi relation view <relation_id>
|
|
137
|
+
|
|
138
|
+
# time_entry (作業時間)
|
|
139
|
+
redi time_entry -p <project_id> -u me
|
|
140
|
+
redi time_entry create 1.5 -i <issue_id> -a <activity_id> -c "comment"
|
|
141
|
+
redi time_entry update <time_entry_id> --hours 2.0
|
|
142
|
+
redi time_entry delete <time_entry_id> # confirm before delete (-y to skip)
|
|
143
|
+
|
|
144
|
+
# me (自分のアカウント)
|
|
145
|
+
redi me
|
|
146
|
+
redi me update -f <firstname> -l <lastname> -m <mail>
|
|
147
|
+
|
|
148
|
+
# membership (alias: m)
|
|
149
|
+
redi membership -p <project_id>
|
|
150
|
+
redi membership view <membership_id>
|
|
151
|
+
|
|
152
|
+
# news
|
|
153
|
+
redi news -p <project_id>
|
|
154
|
+
|
|
155
|
+
# issue_category
|
|
156
|
+
redi issue_category -p <project_id>
|
|
157
|
+
redi issue_category create "category" -p <project_id>
|
|
158
|
+
|
|
159
|
+
# others
|
|
160
|
+
redi user # list users (alias: u)
|
|
161
|
+
redi tracker # list trackers
|
|
162
|
+
redi issue_status # list issue statuses
|
|
163
|
+
redi issue_priority # list priorities
|
|
164
|
+
redi time_entry_activity # list activities
|
|
165
|
+
redi document_category # list document categories
|
|
166
|
+
redi role # list roles
|
|
167
|
+
redi group # list groups
|
|
168
|
+
redi custom_field # list custom fields
|
|
169
|
+
redi query # list custom queries
|
|
170
|
+
redi search "keyword"
|
|
171
|
+
redi --version
|
|
172
|
+
redi --tui
|
|
173
|
+
```
|
redtile-0.0.17/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# redi
|
|
2
|
+
|
|
3
|
+
redmine CLI tool
|
|
4
|
+
|
|
5
|
+
## install
|
|
6
|
+
|
|
7
|
+
Install via [uv](https://github.com/astral-sh/uv)
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
uv tool install https://github.com/kawagh/redi.git
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## install(for development)
|
|
14
|
+
|
|
15
|
+
In repository root
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
uv tool install -e .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## setup
|
|
22
|
+
|
|
23
|
+
### config
|
|
24
|
+
|
|
25
|
+
To use redi, you need to set remdine url and redmine_api_key in one of below ways.
|
|
26
|
+
|
|
27
|
+
#### environment variable
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
export REDMINE_URL=xxx
|
|
31
|
+
export REDMINE_API_KEY=yyy
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### ~/.config/redi/config.toml
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
default_profile = "main"
|
|
38
|
+
|
|
39
|
+
["main"]
|
|
40
|
+
redmine_url = "xxx"
|
|
41
|
+
redmine_api_key = "yyy"
|
|
42
|
+
default_project_id = "1"
|
|
43
|
+
wiki_project_id = "2"
|
|
44
|
+
editor = "nvim"
|
|
45
|
+
|
|
46
|
+
["sub"]
|
|
47
|
+
redmine_url = "vvv"
|
|
48
|
+
redmine_api_key = "www"
|
|
49
|
+
default_project_id = "2"
|
|
50
|
+
wiki_project_id = "3"
|
|
51
|
+
editor = "code"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### setup completion
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
uv tool install argcomplete
|
|
58
|
+
echo 'eval "$(register-python-argcomplete redi)"' >> ~/.zshrc
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## usage(example)
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
# config (alias: c)
|
|
65
|
+
redi config
|
|
66
|
+
redi config create <profile_name> --url <url> --api_key <key> # create new profile
|
|
67
|
+
redi config create <profile_name> --url <url> --api_key <key> --set_default
|
|
68
|
+
redi config update --default_profile <profile_name> # switch profile
|
|
69
|
+
redi config update <profile_name> --editor nvim # update profile
|
|
70
|
+
redi --profile <profile_name> issue # 一時的にプロファイルを切り替えて実行
|
|
71
|
+
|
|
72
|
+
# project (alias: p)
|
|
73
|
+
redi project # list projects
|
|
74
|
+
redi project list # 同上 (`redi project l` / `redi p list` / `redi p l` / `redi p` も同じ)
|
|
75
|
+
redi project view <project_id> # view project
|
|
76
|
+
redi project view <project_id> --include trackers,issue_categories
|
|
77
|
+
redi project create <name> <identifier>
|
|
78
|
+
redi project create <name> <identifier> -d "description" --is_public true
|
|
79
|
+
redi project update <project_id> --name renamed_project
|
|
80
|
+
|
|
81
|
+
# issue (alias: i)
|
|
82
|
+
redi issue # list issues
|
|
83
|
+
redi issue -p <project_id> -a me -s open
|
|
84
|
+
redi issue -q <query_id>
|
|
85
|
+
redi issue view <issue_id>
|
|
86
|
+
redi issue view <issue_id> --web # view issue with web browser
|
|
87
|
+
redi issue view <issue_id> --include journals,attachments,relations
|
|
88
|
+
redi issue create # (interactive)
|
|
89
|
+
redi issue create "subject" -p <project_id> -t <tracker_id> -a <user_id> -d "description"
|
|
90
|
+
redi issue update <issue_id> # (interactive)
|
|
91
|
+
redi issue update <issue_id> --status_id <status_id> -n "notes"
|
|
92
|
+
redi issue update <issue_id> --relate relates --to <other_issue_id>
|
|
93
|
+
redi issue update <issue_id> --attach ./foo.png --attach ./bar.log
|
|
94
|
+
redi issue comment <issue_id> "hello~"
|
|
95
|
+
redi issue delete <issue_id> # (confirm before delete)
|
|
96
|
+
redi issue delete <issue_id> -y # skip confirmation
|
|
97
|
+
|
|
98
|
+
# version (alias: v)
|
|
99
|
+
redi version # list versions(fixed_versions)
|
|
100
|
+
redi version -p <project_id>
|
|
101
|
+
redi version view <version_id>
|
|
102
|
+
redi version create <name> -p <project_id> --due_date 2026-12-31 --status open
|
|
103
|
+
redi version update <version_id> --status closed
|
|
104
|
+
|
|
105
|
+
# wiki (alias: w)
|
|
106
|
+
redi wiki
|
|
107
|
+
redi wiki -p <project_id>
|
|
108
|
+
redi wiki view <page_title>
|
|
109
|
+
redi wiki create # (interactive)
|
|
110
|
+
redi wiki update # (interactive)
|
|
111
|
+
|
|
112
|
+
# file (プロジェクトファイル)
|
|
113
|
+
redi file -p <project_id> # list
|
|
114
|
+
redi file create ./foo.zip -p <project_id> -d "description"
|
|
115
|
+
|
|
116
|
+
# attachment
|
|
117
|
+
redi attachment view <attachment_id>
|
|
118
|
+
redi attachment update <attachment_id> -f new_name.png -d "desc"
|
|
119
|
+
redi attachment delete <attachment_id> # confirm before delete (-y to skip)
|
|
120
|
+
|
|
121
|
+
# relation (イシュー関係性詳細)
|
|
122
|
+
redi relation view <relation_id>
|
|
123
|
+
|
|
124
|
+
# time_entry (作業時間)
|
|
125
|
+
redi time_entry -p <project_id> -u me
|
|
126
|
+
redi time_entry create 1.5 -i <issue_id> -a <activity_id> -c "comment"
|
|
127
|
+
redi time_entry update <time_entry_id> --hours 2.0
|
|
128
|
+
redi time_entry delete <time_entry_id> # confirm before delete (-y to skip)
|
|
129
|
+
|
|
130
|
+
# me (自分のアカウント)
|
|
131
|
+
redi me
|
|
132
|
+
redi me update -f <firstname> -l <lastname> -m <mail>
|
|
133
|
+
|
|
134
|
+
# membership (alias: m)
|
|
135
|
+
redi membership -p <project_id>
|
|
136
|
+
redi membership view <membership_id>
|
|
137
|
+
|
|
138
|
+
# news
|
|
139
|
+
redi news -p <project_id>
|
|
140
|
+
|
|
141
|
+
# issue_category
|
|
142
|
+
redi issue_category -p <project_id>
|
|
143
|
+
redi issue_category create "category" -p <project_id>
|
|
144
|
+
|
|
145
|
+
# others
|
|
146
|
+
redi user # list users (alias: u)
|
|
147
|
+
redi tracker # list trackers
|
|
148
|
+
redi issue_status # list issue statuses
|
|
149
|
+
redi issue_priority # list priorities
|
|
150
|
+
redi time_entry_activity # list activities
|
|
151
|
+
redi document_category # list document categories
|
|
152
|
+
redi role # list roles
|
|
153
|
+
redi group # list groups
|
|
154
|
+
redi custom_field # list custom fields
|
|
155
|
+
redi query # list custom queries
|
|
156
|
+
redi search "keyword"
|
|
157
|
+
redi --version
|
|
158
|
+
redi --tui
|
|
159
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "redtile"
|
|
3
|
+
version = "0.0.17"
|
|
4
|
+
description = "Redmine CLI tool"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "kawagh", email = "kawagh.dev@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"argcomplete>=3.6.3",
|
|
12
|
+
"prompt-toolkit>=3.0.52",
|
|
13
|
+
"requests>=2.33.1",
|
|
14
|
+
"tomlkit>=0.14.0",
|
|
15
|
+
"wcwidth>=0.2.13",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
redi = "redi.cli:main"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["uv_build>=0.9.2,<0.10.0"]
|
|
23
|
+
build-backend = "uv_build"
|
|
24
|
+
|
|
25
|
+
[dependency-groups]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=9.0.3",
|
|
28
|
+
"ruff>=0.15.9",
|
|
29
|
+
"ty>=0.0.31",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.uv.build-backend]
|
|
33
|
+
module-name = "redi"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import mimetypes
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from redi.client import client
|
|
8
|
+
from redi.config import redmine_url
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def upload_file(file_path: str) -> dict:
|
|
12
|
+
if not os.path.isfile(file_path):
|
|
13
|
+
print(f"ファイルが見つかりません: {file_path}")
|
|
14
|
+
exit(1)
|
|
15
|
+
filename = os.path.basename(file_path)
|
|
16
|
+
content_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
|
17
|
+
with open(file_path, "rb") as f:
|
|
18
|
+
response = client.post(
|
|
19
|
+
"/uploads.json",
|
|
20
|
+
headers={"Content-Type": "application/octet-stream"},
|
|
21
|
+
data=f,
|
|
22
|
+
)
|
|
23
|
+
try:
|
|
24
|
+
response.raise_for_status()
|
|
25
|
+
except requests.exceptions.HTTPError as e:
|
|
26
|
+
print(e)
|
|
27
|
+
print(e.response.text)
|
|
28
|
+
print(f"ファイルのアップロードに失敗しました: {file_path}")
|
|
29
|
+
exit(1)
|
|
30
|
+
token = response.json()["upload"]["token"]
|
|
31
|
+
return {
|
|
32
|
+
"token": token,
|
|
33
|
+
"filename": filename,
|
|
34
|
+
"content_type": content_type,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def fetch_attachment(attachment_id: str) -> dict:
|
|
39
|
+
response = client.get(f"/attachments/{attachment_id}.json")
|
|
40
|
+
if response.status_code == 404:
|
|
41
|
+
print(f"添付ファイルが見つかりません: #{attachment_id}")
|
|
42
|
+
exit(1)
|
|
43
|
+
response.raise_for_status()
|
|
44
|
+
return response.json()["attachment"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def read_attachment(attachment_id: str, full: bool = False) -> None:
|
|
48
|
+
attachment = fetch_attachment(attachment_id)
|
|
49
|
+
if full:
|
|
50
|
+
print(json.dumps(attachment, ensure_ascii=False))
|
|
51
|
+
return
|
|
52
|
+
lines = [
|
|
53
|
+
f"{attachment['id']} {attachment['filename']}",
|
|
54
|
+
f"サイズ: {attachment.get('filesize', '')}",
|
|
55
|
+
f"種別: {attachment.get('content_type', '')}",
|
|
56
|
+
]
|
|
57
|
+
author = attachment.get("author") or {}
|
|
58
|
+
if author:
|
|
59
|
+
lines.append(f"作成者: {author.get('name', '')}")
|
|
60
|
+
if attachment.get("created_on"):
|
|
61
|
+
lines.append(f"作成日時: {attachment['created_on']}")
|
|
62
|
+
if attachment.get("description"):
|
|
63
|
+
lines.append(f"説明: {attachment['description']}")
|
|
64
|
+
if attachment.get("content_url"):
|
|
65
|
+
lines.append(f"URL: {attachment['content_url']}")
|
|
66
|
+
print("\n".join(lines))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def delete_attachment(attachment_id: str) -> None:
|
|
70
|
+
response = client.delete(f"/attachments/{attachment_id}.json")
|
|
71
|
+
if response.status_code == 404:
|
|
72
|
+
print(f"添付ファイルが見つかりません: #{attachment_id}")
|
|
73
|
+
exit(1)
|
|
74
|
+
try:
|
|
75
|
+
response.raise_for_status()
|
|
76
|
+
except requests.exceptions.HTTPError as e:
|
|
77
|
+
print(e)
|
|
78
|
+
print(e.response.text)
|
|
79
|
+
print("添付ファイルの削除に失敗しました")
|
|
80
|
+
exit(1)
|
|
81
|
+
print(f"添付ファイルを削除しました: #{attachment_id}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def update_attachment(
|
|
85
|
+
attachment_id: str,
|
|
86
|
+
filename: str | None = None,
|
|
87
|
+
description: str | None = None,
|
|
88
|
+
) -> None:
|
|
89
|
+
data: dict = {}
|
|
90
|
+
if filename is not None:
|
|
91
|
+
data["filename"] = filename
|
|
92
|
+
if description is not None:
|
|
93
|
+
data["description"] = description
|
|
94
|
+
if not data:
|
|
95
|
+
print("更新をキャンセルしました")
|
|
96
|
+
exit()
|
|
97
|
+
response = client.patch(
|
|
98
|
+
f"/attachments/{attachment_id}.json", json={"attachment": data}
|
|
99
|
+
)
|
|
100
|
+
if response.status_code == 404:
|
|
101
|
+
print(f"添付ファイルが見つかりません: #{attachment_id}")
|
|
102
|
+
exit(1)
|
|
103
|
+
try:
|
|
104
|
+
response.raise_for_status()
|
|
105
|
+
except requests.exceptions.HTTPError as e:
|
|
106
|
+
print(e)
|
|
107
|
+
print(e.response.text)
|
|
108
|
+
print("添付ファイルの更新に失敗しました")
|
|
109
|
+
exit(1)
|
|
110
|
+
print(f"添付ファイルを更新しました: {redmine_url}/attachments/{attachment_id}")
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from redi import cache
|
|
4
|
+
from redi.client import client
|
|
5
|
+
|
|
6
|
+
CACHE_KEY = "custom_fields"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def fetch_custom_fields() -> list[dict] | None:
|
|
10
|
+
cached = cache.load(CACHE_KEY)
|
|
11
|
+
if cached is not None:
|
|
12
|
+
return cached
|
|
13
|
+
response = client.get("/custom_fields.json")
|
|
14
|
+
if response.status_code == 403:
|
|
15
|
+
# https://www.redmine.org/projects/redmine/wiki/Rest_CustomFields
|
|
16
|
+
# https://www.redmine.org/issues/18875
|
|
17
|
+
return
|
|
18
|
+
response.raise_for_status()
|
|
19
|
+
data = response.json()["custom_fields"]
|
|
20
|
+
cache.save(CACHE_KEY, data)
|
|
21
|
+
return data
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def list_custom_fields(full: bool = False) -> None:
|
|
25
|
+
custom_fields = fetch_custom_fields()
|
|
26
|
+
if custom_fields is None:
|
|
27
|
+
print("カスタムフィールドの取得には管理者権限が必要です")
|
|
28
|
+
exit(1)
|
|
29
|
+
if full:
|
|
30
|
+
print(json.dumps(custom_fields, ensure_ascii=False))
|
|
31
|
+
else:
|
|
32
|
+
for cf in custom_fields:
|
|
33
|
+
print(f"{cf['id']} {cf['name']}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def fetch_project_issue_custom_field_ids(project_id: str) -> set[int]:
|
|
37
|
+
"""プロジェクトで有効なイシュー用カスタムフィールドのIDを取得する。"""
|
|
38
|
+
response = client.get(
|
|
39
|
+
f"/projects/{project_id}.json", params={"include": "issue_custom_fields"}
|
|
40
|
+
)
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
project = response.json()["project"]
|
|
43
|
+
|
|
44
|
+
return {cf["id"] for cf in project.get("issue_custom_fields") or []}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def filter_required_issue_custom_fields(
|
|
48
|
+
custom_fields: list[dict],
|
|
49
|
+
project_cf_ids: set[int],
|
|
50
|
+
tracker_id: str | None,
|
|
51
|
+
) -> list[dict]:
|
|
52
|
+
"""
|
|
53
|
+
入力必須・初期値なし・プロジェクト/トラッカーに該当する
|
|
54
|
+
イシュー用カスタムフィールドを抽出する。
|
|
55
|
+
"""
|
|
56
|
+
result = []
|
|
57
|
+
for cf in custom_fields:
|
|
58
|
+
if cf.get("customized_type") != "issue":
|
|
59
|
+
continue
|
|
60
|
+
if not cf.get("is_required"):
|
|
61
|
+
continue
|
|
62
|
+
if cf.get("default_value"):
|
|
63
|
+
continue
|
|
64
|
+
if cf["id"] not in project_cf_ids:
|
|
65
|
+
continue
|
|
66
|
+
trackers = cf.get("trackers") or []
|
|
67
|
+
if trackers and tracker_id is not None:
|
|
68
|
+
tracker_ids = {str(t["id"]) for t in trackers}
|
|
69
|
+
if str(tracker_id) not in tracker_ids:
|
|
70
|
+
continue
|
|
71
|
+
result.append(cf)
|
|
72
|
+
return result
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from redi import cache
|
|
4
|
+
from redi.client import client
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _fetch_enumeration(resource: str) -> list[dict]:
|
|
8
|
+
cached = cache.load(resource)
|
|
9
|
+
if cached is not None:
|
|
10
|
+
return cached
|
|
11
|
+
response = client.get(f"/enumerations/{resource}.json")
|
|
12
|
+
response.raise_for_status()
|
|
13
|
+
data = response.json()[resource]
|
|
14
|
+
cache.save(resource, data)
|
|
15
|
+
return data
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _list_enumeration(resource: str, full: bool = False) -> None:
|
|
19
|
+
items = _fetch_enumeration(resource)
|
|
20
|
+
if full:
|
|
21
|
+
print(json.dumps(items, ensure_ascii=False))
|
|
22
|
+
else:
|
|
23
|
+
for item in items:
|
|
24
|
+
print(f"{item['id']} {item['name']}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def fetch_issue_priorities() -> list[dict]:
|
|
28
|
+
return _fetch_enumeration("issue_priorities")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def list_issue_priorities(full: bool = False) -> None:
|
|
32
|
+
_list_enumeration("issue_priorities", full)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def fetch_time_entry_activities() -> list[dict]:
|
|
36
|
+
return _fetch_enumeration("time_entry_activities")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def list_time_entry_activities(full: bool = False) -> None:
|
|
40
|
+
_list_enumeration("time_entry_activities", full)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def list_document_categories(full: bool = False) -> None:
|
|
44
|
+
_list_enumeration("document_categories", full)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from redi.api.attachment import upload_file
|
|
6
|
+
from redi.client import client
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def list_files(project_id: str, full: bool = False) -> None:
|
|
10
|
+
response = client.get(f"/projects/{project_id}/files.json")
|
|
11
|
+
if response.status_code == 404:
|
|
12
|
+
print(f"プロジェクトが見つかりません: {project_id}")
|
|
13
|
+
exit(1)
|
|
14
|
+
response.raise_for_status()
|
|
15
|
+
files = response.json()["files"]
|
|
16
|
+
if full:
|
|
17
|
+
print(json.dumps(files, ensure_ascii=False))
|
|
18
|
+
return
|
|
19
|
+
for f in files:
|
|
20
|
+
version = f.get("version") or {}
|
|
21
|
+
version_label = f" [{version.get('name')}]" if version else ""
|
|
22
|
+
size = f.get("filesize", "")
|
|
23
|
+
print(f"{f['id']} {f['filename']} ({size}B){version_label}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_file(
|
|
27
|
+
project_id: str,
|
|
28
|
+
file_path: str,
|
|
29
|
+
version_id: int | None = None,
|
|
30
|
+
description: str | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
upload = upload_file(file_path)
|
|
33
|
+
file_data: dict = {
|
|
34
|
+
"token": upload["token"],
|
|
35
|
+
"filename": upload["filename"],
|
|
36
|
+
"content_type": upload["content_type"],
|
|
37
|
+
}
|
|
38
|
+
if version_id is not None:
|
|
39
|
+
file_data["version_id"] = version_id
|
|
40
|
+
if description is not None:
|
|
41
|
+
file_data["description"] = description
|
|
42
|
+
response = client.post(
|
|
43
|
+
f"/projects/{project_id}/files.json", json={"file": file_data}
|
|
44
|
+
)
|
|
45
|
+
if response.status_code == 404:
|
|
46
|
+
print(f"プロジェクトが見つかりません: {project_id}")
|
|
47
|
+
exit(1)
|
|
48
|
+
try:
|
|
49
|
+
response.raise_for_status()
|
|
50
|
+
except requests.exceptions.HTTPError as e:
|
|
51
|
+
print(e)
|
|
52
|
+
print(e.response.text)
|
|
53
|
+
print("ファイルのアップロードに失敗しました")
|
|
54
|
+
exit(1)
|
|
55
|
+
print(f"ファイルをアップロードしました: {upload['filename']}")
|