trellodon 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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