trellodon 0.2.1 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9931b6c34e6a8fc7c74261de329037838ac5fc098b5436e146f3c6a38068ec09
4
- data.tar.gz: a81d60ff2a430f2dd23a7ae7d4ca0ddb958f47e1539ffa16fb9f2ca298ad3301
3
+ metadata.gz: a61911e92509a77f13aa90f9f56c14d038dbe443b4e1063d28f66b6745b04432
4
+ data.tar.gz: '079609a773107e786a7d795fe7d23564ded2aa66e8d587a6fd22b4e288e15478'
5
5
  SHA512:
6
- metadata.gz: 3831a6d818d121e245b6a6f87ab11eb80f58ac7814e4fd10423b59d131a2e3a52234ed724b25aeb5f98de5473a1350d67f330d95faeff1f9e05025ca2e379d0e
7
- data.tar.gz: 0adca9ec2bef56ae020c840e912a3a4276c9da1b5eefb66f294215232b994c52e8bb148c896ff292eac683c824a9b9cfbaf6bee3725948523be1375e2dde5f8c
6
+ metadata.gz: 617119b48c1f2385c87d57ffd2683933097ed1aae11a51577f65e16012ab1f58b65187f57884123ed04ba522797925826e3cf202e7d4dad9d1fd63a3d736bcef
7
+ data.tar.gz: 30c1f1bcb2df2c69a3c8d0cebc93a597f16d2207d33bef979670134d306bd0173cc97948f38f739cf1d6b5eef0a7a602112fb0de40021d078d0188280ca4d039
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [master]
4
4
 
5
+ ## [0.3.0] - 2022-04-12 🚀🚀🚀
6
+
7
+ - Handle missing members. ([@palkan][])
8
+
9
+ - Use inline scheduler by default. ([@palkan][])
10
+
11
+ - Replace `--sync` with `--concurrency=0`. ([@palkan][])
12
+
5
13
  ## [0.2.1] - 2022-04-12 🚀
6
14
 
7
15
  - Misc changes.
