trellodon 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +17 -1
- data/lib/.rbnext/2.7/trellodon/executor.rb +6 -6
- data/lib/.rbnext/3.0/trellodon/client.rb +27 -24
- data/lib/trellodon/cli.rb +19 -10
- data/lib/trellodon/client.rb +27 -24
- data/lib/trellodon/config.rb +1 -1
- data/lib/trellodon/entities/attachment.rb +1 -1
- data/lib/trellodon/entities/board.rb +1 -1
- data/lib/trellodon/entities/card.rb +1 -1
- data/lib/trellodon/entities/card_event.rb +6 -0
- data/lib/trellodon/entities/checklist_item.rb +1 -1
- data/lib/trellodon/entities/list.rb +1 -1
- data/lib/trellodon/executor.rb +6 -6
- data/lib/trellodon/formatters/markdown.rb +72 -7
- data/lib/trellodon/journal.rb +20 -0
- data/lib/trellodon/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7210dc1a4a18474871607069363ab1d1acdfeb283588ef52403a38699e24bebb
|
4
|
+
data.tar.gz: 465aa0681c0c9e2a3744a94ad06b17e8eaf35bfdc90204e36d6aa35359765134
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3895577372463c4d917c0c91f557789d298d5183263b72475bf213a44bb4d17deefac8e1ce5eb13b65b93540ae8442dfb43a4123d255abbe9a403700307353dc
|
7
|
+
data.tar.gz: d7e099a0bdb52d541152522f317e3b66ed89aa0128639be8731bb0c99c677e26b815bf1d6d1b61754bfa72998df7b5649967b8a28c23c05d5d561abd775b841c
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
## [master]
|
4
4
|
|
5
|
+
## [0.4.0] - 2022-04-27 🗂️
|
6
|
+
|
7
|
+
- Resolve folder name collisions in markdown formatter ([@rinasergeeva][])
|
8
|
+
- Add user info to attachments and checklists ([@rinasergeeva][])
|
9
|
+
- Verbose option in cli. ([@fargelus][])
|
10
|
+
|
5
11
|
## [0.3.0] - 2022-04-12 🚀🚀🚀
|
6
12
|
|
7
13
|
- Handle missing members. ([@palkan][])
|
@@ -29,3 +35,4 @@
|
|
29
35
|
|
30
36
|
[@fargelus]: https://github.com/fargelus
|
31
37
|
[@rinasergeeva]: https://github.com/rinasergeeva
|
38
|
+
[@palkan]: https://github.com/palkan
|
data/README.md
CHANGED
@@ -9,6 +9,7 @@ A Ruby tool to export a Trello board and convert it into a set of folders and ma
|
|
9
9
|
* Description
|
10
10
|
* Labels
|
11
11
|
* Comments
|
12
|
+
* Checklists
|
12
13
|
* Attachments
|
13
14
|
|
14
15
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
@@ -30,6 +31,14 @@ To generate them, go to the [trello.com/app-key](https://trello.com/app-key) pag
|
|
30
31
|
|
31
32
|
## Usage
|
32
33
|
|
34
|
+
Copy the board URL and run the following command:
|
35
|
+
|
36
|
+
```sh
|
37
|
+
trellodon dump --board=<url>
|
38
|
+
```
|
39
|
+
|
40
|
+
Other supported options:
|
41
|
+
|
33
42
|
```sh
|
34
43
|
Usage: trellodon dump [options]
|
35
44
|
Options:
|
@@ -37,7 +46,8 @@ Options:
|
|
37
46
|
--out VALUE Destination folder path
|
38
47
|
--concurrency VALUE Amount of processing threads (default: 4). Set to 0 to execute API requests in-line
|
39
48
|
--clear-auth Remove saved api credentials
|
40
|
-
-v, --
|
49
|
+
-v, --verbose Print all processes output
|
50
|
+
-V, --version Print current version
|
41
51
|
-h, --help Print help
|
42
52
|
```
|
43
53
|
|
@@ -90,3 +100,9 @@ Some card description
|
|
90
100
|
**date**: 2022-03-16
|
91
101
|
**url**: https://trello.com/1/cards/#{card_id}/attachments/#{attachment.id}/download/image.png
|
92
102
|
```
|
103
|
+
|
104
|
+
### Trello API Limits 🚧
|
105
|
+
|
106
|
+
Trello API limits the total amount of requests for each API Key & token.
|
107
|
+
By default, 300 requests are available per 10 seconds for each API key and no more than 100 requests per 10 second interval for each token.
|
108
|
+
If a request exceeds the limit, Trello will return a 429 error. Additional information see [here](https://help.trello.com/article/838-api-rate-limits).
|
@@ -5,12 +5,12 @@ module Trellodon
|
|
5
5
|
BOARD_ID_REGEX = /\/b\/([^\/]+)\//
|
6
6
|
private_constant :BOARD_ID_REGEX
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@api_key = api_key
|
10
|
-
@api_token = api_token
|
11
|
-
@formatter = formatter
|
12
|
-
@logger = logger
|
13
|
-
@scheduler = scheduler
|
8
|
+
def initialize(opts)
|
9
|
+
@api_key = opts.fetch(:api_key)
|
10
|
+
@api_token = opts.fetch(:api_token)
|
11
|
+
@formatter = opts.fetch(:formatter)
|
12
|
+
@logger = opts.fetch(:logger) { Config.logger }
|
13
|
+
@scheduler = opts.fetch(:scheduler)
|
14
14
|
check_credentials!
|
15
15
|
|
16
16
|
@client = Client.new(api_key: @api_key, api_token: @api_token)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "uri"
|
3
4
|
require "trello"
|
4
5
|
require "trellodon/entities"
|
5
6
|
|
@@ -17,7 +18,7 @@ module Trellodon
|
|
17
18
|
developer_public_key: api_key,
|
18
19
|
member_token: api_token
|
19
20
|
)
|
20
|
-
@members =
|
21
|
+
@members = Concurrent::Map.new
|
21
22
|
end
|
22
23
|
|
23
24
|
def fetch_board(board_id)
|
@@ -26,7 +27,6 @@ module Trellodon
|
|
26
27
|
|
27
28
|
Board.new(
|
28
29
|
id: board.id,
|
29
|
-
short_id: board.short_url.chars.last(8).join,
|
30
30
|
name: board.name,
|
31
31
|
lists: lists_from(board),
|
32
32
|
card_ids: @client.find_many(Trello::Card, "/boards/#{board_id}/cards", fields: "id").map(&:id),
|
@@ -41,7 +41,6 @@ module Trellodon
|
|
41
41
|
|
42
42
|
Card.new(
|
43
43
|
id: card.id,
|
44
|
-
short_id: card.short_url.chars.last(8).join,
|
45
44
|
name: card.name,
|
46
45
|
desc: card.desc,
|
47
46
|
labels: card.labels.map(&:name),
|
@@ -77,7 +76,7 @@ module Trellodon
|
|
77
76
|
Comment.new(
|
78
77
|
text: comment.data["text"],
|
79
78
|
date: comment.date,
|
80
|
-
creator: member_from(comment)
|
79
|
+
creator: member_from(comment.creator_id)
|
81
80
|
)
|
82
81
|
end
|
83
82
|
end
|
@@ -85,12 +84,13 @@ module Trellodon
|
|
85
84
|
def attachments_from(card)
|
86
85
|
card.attachments.map do |attach|
|
87
86
|
Attachment.new(
|
88
|
-
file_name: attach.file_name,
|
87
|
+
file_name: attach.is_upload ? URI.decode_www_form_component(attach.file_name) : attach.file_name,
|
89
88
|
mime_type: attach.mime_type,
|
90
89
|
bytes: attach.bytes,
|
91
90
|
date: attach.date,
|
92
91
|
name: attach.name,
|
93
92
|
is_upload: attach.is_upload,
|
93
|
+
added_by: member_from(attach.member_id),
|
94
94
|
headers: attach.is_upload ? {"Authorization" => "OAuth oauth_consumer_key=\"#{@client.configuration.developer_public_key}\", oauth_token=\"#{@client.configuration.member_token}\""} : {},
|
95
95
|
url: attach.url
|
96
96
|
)
|
@@ -102,7 +102,7 @@ module Trellodon
|
|
102
102
|
List.new(
|
103
103
|
id: list.id,
|
104
104
|
name: list.name,
|
105
|
-
|
105
|
+
board_id: list.board_id
|
106
106
|
)
|
107
107
|
end
|
108
108
|
end
|
@@ -122,28 +122,31 @@ module Trellodon
|
|
122
122
|
ChecklistItem.new(
|
123
123
|
id: item.id,
|
124
124
|
name: item.name,
|
125
|
-
state: item.state
|
125
|
+
state: item.state,
|
126
|
+
member: member_from(item.member_id),
|
127
|
+
due_date: item.due_date
|
126
128
|
)
|
127
129
|
end
|
128
130
|
end
|
129
131
|
|
130
|
-
def member_from(
|
131
|
-
return
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
132
|
+
def member_from(id)
|
133
|
+
return nil unless id
|
134
|
+
|
135
|
+
@members.compute_if_absent(id) do
|
136
|
+
member =
|
137
|
+
begin
|
138
|
+
@client.find(:members, id)
|
139
|
+
rescue Trello::Error => err
|
140
|
+
raise unless err.status_code == 404
|
141
|
+
|
142
|
+
DeletedMember.new(id)
|
143
|
+
end
|
144
|
+
Member.new(
|
145
|
+
id: member.id,
|
146
|
+
full_name: member.full_name,
|
147
|
+
username: member.username
|
148
|
+
)
|
149
|
+
end
|
147
150
|
end
|
148
151
|
end
|
149
152
|
end
|
data/lib/trellodon/cli.rb
CHANGED
@@ -50,8 +50,9 @@ module Trellodon
|
|
50
50
|
opts.on("--clear-auth", "Remove saved api credentials") do
|
51
51
|
config.reload(api_token: nil, api_key: nil)
|
52
52
|
end
|
53
|
+
opts.on("-v", "--verbose", "Print all processes output")
|
53
54
|
|
54
|
-
opts.on_tail("-
|
55
|
+
opts.on_tail("-V", "--version", "Print current version") do
|
55
56
|
puts Trellodon::VERSION
|
56
57
|
exit
|
57
58
|
end
|
@@ -103,28 +104,25 @@ module Trellodon
|
|
103
104
|
def initialize
|
104
105
|
@config = Config.new
|
105
106
|
@scheduler = Schedulers::Inline.new
|
107
|
+
@logger = Config.logger
|
106
108
|
end
|
107
109
|
|
108
110
|
def run
|
109
111
|
check_command!
|
110
112
|
|
111
113
|
need_to_fill_config = config.to_h[:api_key].nil?
|
112
|
-
|
113
|
-
executor = Trellodon::Executor.new(
|
114
|
-
api_key: config.api_key,
|
115
|
-
api_token: config.api_token,
|
116
|
-
formatter: Trellodon::Formatters::Markdown.new(output_dir: config.out),
|
117
|
-
scheduler: scheduler
|
118
|
-
)
|
114
|
+
executor = Trellodon::Executor.new(executor_options)
|
119
115
|
|
120
116
|
ask_save_credentials if need_to_fill_config
|
121
117
|
|
122
118
|
executor.download(config.board)
|
123
119
|
end
|
124
120
|
|
121
|
+
attr_reader :scheduler, :logger
|
122
|
+
|
125
123
|
private
|
126
124
|
|
127
|
-
attr_reader :config
|
125
|
+
attr_reader :config
|
128
126
|
|
129
127
|
def prompt
|
130
128
|
Prompt.instance
|
@@ -139,13 +137,24 @@ module Trellodon
|
|
139
137
|
end
|
140
138
|
|
141
139
|
if command == "dump"
|
142
|
-
change_scheduler(params)
|
140
|
+
change_scheduler(params)
|
141
|
+
@logger.level = 0 if params.key?(:verbose)
|
143
142
|
return
|
144
143
|
end
|
145
144
|
|
146
145
|
raise "Unknown command: #{command}. Available commands: dump"
|
147
146
|
end
|
148
147
|
|
148
|
+
def executor_options
|
149
|
+
{
|
150
|
+
api_key: config.api_key,
|
151
|
+
api_token: config.api_token,
|
152
|
+
formatter: Trellodon::Formatters::Markdown.new(output_dir: config.out, logger: logger),
|
153
|
+
scheduler: scheduler,
|
154
|
+
logger: logger
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
149
158
|
def ask_save_credentials
|
150
159
|
answer = prompt.yes?("Would you like to save api creds so next time trellodon won't asking it?", default: true)
|
151
160
|
config.update_config_file! if answer
|
data/lib/trellodon/client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "uri"
|
3
4
|
require "trello"
|
4
5
|
require "trellodon/entities"
|
5
6
|
|
@@ -17,7 +18,7 @@ module Trellodon
|
|
17
18
|
developer_public_key: api_key,
|
18
19
|
member_token: api_token
|
19
20
|
)
|
20
|
-
@members =
|
21
|
+
@members = Concurrent::Map.new
|
21
22
|
end
|
22
23
|
|
23
24
|
def fetch_board(board_id)
|
@@ -26,7 +27,6 @@ module Trellodon
|
|
26
27
|
|
27
28
|
Board.new(
|
28
29
|
id: board.id,
|
29
|
-
short_id: board.short_url.chars.last(8).join,
|
30
30
|
name: board.name,
|
31
31
|
lists: lists_from(board),
|
32
32
|
card_ids: @client.find_many(Trello::Card, "/boards/#{board_id}/cards", fields: "id").map(&:id),
|
@@ -41,7 +41,6 @@ module Trellodon
|
|
41
41
|
|
42
42
|
Card.new(
|
43
43
|
id: card.id,
|
44
|
-
short_id: card.short_url.chars.last(8).join,
|
45
44
|
name: card.name,
|
46
45
|
desc: card.desc,
|
47
46
|
labels: card.labels.map(&:name),
|
@@ -77,7 +76,7 @@ module Trellodon
|
|
77
76
|
Comment.new(
|
78
77
|
text: comment.data["text"],
|
79
78
|
date: comment.date,
|
80
|
-
creator: member_from(comment)
|
79
|
+
creator: member_from(comment.creator_id)
|
81
80
|
)
|
82
81
|
end
|
83
82
|
end
|
@@ -85,12 +84,13 @@ module Trellodon
|
|
85
84
|
def attachments_from(card)
|
86
85
|
card.attachments.map do |attach|
|
87
86
|
Attachment.new(
|
88
|
-
file_name: attach.file_name,
|
87
|
+
file_name: attach.is_upload ? URI.decode_www_form_component(attach.file_name) : attach.file_name,
|
89
88
|
mime_type: attach.mime_type,
|
90
89
|
bytes: attach.bytes,
|
91
90
|
date: attach.date,
|
92
91
|
name: attach.name,
|
93
92
|
is_upload: attach.is_upload,
|
93
|
+
added_by: member_from(attach.member_id),
|
94
94
|
headers: attach.is_upload ? {"Authorization" => "OAuth oauth_consumer_key=\"#{@client.configuration.developer_public_key}\", oauth_token=\"#{@client.configuration.member_token}\""} : {},
|
95
95
|
url: attach.url
|
96
96
|
)
|
@@ -102,7 +102,7 @@ module Trellodon
|
|
102
102
|
List.new(
|
103
103
|
id: list.id,
|
104
104
|
name: list.name,
|
105
|
-
|
105
|
+
board_id: list.board_id
|
106
106
|
)
|
107
107
|
end
|
108
108
|
end
|
@@ -122,28 +122,31 @@ module Trellodon
|
|
122
122
|
ChecklistItem.new(
|
123
123
|
id: item.id,
|
124
124
|
name: item.name,
|
125
|
-
state: item.state
|
125
|
+
state: item.state,
|
126
|
+
member: member_from(item.member_id),
|
127
|
+
due_date: item.due_date
|
126
128
|
)
|
127
129
|
end
|
128
130
|
end
|
129
131
|
|
130
|
-
def member_from(
|
131
|
-
return
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
132
|
+
def member_from(id)
|
133
|
+
return nil unless id
|
134
|
+
|
135
|
+
@members.compute_if_absent(id) do
|
136
|
+
member =
|
137
|
+
begin
|
138
|
+
@client.find(:members, id)
|
139
|
+
rescue Trello::Error => err
|
140
|
+
raise unless err.status_code == 404
|
141
|
+
|
142
|
+
DeletedMember.new(id)
|
143
|
+
end
|
144
|
+
Member.new(
|
145
|
+
id: member.id,
|
146
|
+
full_name: member.full_name,
|
147
|
+
username: member.username
|
148
|
+
)
|
149
|
+
end
|
147
150
|
end
|
148
151
|
end
|
149
152
|
end
|
data/lib/trellodon/config.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Trellodon
|
4
|
-
Attachment = Struct.new("Attachment", :file_name, :mime_type, :bytes, :date, :name, :is_upload, :headers, :url, keyword_init: true)
|
4
|
+
Attachment = Struct.new("Attachment", :file_name, :mime_type, :bytes, :date, :name, :is_upload, :headers, :url, :added_by, keyword_init: true)
|
5
5
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Trellodon
|
4
|
-
Board = Struct.new("Board", :id, :
|
4
|
+
Board = Struct.new("Board", :id, :name, :card_ids, :lists, :last_activity_date, keyword_init: true) do
|
5
5
|
def get_list(list_id)
|
6
6
|
return nil if lists.nil?
|
7
7
|
lists.find { |list| list.id == list_id }
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Trellodon
|
4
|
-
Card = Struct.new("Card", :id, :
|
4
|
+
Card = Struct.new("Card", :id, :name, :desc, :list_id, :labels, :comments, :attachments, :checklists, :last_activity_date, keyword_init: true)
|
5
5
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Trellodon
|
4
|
-
ChecklistItem = Struct.new("ChecklistItem", :id, :name, :state, keyword_init: true) do
|
4
|
+
ChecklistItem = Struct.new("ChecklistItem", :id, :name, :state, :member, :due_date, keyword_init: true) do
|
5
5
|
def checked?
|
6
6
|
state == "complete"
|
7
7
|
end
|
data/lib/trellodon/executor.rb
CHANGED
@@ -5,12 +5,12 @@ module Trellodon
|
|
5
5
|
BOARD_ID_REGEX = /\/b\/([^\/]+)\//
|
6
6
|
private_constant :BOARD_ID_REGEX
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@api_key = api_key
|
10
|
-
@api_token = api_token
|
11
|
-
@formatter = formatter
|
12
|
-
@logger = logger
|
13
|
-
@scheduler = scheduler
|
8
|
+
def initialize(opts)
|
9
|
+
@api_key = opts.fetch(:api_key)
|
10
|
+
@api_token = opts.fetch(:api_token)
|
11
|
+
@formatter = opts.fetch(:formatter)
|
12
|
+
@logger = opts.fetch(:logger) { Config.logger }
|
13
|
+
@scheduler = opts.fetch(:scheduler)
|
14
14
|
check_credentials!
|
15
15
|
|
16
16
|
@client = Client.new(api_key: @api_key, api_token: @api_token)
|
@@ -11,10 +11,16 @@ module Trellodon
|
|
11
11
|
|
12
12
|
MD_FILENAME = "README.md"
|
13
13
|
ATTACHMENTS_DIRNAME = "attachments"
|
14
|
+
DIRNAME_MAXLENGTH = 50
|
14
15
|
|
15
16
|
def initialize(output_dir:, **opts)
|
16
17
|
super(**opts)
|
17
18
|
@output_dir = output_dir
|
19
|
+
@board_dirname = Concurrent::Map.new
|
20
|
+
@board_subdirs = Concurrent::Map.new
|
21
|
+
@list_dirname = Concurrent::Map.new
|
22
|
+
@list_subdirs = Concurrent::Map.new
|
23
|
+
@card_dirname = Concurrent::Map.new
|
18
24
|
end
|
19
25
|
|
20
26
|
def card_added(card)
|
@@ -37,10 +43,7 @@ module Trellodon
|
|
37
43
|
list = @board.get_list(card.list_id)
|
38
44
|
raise "List #{card.list_id} is not found" if list.nil?
|
39
45
|
|
40
|
-
card_dir = File.join(@output_dir,
|
41
|
-
"#{board.name} [#{board.short_id}]",
|
42
|
-
"#{list.name} [#{list.short_id}]",
|
43
|
-
"#{card.name} [#{card.short_id}]")
|
46
|
+
card_dir = File.join(@output_dir, board_dirname(board), list_dirname(list), card_dirname(card))
|
44
47
|
FileUtils.mkdir_p(card_dir) unless File.directory?(card_dir)
|
45
48
|
card_dir
|
46
49
|
end
|
@@ -51,11 +54,57 @@ module Trellodon
|
|
51
54
|
attachments_dir
|
52
55
|
end
|
53
56
|
|
57
|
+
def board_dirname(board)
|
58
|
+
@board_dirname.compute_if_absent(board.id) { sanitize(board.name) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def list_dirname(list)
|
62
|
+
@list_dirname.compute_if_absent(list.id) do
|
63
|
+
uniq_dirname(
|
64
|
+
dir: sanitize(list.name),
|
65
|
+
entity: list,
|
66
|
+
dirlist: @board_subdirs,
|
67
|
+
parent_id: list.board_id
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def card_dirname(card)
|
73
|
+
@card_dirname.compute_if_absent(card.id) do
|
74
|
+
uniq_dirname(
|
75
|
+
dir: sanitize(card.name),
|
76
|
+
entity: card,
|
77
|
+
dirlist: @list_subdirs,
|
78
|
+
parent_id: card.list_id
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def uniq_dirname(dir:, entity:, dirlist:, parent_id:)
|
84
|
+
return dir unless [List, Card].include?(entity.class)
|
85
|
+
|
86
|
+
dirlist.put_if_absent(parent_id, Concurrent::Map.new)
|
87
|
+
dirlist[parent_id].put_if_absent(dir, Concurrent::Array.new([entity.id]))
|
88
|
+
dirlist[parent_id][dir].push(entity.id) unless dirlist[parent_id][dir].include?(entity.id)
|
89
|
+
idx = dirlist[parent_id][dir].index(entity.id)
|
90
|
+
idx > 0 ? dir + "(#{idx})" : dir
|
91
|
+
end
|
92
|
+
|
93
|
+
def sanitize(name, separator: "")
|
94
|
+
name.gsub(/[\/\\"*?<>|:]+/, separator).slice(0, DIRNAME_MAXLENGTH)
|
95
|
+
end
|
96
|
+
|
54
97
|
def download_attachments(card)
|
55
98
|
return if card.attachments.nil? || card.attachments.empty?
|
99
|
+
|
56
100
|
attachments_path = card_attachments_path(card)
|
57
101
|
card.attachments.select(&:is_upload).each do |att|
|
58
|
-
|
102
|
+
att_file_path = File.join(attachments_path, att.file_name)
|
103
|
+
if File.file?(att_file_path)
|
104
|
+
logger.warn "Attachment file already exists: #{att_file_path}"
|
105
|
+
else
|
106
|
+
IO.copy_stream(URI.parse(att.url).open(att.headers), att_file_path)
|
107
|
+
end
|
59
108
|
end
|
60
109
|
end
|
61
110
|
|
@@ -73,6 +122,7 @@ module Trellodon
|
|
73
122
|
---
|
74
123
|
title: #{card.name}
|
75
124
|
last_updated_at: #{card.last_activity_date}
|
125
|
+
dumped_at: #{Time.now}
|
76
126
|
labels: #{create_card_labels(card)}
|
77
127
|
---
|
78
128
|
EOS
|
@@ -96,11 +146,13 @@ module Trellodon
|
|
96
146
|
|
97
147
|
def create_card_labels(card)
|
98
148
|
return "" if card.labels.nil? || card.labels.empty?
|
149
|
+
|
99
150
|
card.labels.map { |label| "\n - " + label }.join
|
100
151
|
end
|
101
152
|
|
102
153
|
def create_card_checklists(card)
|
103
154
|
return "" if card.checklists.nil? || card.checklists.empty?
|
155
|
+
|
104
156
|
<<~EOS
|
105
157
|
|
106
158
|
## Checklists
|
@@ -117,11 +169,15 @@ module Trellodon
|
|
117
169
|
end
|
118
170
|
|
119
171
|
def create_card_checklist_item(item)
|
120
|
-
"\n- [#{item.checked? ? "x" : " "}] #{item.name}"
|
172
|
+
formatted_item = "\n- [#{item.checked? ? "x" : " "}] #{item.name}"
|
173
|
+
formatted_item += " | *assigned_to: #{create_member(item.member)}*" if item.member
|
174
|
+
formatted_item += " | *due: #{item.due_date}*" if item.due_date
|
175
|
+
formatted_item
|
121
176
|
end
|
122
177
|
|
123
178
|
def create_card_comments(card)
|
124
179
|
return "" if card.comments.nil? || card.comments.empty?
|
180
|
+
|
125
181
|
<<~EOS
|
126
182
|
|
127
183
|
## Comments
|
@@ -132,13 +188,15 @@ module Trellodon
|
|
132
188
|
def create_card_comment(comment)
|
133
189
|
<<~EOS
|
134
190
|
|
135
|
-
|
191
|
+
### #{create_member(comment.creator)} at #{comment.date}
|
192
|
+
|
136
193
|
#{comment.text}
|
137
194
|
EOS
|
138
195
|
end
|
139
196
|
|
140
197
|
def create_card_attachments(card)
|
141
198
|
return "" if card.attachments.nil? || card.attachments.empty?
|
199
|
+
|
142
200
|
<<~EOS
|
143
201
|
|
144
202
|
## Attachments
|
@@ -152,6 +210,7 @@ module Trellodon
|
|
152
210
|
### #{attachment.name}
|
153
211
|
|
154
212
|
**date**: #{attachment.date}
|
213
|
+
**added_by**: #{create_member(attachment.added_by)}
|
155
214
|
**url**: #{attachment.url}
|
156
215
|
**local copy**: #{create_attachment_local_link(attachment)}
|
157
216
|
EOS
|
@@ -159,8 +218,14 @@ module Trellodon
|
|
159
218
|
|
160
219
|
def create_attachment_local_link(attachment)
|
161
220
|
return "-" unless attachment.is_upload
|
221
|
+
|
162
222
|
"[#{attachment.file_name}](#{File.join("./", ATTACHMENTS_DIRNAME, attachment.file_name)})"
|
163
223
|
end
|
224
|
+
|
225
|
+
def create_member(member)
|
226
|
+
return "" unless member
|
227
|
+
"#{member.full_name} (@#{member.username})"
|
228
|
+
end
|
164
229
|
end
|
165
230
|
end
|
166
231
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module Trellodon
|
6
|
+
class Journal
|
7
|
+
FILENAME = "journal.yml"
|
8
|
+
|
9
|
+
def initialize(dir)
|
10
|
+
@location = File.join(dir, FILENAME)
|
11
|
+
File.write(location, "") unless File.exist?(location)
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(record)
|
15
|
+
File.write(location, record.to_yaml, mode: "a+")
|
16
|
+
end
|
17
|
+
|
18
|
+
private attr_reader :location
|
19
|
+
end
|
20
|
+
end
|
data/lib/trellodon/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trellodon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fargelus
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2022-04-
|
13
|
+
date: 2022-04-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: anyway_config
|
@@ -69,7 +69,7 @@ dependencies:
|
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '0'
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
|
-
name: ruby-next
|
72
|
+
name: ruby-next
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
75
|
- - ">="
|
@@ -111,6 +111,7 @@ files:
|
|
111
111
|
- lib/trellodon/entities/attachment.rb
|
112
112
|
- lib/trellodon/entities/board.rb
|
113
113
|
- lib/trellodon/entities/card.rb
|
114
|
+
- lib/trellodon/entities/card_event.rb
|
114
115
|
- lib/trellodon/entities/checklist.rb
|
115
116
|
- lib/trellodon/entities/checklist_item.rb
|
116
117
|
- lib/trellodon/entities/comment.rb
|
@@ -120,6 +121,7 @@ files:
|
|
120
121
|
- lib/trellodon/formatters.rb
|
121
122
|
- lib/trellodon/formatters/base.rb
|
122
123
|
- lib/trellodon/formatters/markdown.rb
|
124
|
+
- lib/trellodon/journal.rb
|
123
125
|
- lib/trellodon/schedulers.rb
|
124
126
|
- lib/trellodon/schedulers/base.rb
|
125
127
|
- lib/trellodon/schedulers/concurrent.rb
|