data/README.md CHANGED
@@ -31,10 +31,12 @@ To generate them, go to the [trello.com/app-key](https://trello.com/app-key) pag
31
31
  ## Usage
32
32
 
33
33
  ```sh
34
- Usage: trellodon dump
34
+ Usage: trellodon dump [options]
35
35
  Options:
36
36
  --board VALUE Board URL or ID
37
37
  --out VALUE Destination folder path
38
+ --concurrency VALUE Amount of processing threads (default: 4). Set to 0 to execute API requests in-line
39
+ --clear-auth Remove saved api credentials
38
40
  -v, --version Print current version
39
41
  -h, --help Print help
40
42
  ```
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "trellodon/api_client"
4
- require "trellodon/config"
5
-
6
3
  module Trellodon
7
- class APIExecutor
4
+ class Executor
8
5
  BOARD_ID_REGEX = /\/b\/([^\/]+)\//
9
6
  private_constant :BOARD_ID_REGEX
10
7
 
@@ -16,7 +13,7 @@ module Trellodon
16
13
  @scheduler = scheduler
17
14
  check_credentials!
18
15
 
19
- @api_client = APIClient.new(api_key: @api_key, api_token: @api_token)
16
+ @client = Client.new(api_key: @api_key, api_token: @api_token)
20
17
  end
21
18
 
22
19
  def download(board_pointer)
@@ -25,13 +22,13 @@ module Trellodon
25
22
  startup_time = Time.now
26
23
  logger.debug "Fetching board 🚀️️"
27
24
  board = scheduler.post do
28
- api_client.fetch_board(board_id).tap { formatter.board_added(_1) }
25
+ client.fetch_board(board_id).tap { |_1| formatter.board_added(_1) }
29
26
  end.value
30
27
 
31
- logger.debug "Fetching cards in board with comments and attachments 🐢"
28
+ logger.debug "Fetching #{board.card_ids.size} cards from the board with comments and attachments 🐢"
32
29
  board.card_ids.map do |card_id|
33
30
  scheduler.post do
34
- api_client.fetch_card(card_id).tap { formatter.card_added(_1) }
31
+ client.fetch_card(card_id).tap { |_1| formatter.card_added(_1) }
35
32
  end
36
33
  end.map(&:value)
37
34
 
@@ -41,7 +38,7 @@ module Trellodon
41
38
 
42
39
  private
43
40
 
44
- attr_reader :api_client, :board_id, :logger, :scheduler, :formatter
41
+ attr_reader :client, :board_id, :logger, :scheduler, :formatter
45
42
 
46
43
  def check_credentials!
47
44
  return if @api_key.to_s.size.positive? && @api_token.to_s.size.positive?
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trello"
4
+ require "trellodon/entities"
5
+
6
+ module Trellodon
7
+ class Client
8
+ class DeletedMember < Struct.new(:id)
9
+ def full_name ; "[DELETED]"; end
10
+
11
+ def username ; "__deleted__"; end
12
+ end
13
+
14
+ def initialize(api_key:, api_token:, logger: Config.logger)
15
+ @logger = logger
16
+ @client = Trello::Client.new(
17
+ developer_public_key: api_key,
18
+ member_token: api_token
19
+ )
20
+ @members = {}
21
+ end
22
+
23
+ def fetch_board(board_id)
24
+ retrying do
25
+ board = @client.find(:boards, board_id)
26
+
27
+ Board.new(
28
+ id: board.id,
29
+ short_id: board.short_url.chars.last(8).join,
30
+ name: board.name,
31
+ lists: lists_from(board),
32
+ card_ids: @client.find_many(Trello::Card, "/boards/#{board_id}/cards", fields: "id").map(&:id),
33
+ last_activity_date: board.last_activity_date
34
+ )
35
+ end
36
+ end
37
+
38
+ def fetch_card(card_id)
39
+ retrying do
40
+ card = @client.find(:cards, card_id)
41
+
42
+ Card.new(
43
+ id: card.id,
44
+ short_id: card.short_url.chars.last(8).join,
45
+ name: card.name,
46
+ desc: card.desc,
47
+ labels: card.labels.map(&:name),
48
+ list_id: card.list_id,
49
+ comments: comments_from(card),
50
+ attachments: attachments_from(card),
51
+ checklists: checklists_from(card),
52
+ last_activity_date: card.last_activity_date
53
+ )
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :logger
60
+
61
+ def retrying(&block)
62
+ attempt = 0
63
+ begin
64
+ block.call
65
+ rescue Trello::Error => err
66
+ raise unless err.status_code == 429
67
+ attempt += 1
68
+ cooldown = 2**attempt + rand(2**attempt) - 2**(attempt - 1)
69
+ logger.warn "API limit exceeded, cool down for #{cooldown}s"
70
+ sleep cooldown
71
+ retry
72
+ end
73
+ end
74
+
75
+ def comments_from(card)
76
+ card.comments.map do |comment|
77
+ Comment.new(
78
+ text: comment.data["text"],
79
+ date: comment.date,
80
+ creator: member_from(comment)
81
+ )
82
+ end
83
+ end
84
+
85
+ def attachments_from(card)
86
+ card.attachments.map do |attach|
87
+ Attachment.new(
88
+ file_name: attach.file_name,
89
+ mime_type: attach.mime_type,
90
+ bytes: attach.bytes,
91
+ date: attach.date,
92
+ name: attach.name,
93
+ is_upload: attach.is_upload,
94
+ headers: attach.is_upload ? {"Authorization" => "OAuth oauth_consumer_key=\"#{@client.configuration.developer_public_key}\", oauth_token=\"#{@client.configuration.member_token}\""} : {},
95
+ url: attach.url
96
+ )
97
+ end
98
+ end
99
+
100
+ def lists_from(board)
101
+ board.lists.map do |list|
102
+ List.new(
103
+ id: list.id,
104
+ name: list.name,
105
+ short_id: board.short_url.chars.last(8).join
106
+ )
107
+ end
108
+ end
109
+
110
+ def checklists_from(card)
111
+ card.checklists.map do |checklist|
112
+ Checklist.new(
113
+ id: checklist.id,
114
+ name: checklist.name,
115
+ items: checklist_items_from(checklist)
116
+ )
117
+ end
118
+ end
119
+
120
+ def checklist_items_from(checklist)
121
+ checklist.items.map do |item|
122
+ ChecklistItem.new(
123
+ id: item.id,
124
+ name: item.name,
125
+ state: item.state
126
+ )
127
+ end
128
+ end
129
+
130
+ def member_from(comment)
131
+ return @members[comment.creator_id] if @members.has_key? comment.creator_id
132
+
133
+ member =
134
+ begin
135
+ @client.find(:members, comment.creator_id)
136
+ rescue Trello::Error => err
137
+ raise unless err.status_code == 404
138
+
139
+ DeletedMember.new(comment.creator_id)
140
+ end
141
+
142
+ @members[comment.creator_id] = Member.new(
143
+ id: member.id,
144
+ full_name: member.full_name,
145
+ username: member.username
146
+ )
147
+ end
148
+ end
149
+ end
@@ -8,7 +8,7 @@ module Trellodon
8
8
  class Concurrent < Base
9
9
  using RubyNext
10
10
 
11
- DEFAULT_MAX_THREADS = 50
11
+ DEFAULT_MAX_THREADS = 4
12
12
 
13
13
  attr_reader :max_threads
14
14
 
data/lib/trellodon/cli.rb CHANGED
@@ -46,8 +46,7 @@ module Trellodon
46
46
  opts.banner = "Usage: trellodon dump \e[34m[options]\e[0m\n"\
47
47
  "Options:\n"
48
48
 
49
- opts.on("--sync", "Process API requests synchronously")
50
- opts.on("--concurrency VALUE", "Amount of processing threads (default: 50)")
49
+ opts.on("--concurrency VALUE", "Amount of processing threads (default: 4)")
51
50
  opts.on("--clear-auth", "Remove saved api credentials") do
52
51
  config.reload(api_token: nil, api_key: nil)
53
52
  end
@@ -103,7 +102,7 @@ module Trellodon
103
102
 
104
103
  def initialize
105
104
  @config = Config.new
106
- @scheduler = Schedulers::Concurrent.new
105
+ @scheduler = Schedulers::Inline.new
107
106
  end
108
107
 
109
108
  def run
@@ -111,15 +110,16 @@ module Trellodon
111
110
 
112
111
  need_to_fill_config = config.to_h[:api_key].nil?
113
112
 
114
- executor = Trellodon::APIExecutor.new(
113
+ executor = Trellodon::Executor.new(
115
114
  api_key: config.api_key,
116
115
  api_token: config.api_token,
117
116
  formatter: Trellodon::Formatters::Markdown.new(output_dir: config.out),
118
117
  scheduler: scheduler
119
118
  )
120
- executor.download(config.board)
121
119
 
122
120
  ask_save_credentials if need_to_fill_config
121
+
122
+ executor.download(config.board)
123
123
  end
124
124
 
125
125
  private
@@ -152,14 +152,12 @@ module Trellodon
152
152
  end
153
153
 
154
154
  def change_scheduler(params)
155
- if params.key?(:sync)
156
- @scheduler = Schedulers::Inline.new
157
- return
158
- end
159
-
160
155
  return unless params[:concurrency]
161
156
 
162
- threads = params[:concurrency].to_i
157
+ threads = Integer(params[:concurrency])
158
+
159
+ return @scheduler = Schedulers::Inline.new if threads.zero?
160
+
163
161
  raise "Value of concurrency option must be integer and greater than zero" unless threads.positive?
164
162
 
165
163
  @scheduler = Schedulers::Concurrent.new(threads)
@@ -4,7 +4,13 @@ require "trello"
4
4
  require "trellodon/entities"
5
5
 
6
6
  module Trellodon
7
- class APIClient
7
+ class Client
8
+ class DeletedMember < Struct.new(:id)
9
+ def full_name = "[DELETED]"
10
+
11
+ def username = "__deleted__"
12
+ end
13
+
8
14
  def initialize(api_key:, api_token:, logger: Config.logger)
9
15
  @logger = logger
10
16
  @client = Trello::Client.new(
@@ -124,7 +130,15 @@ module Trellodon
124
130
  def member_from(comment)
125
131
  return @members[comment.creator_id] if @members.has_key? comment.creator_id
126
132
 
127
- member = @client.find(:members, comment.creator_id)
133
+ member =
134
+ begin
135
+ @client.find(:members, comment.creator_id)
136
+ rescue Trello::Error => err
137
+ raise unless err.status_code == 404
138
+
139
+ DeletedMember.new(comment.creator_id)
140
+ end
141
+
128
142
  @members[comment.creator_id] = Member.new(
129
143
  id: member.id,
130
144
  full_name: member.full_name,
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "trellodon/api_client"
4
- require "trellodon/config"
5
-
6
3
  module Trellodon
7
- class APIExecutor
4
+ class Executor
8
5
  BOARD_ID_REGEX = /\/b\/([^\/]+)\//
9
6
  private_constant :BOARD_ID_REGEX
10
7
 
@@ -16,7 +13,7 @@ module Trellodon
16
13
  @scheduler = scheduler
17
14
  check_credentials!
18
15
 
19
- @api_client = APIClient.new(api_key: @api_key, api_token: @api_token)
16
+ @client = Client.new(api_key: @api_key, api_token: @api_token)
20
17
  end
21
18
 
22
19
  def download(board_pointer)
@@ -25,13 +22,13 @@ module Trellodon
25
22
  startup_time = Time.now
26
23
  logger.debug "Fetching board 🚀️️"
27
24
  board = scheduler.post do
28
- api_client.fetch_board(board_id).tap { |_1| formatter.board_added(_1) }
25
+ client.fetch_board(board_id).tap { formatter.board_added(_1) }
29
26
  end.value
30
27
 
31
- logger.debug "Fetching cards in board with comments and attachments 🐢"
28
+ logger.debug "Fetching #{board.card_ids.size} cards from the board with comments and attachments 🐢"
32
29
  board.card_ids.map do |card_id|
33
30
  scheduler.post do
34
- api_client.fetch_card(card_id).tap { |_1| formatter.card_added(_1) }
31
+ client.fetch_card(card_id).tap { formatter.card_added(_1) }
35
32
  end
36
33
  end.map(&:value)
37
34
 
@@ -41,7 +38,7 @@ module Trellodon
41
38
 
42
39
  private
43
40
 
44
- attr_reader :api_client, :board_id, :logger, :scheduler, :formatter
41
+ attr_reader :client, :board_id, :logger, :scheduler, :formatter
45
42
 
46
43
  def check_credentials!
47
44
  return if @api_key.to_s.size.positive? && @api_token.to_s.size.positive?
@@ -8,7 +8,7 @@ module Trellodon
8
8
  class Concurrent < Base
9
9
  using RubyNext
10
10
 
11
- DEFAULT_MAX_THREADS = 50
11
+ DEFAULT_MAX_THREADS = 4
12
12
 
13
13
  attr_reader :max_threads
14
14
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trellodon
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/trellodon.rb CHANGED
@@ -7,7 +7,8 @@ RubyNext::Language.setup_gem_load_path(transpile: true)
7
7
 
8
8
  require "trellodon/version"
9
9
  require "trellodon/config"
10
- require "trellodon/api_executor"
10
+ require "trellodon/client"
11
+ require "trellodon/executor"
11
12
 
12
13
  require "trellodon/entities"
13
14
  require "trellodon/schedulers"
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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fargelus
@@ -100,12 +100,12 @@ files:
100
100
  - bin/console
101
101
  - bin/setup
102
102
  - bin/trellodon
103
- - lib/.rbnext/2.7/trellodon/api_executor.rb
103
+ - lib/.rbnext/2.7/trellodon/executor.rb
104
+ - lib/.rbnext/3.0/trellodon/client.rb
104
105
  - lib/.rbnext/3.1/trellodon/schedulers/concurrent.rb
105
106
  - lib/trellodon.rb
106
- - lib/trellodon/api_client.rb
107
- - lib/trellodon/api_executor.rb
108
107
  - lib/trellodon/cli.rb
108
+ - lib/trellodon/client.rb
109
109
  - lib/trellodon/config.rb
110
110
  - lib/trellodon/entities.rb
111
111
  - lib/trellodon/entities/attachment.rb
@@ -116,6 +116,7 @@ files:
116
116
  - lib/trellodon/entities/comment.rb
117
117
  - lib/trellodon/entities/list.rb
118
118
  - lib/trellodon/entities/member.rb
119
+ - lib/trellodon/executor.rb
119
120
  - lib/trellodon/formatters.rb
120
121
  - lib/trellodon/formatters/base.rb
121
122
  - lib/trellodon/formatters/markdown.rb