trellodon 0.1.0 → 0.2.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: bf1b8715b6cbad2c1bd3ab4fce72198a442380cf4f2d7bb39c55928ef72b032f
4
- data.tar.gz: 2caa2a3a24fe463cc1cb730a3aad68928323ec0fea04fea8e3a0f10ebf051ea7
3
+ metadata.gz: 9de6b483c5d9dd4a4dc5a669a4ce6d3ba56cac3575e9d9174ae8603e7f6adf9a
4
+ data.tar.gz: 834fbc2e3c2da99e0b69d5989da18d476976d6e4912077579d04898c24ab6f7b
5
5
  SHA512:
6
- metadata.gz: b50b3279f7a9e787b2f2e718109902a9cda88729f7d7b330aa5db9db944f913af33d6f26fab7b1f6c4d2866a1241d62d99c4dbe989ea468499bcaaa4908a153f
7
- data.tar.gz: 52d86ebd0b43ad0b064facbfb66332623422b2c688eff732188b7ea2ce8baba2842a331a941e1aafb410f114c31aef3ec80f7588b7f6a07808070019f9eb12ba
6
+ metadata.gz: ced2b722b7a3f8536a0fbf958b3e3dfdb9e71921d7658c31f69c1b817b0cc88b4d25d5ccd7c94dd7db7d97e0109fceba574c96c51a2ed7910f74645225f51442
7
+ data.tar.gz: 99b59ce531022d3f341d0c97306bb24cec800d5bfd6590b7342b16bc8c138bab853a8007203b382f5ccedd94cb060dd0f459a28502b394e687ab3ce57086934d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
- ## [Unreleased]
1
+ # Change log
2
+
3
+ ## [master]
4
+
5
+ ## [0.2.0] - 2022-04-12
6
+
7
+ - Store Trello API creds locally for avoid asking it before execution ([@fargelus][]).
8
+ - Download attachments, human names in comments, rename index.md to README.md ([@rinasergeeva][]).
9
+ - Added minimal project README.md ([@rinasergeeva][], [@fargelus][]).
10
+ - --sync param for switch to `Schedulers::Inline` in CLI ([@fargelus][]).
11
+ - Concurrent scheduler with customizable thread pool size ([@fargelus][]).
12
+ - Checklists support ([@rinasergeeva][]).
2
13
 
3
14
  ## [0.1.0] - 2022-04-07
4
15
 
5
16
  - Initial release
17
+
18
+ [@fargelus]: https://github.com/fargelus
19
+ [@rinasergeeva]: https://github.com/rinasergeeva
data/README.md CHANGED
@@ -1,3 +1,90 @@
1
- # trellodon
1
+ [![Build](https://github.com/evilmartians/trellodon/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/evilmartians/trellodon/actions/workflows/main.yml)
2
+ [![Gem Version](https://badge.fury.io/rb/trellodon.svg)](https://rubygems.org/gems/trellodon)
2
3
 
3
- Dump Trello boards to your file system
4
+ # Trellodon
5
+
6
+ A Ruby tool to export a Trello board and convert it into a set of folders and markdown files, corresponding to lists and cards on the board. For each card, you’ll get the basic details such as:
7
+
8
+ * Name
9
+ * Description
10
+ * Labels
11
+ * Comments
12
+ * Attachments
13
+
14
+ <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
15
+
16
+ ## Installation
17
+
18
+ ```
19
+ gem install trellodon
20
+ ```
21
+
22
+ ## Prerequisites
23
+
24
+ Trellodon needs two secret codes to download your boards:
25
+
26
+ * Trello API key
27
+ * Authentication token
28
+
29
+ To generate them, go to the [trello.com/app-key](https://trello.com/app-key) page and follow the instructions.
30
+
31
+ ## Usage
32
+
33
+ ```sh
34
+ Usage: trellodon dump
35
+ Options:
36
+ --board VALUE Board URL or ID
37
+ --out VALUE Destination folder path
38
+ -v, --version Print current version
39
+ -h, --help Print help
40
+ ```
41
+
42
+ ### Detailed example
43
+
44
+ Suppose we have a Trello board called `projects` with lists `Brainstorm`, `TODO`, `DOING`, `DONE`. Lists contains own cards.<br>
45
+ All Trello boards have its own id this id requires Trellodon to fetch board via Trello API.
46
+ You can find this id in two ways:
47
+
48
+ * In URL `https://trello.com/b/{id}/projects` your Trello board id will be placed as `{id}` in example;
49
+ * Put `.json` in your board URL like this `https://trello.com/b/{id}/projects.json` id field in json output is what you need;
50
+
51
+ You can also paste whole board URL and Trellodon will parse it correctly.<br>
52
+ After launch Trellodon creates follow output in specified folder:
53
+
54
+ ```sh
55
+ projects/
56
+ Brainstorm/
57
+ first_card_title/
58
+ README.md
59
+ attachments/
60
+ image.png
61
+ TODO/
62
+ first_card_title/
63
+ README.md
64
+ attachments/
65
+ report.docx
66
+ ...
67
+ ```
68
+
69
+ Each card has follow output format:
70
+
71
+ ```md
72
+ ---
73
+ title: card_title
74
+ last_updated_at: 2022-03-16 16:28:39 UTC
75
+ labels: Test
76
+ ---
77
+
78
+ # card_title
79
+
80
+ ## Description
81
+ Some card description
82
+
83
+ ## Comments
84
+ ** John Doe @doe at 2022-03-16 16:28:39 UTC**
85
+
86
+ ## Attachments
87
+ ### Image.png
88
+ **date**: 2022-03-16
89
+ **url**: https://trello.com/1/cards/#{card_id}/attachments/#{attachment.id}/download/image.png
90
+ ```
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trellodon/api_client"
4
+ require "trellodon/config"
5
+
6
+ module Trellodon
7
+ class APIExecutor
8
+ BOARD_ID_REGEX = /\/b\/([^\/]+)\//
9
+ private_constant :BOARD_ID_REGEX
10
+
11
+ def initialize(api_key:, api_token:, scheduler:, formatter:, logger: Config.logger)
12
+ @api_key = api_key
13
+ @api_token = api_token
14
+ @formatter = formatter
15
+ @logger = logger
16
+ @scheduler = scheduler
17
+ check_credentials!
18
+
19
+ @api_client = APIClient.new(api_key: @api_key, api_token: @api_token)
20
+ end
21
+
22
+ def download(board_pointer)
23
+ extract_board_id(board_pointer)
24
+
25
+ startup_time = Time.now
26
+ logger.debug "Fetching board 🚀️️"
27
+ board = scheduler.post do
28
+ api_client.fetch_board(board_id).tap { |_1| formatter.board_added(_1) }
29
+ end.value
30
+
31
+ logger.debug "Fetching cards in board with comments and attachments 🐢"
32
+ board.card_ids.map do |card_id|
33
+ scheduler.post do
34
+ api_client.fetch_card(card_id).tap { |_1| formatter.card_added(_1) }
35
+ end
36
+ end.map(&:value)
37
+
38
+ formatter.finish
39
+ logger.debug "All Trello API requests finished in #{(Time.now - startup_time).to_i} seconds ⌛"
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :api_client, :board_id, :logger, :scheduler, :formatter
45
+
46
+ def check_credentials!
47
+ return if @api_key.to_s.size.positive? && @api_token.to_s.size.positive?
48
+
49
+ raise ArgumentError, "Missing credentials. Please fill out both api_key, api_token first."
50
+ end
51
+
52
+ def extract_board_id(board_pointer)
53
+ @board_id = if URI::DEFAULT_PARSER.make_regexp.match?(board_pointer)
54
+ parse_board_url(board_pointer)
55
+ else
56
+ board_pointer
57
+ end
58
+ end
59
+
60
+ def parse_board_url(board_url)
61
+ rel_path = URI.parse(board_url).request_uri
62
+ match_data = rel_path.match(BOARD_ID_REGEX)
63
+ raise ArgumentError, "Wrong trello board url" if match_data.nil? || match_data.size != 2
64
+
65
+ match_data[1]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trellodon/schedulers/base"
4
+ require "concurrent"
5
+
6
+ module Trellodon
7
+ module Schedulers
8
+ class Concurrent < Base
9
+ using RubyNext
10
+
11
+ MAX_THREADS = 50
12
+
13
+ def post(&block)
14
+ ::Concurrent::Future.execute(executor: executor, &block)
15
+ end
16
+
17
+ private def executor
18
+ ::Concurrent::FixedThreadPool.new(MAX_THREADS)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "trello"
4
-
5
4
  require "trellodon/entities"
6
5
 
7
6
  module Trellodon
@@ -12,6 +11,7 @@ module Trellodon
12
11
  developer_public_key: api_key,
13
12
  member_token: api_token
14
13
  )
14
+ @members = {}
15
15
  end
16
16
 
17
17
  def fetch_board(board_id)
@@ -42,6 +42,7 @@ module Trellodon
42
42
  list_id: card.list_id,
43
43
  comments: comments_from(card),
44
44
  attachments: attachments_from(card),
45
+ checklists: checklists_from(card),
45
46
  last_activity_date: card.last_activity_date
46
47
  )
47
48
  end
@@ -57,15 +58,10 @@ module Trellodon
57
58
  block.call
58
59
  rescue Trello::Error => err
59
60
  raise unless err.status_code == 429
60
-
61
61
  attempt += 1
62
-
63
62
  cooldown = 2**attempt + rand(2**attempt) - 2**(attempt - 1)
64
-
65
63
  logger.warn "API limit exceeded, cool down for #{cooldown}s"
66
-
67
64
  sleep cooldown
68
-
69
65
  retry
70
66
  end
71
67
  end
@@ -75,7 +71,7 @@ module Trellodon
75
71
  Comment.new(
76
72
  text: comment.data["text"],
77
73
  date: comment.date,
78
- creator_id: comment.creator_id
74
+ creator: member_from(comment)
79
75
  )
80
76
  end
81
77
  end
@@ -86,6 +82,10 @@ module Trellodon
86
82
  file_name: attach.file_name,
87
83
  mime_type: attach.mime_type,
88
84
  bytes: attach.bytes,
85
+ date: attach.date,
86
+ name: attach.name,
87
+ is_upload: attach.is_upload,
88
+ headers: attach.is_upload ? {"Authorization" => "OAuth oauth_consumer_key=\"#{@client.configuration.developer_public_key}\", oauth_token=\"#{@client.configuration.member_token}\""} : {},
89
89
  url: attach.url
90
90
  )
91
91
  end
@@ -100,5 +100,36 @@ module Trellodon
100
100
  )
101
101
  end
102
102
  end
103
+
104
+ def checklists_from(card)
105
+ card.checklists.map do |checklist|
106
+ Checklist.new(
107
+ id: checklist.id,
108
+ name: checklist.name,
109
+ items: checklist_items_from(checklist)
110
+ )
111
+ end
112
+ end
113
+
114
+ def checklist_items_from(checklist)
115
+ checklist.items.map do |item|
116
+ ChecklistItem.new(
117
+ id: item.id,
118
+ name: item.name,
119
+ state: item.state
120
+ )
121
+ end
122
+ end
123
+
124
+ def member_from(comment)
125
+ return @members[comment.creator_id] if @members.has_key? comment.creator_id
126
+
127
+ member = @client.find(:members, comment.creator_id)
128
+ @members[comment.creator_id] = Member.new(
129
+ id: member.id,
130
+ full_name: member.full_name,
131
+ username: member.username
132
+ )
133
+ end
103
134
  end
104
135
  end
data/lib/trellodon/cli.rb CHANGED
@@ -1,36 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "trellodon"
4
-
5
4
  require "tty-prompt"
5
+ require "fileutils"
6
6
 
7
7
  module Trellodon
8
8
  class CLI
9
- class Prompt
10
- def initialize
11
- @prompt = TTY::Prompt.new
12
- end
9
+ class Prompt < TTY::Prompt
10
+ include Singleton
13
11
 
14
12
  def ask_api_key
15
- @prompt.mask(
13
+ mask(
16
14
  "Provide your Developer API Key (see https://trello.com/app-key):"
17
15
  ) { |q| q.required true }
18
16
  end
19
17
 
20
18
  def ask_api_token
21
- @prompt.mask(
19
+ mask(
22
20
  "Provide your API token:"
23
21
  ) { |q| q.required true }
24
22
  end
25
23
 
26
24
  def ask_board
27
- @prompt.ask("Which board would you like to dump? (URL or ID)") do |q|
25
+ ask("Which board would you like to dump? (URL or ID)") do |q|
28
26
  q.required true
29
27
  end
30
28
  end
31
29
 
32
30
  def ask_folder
33
- @prompt.ask("Destination folder?", default: "./")
31
+ ask("Destination folder?", default: "./")
34
32
  end
35
33
  end
36
34
 
@@ -44,10 +42,16 @@ module Trellodon
44
42
  out: "Destination folder path"
45
43
  )
46
44
 
47
- extend_options do |opts, _|
48
- opts.banner = "Usage: trellodon dump\n"\
45
+ extend_options do |opts, config|
46
+ opts.banner = "Usage: trellodon dump \e[34m[options]\e[0m\n"\
49
47
  "Options:\n"
50
48
 
49
+ opts.on("--sync", "Process API requests synchronously")
50
+ opts.on("--concurrency VALUE", "Amount of processing threads (default: 50)")
51
+ opts.on("--clear-auth", "Remove saved api credentials") do
52
+ config.reload(api_token: nil, api_key: nil)
53
+ end
54
+
51
55
  opts.on_tail("-v", "--version", "Print current version") do
52
56
  puts Trellodon::VERSION
53
57
  exit
@@ -59,8 +63,25 @@ module Trellodon
59
63
  end
60
64
  end
61
65
 
66
+ def resolve_config_path(_, _)
67
+ config_path
68
+ end
69
+
70
+ def config_path
71
+ File.join(Dir.home, ".config", "#{config_name}.yml")
72
+ end
73
+
74
+ def update_config_file!
75
+ dir = File.dirname(config_path)
76
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
77
+
78
+ yml = "api_token: #{api_token}\n"\
79
+ "api_key: #{api_key}"
80
+ File.write(config_path, yml)
81
+ end
82
+
62
83
  private def prompt
63
- @prompt ||= Prompt.new
84
+ Prompt.instance
64
85
  end
65
86
 
66
87
  def api_key
@@ -82,34 +103,66 @@ module Trellodon
82
103
 
83
104
  def initialize
84
105
  @config = Config.new
106
+ @scheduler = Schedulers::Concurrent.new
85
107
  end
86
108
 
87
109
  def run
88
110
  check_command!
89
111
 
112
+ need_to_fill_config = config.to_h[:api_key].nil?
113
+
90
114
  executor = Trellodon::APIExecutor.new(
91
115
  api_key: config.api_key,
92
116
  api_token: config.api_token,
93
117
  formatter: Trellodon::Formatters::Markdown.new(output_dir: config.out),
94
- scheduler: Schedulers::Threaded.new
118
+ scheduler: scheduler
95
119
  )
96
120
  executor.download(config.board)
121
+
122
+ ask_save_credentials if need_to_fill_config
97
123
  end
98
124
 
99
125
  private
100
126
 
101
- attr_reader :config
127
+ attr_reader :config, :scheduler
128
+
129
+ def prompt
130
+ Prompt.instance
131
+ end
102
132
 
103
133
  def check_command!
104
- command, = config.option_parser.permute!(ARGV)
134
+ params = {}
135
+ command, = config.option_parser.parse!(ARGV, into: params)
105
136
  unless command
106
137
  puts config.option_parser.help
107
138
  exit
108
139
  end
109
140
 
110
- return if command == "dump"
141
+ if command == "dump"
142
+ change_scheduler(params) if params.key?(:sync) || params.key?(:concurrency)
143
+ return
144
+ end
111
145
 
112
146
  raise "Unknown command: #{command}. Available commands: dump"
113
147
  end
148
+
149
+ def ask_save_credentials
150
+ answer = prompt.yes?("Would you like to save api creds so next time trellodon won't asking it?", default: true)
151
+ config.update_config_file! if answer
152
+ end
153
+
154
+ def change_scheduler(params)
155
+ if params.key?(:sync)
156
+ @scheduler = Schedulers::Inline.new
157
+ return
158
+ end
159
+
160
+ return unless params[:concurrency]
161
+
162
+ threads = params[:concurrency].to_i
163
+ raise "Value of concurrency option must be integer and greater than zero" unless threads.positive?
164
+
165
+ @scheduler = Schedulers::Concurrent.new(threads)
166
+ end
114
167
  end
115
168
  end
@@ -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, :url, keyword_init: true)
4
+ Attachment = Struct.new("Attachment", :file_name, :mime_type, :bytes, :date, :name, :is_upload, :headers, :url, keyword_init: true)
5
5
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trellodon
4
- Card = Struct.new("Card", :id, :short_id, :name, :desc, :list_id, :labels, :comments, :attachments, :last_activity_date, keyword_init: true)
4
+ Card = Struct.new("Card", :id, :short_id, :name, :desc, :list_id, :labels, :comments, :attachments, :checklists, :last_activity_date, keyword_init: true)
5
5
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trellodon
4
+ Checklist = Struct.new("Checklist", :id, :name, :items, keyword_init: true)
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trellodon
4
+ ChecklistItem = Struct.new("ChecklistItem", :id, :name, :state, keyword_init: true) do
5
+ def checked?
6
+ state == "complete"
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trellodon
4
- Comment = Struct.new("Comment", :text, :date, :creator_id, keyword_init: true)
4
+ Comment = Struct.new("Comment", :text, :date, :creator, keyword_init: true)
5
5
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trellodon
4
+ Member = Struct.new("Member", :id, :full_name, :username, keyword_init: true)
5
+ end
@@ -5,3 +5,6 @@ require "trellodon/entities/card"
5
5
  require "trellodon/entities/list"
6
6
  require "trellodon/entities/comment"
7
7
  require "trellodon/entities/attachment"
8
+ require "trellodon/entities/checklist"
9
+ require "trellodon/entities/checklist_item"
10
+ require "trellodon/entities/member"
@@ -9,13 +9,16 @@ module Trellodon
9
9
  class Markdown < Base
10
10
  attr_reader :output_dir
11
11
 
12
+ MD_FILENAME = "README.md"
13
+ ATTACHMENTS_DIRNAME = "attachments"
14
+
12
15
  def initialize(output_dir:, **opts)
13
16
  super(**opts)
14
17
  @output_dir = output_dir
15
18
  end
16
19
 
17
20
  def card_added(card)
18
- card_mdfile = File.join(card_path(card), "index.md")
21
+ card_mdfile = File.join(card_path(card), MD_FILENAME)
19
22
  raise "File #{card_mdfile} already exists" if File.exist?(card_mdfile)
20
23
 
21
24
  File.write(card_mdfile, format_card(card))
@@ -29,7 +32,7 @@ module Trellodon
29
32
  private
30
33
 
31
34
  def card_path(card)
32
- raise "Board is undefined" if @board.nil?
35
+ raise "Board is undefined" unless @board
33
36
  raise "List id is undefined" if card.list_id.nil? || card.list_id.empty?
34
37
  list = @board.get_list(card.list_id)
35
38
  raise "List #{card.list_id} is not found" if list.nil?
@@ -43,17 +46,16 @@ module Trellodon
43
46
  end
44
47
 
45
48
  def card_attachments_path(card)
46
- attachments_dir = File.join(card_path(card), "attachments")
49
+ attachments_dir = File.join(card_path(card), ATTACHMENTS_DIRNAME)
47
50
  FileUtils.mkdir_p(attachments_dir) unless File.directory?(attachments_dir)
48
51
  attachments_dir
49
52
  end
50
53
 
51
54
  def download_attachments(card)
52
- return # FIXME: 401 Unauthorized ???
53
- return if card.attachments.nil? || card.attachments.empty? # rubocop:disable Lint/UnreachableCode
55
+ return if card.attachments.nil? || card.attachments.empty?
54
56
  attachments_path = card_attachments_path(card)
55
- card.attachments.each do |att|
56
- IO.copy_stream(URI.parse(att.url).open, File.join(attachments_path, att.file_name))
57
+ card.attachments.select(&:is_upload).each do |att|
58
+ IO.copy_stream(URI.parse(att.url).open(att.headers), File.join(attachments_path, att.file_name))
57
59
  end
58
60
  end
59
61
 
@@ -61,7 +63,9 @@ module Trellodon
61
63
  create_card_header(card) +
62
64
  create_card_title(card) +
63
65
  create_card_description(card) +
64
- create_card_comments(card)
66
+ create_card_checklists(card) +
67
+ create_card_comments(card) +
68
+ create_card_attachments(card)
65
69
  end
66
70
 
67
71
  def create_card_header(card)
@@ -85,13 +89,35 @@ module Trellodon
85
89
  <<~EOS
86
90
 
87
91
  ## Description
92
+
88
93
  #{card.desc}
89
94
  EOS
90
95
  end
91
96
 
92
97
  def create_card_labels(card)
93
98
  return "" if card.labels.nil? || card.labels.empty?
94
- card.labels.map { |label| "\n - " + label }.reduce(:+)
99
+ card.labels.map { |label| "\n - " + label }.join
100
+ end
101
+
102
+ def create_card_checklists(card)
103
+ return "" if card.checklists.nil? || card.checklists.empty?
104
+ <<~EOS
105
+
106
+ ## Checklists
107
+ #{card.checklists.map { |checklist| create_card_checklist(checklist) }.join}
108
+ EOS
109
+ end
110
+
111
+ def create_card_checklist(checklist)
112
+ <<~EOS
113
+
114
+ ### #{checklist.name}
115
+ #{checklist.items&.map { |item| create_card_checklist_item(item) }&.join}
116
+ EOS
117
+ end
118
+
119
+ def create_card_checklist_item(item)
120
+ "\n- [#{item.checked? ? "x" : " "}] #{item.name}"
95
121
  end
96
122
 
97
123
  def create_card_comments(card)
@@ -99,17 +125,42 @@ module Trellodon
99
125
  <<~EOS
100
126
 
101
127
  ## Comments
102
- #{card.comments.map { |comment| create_card_comment(comment) }.reduce(:+)}
128
+ #{card.comments.map { |comment| create_card_comment(comment) }.join}
103
129
  EOS
104
130
  end
105
131
 
106
132
  def create_card_comment(comment)
107
133
  <<~EOS
108
134
 
109
- **#{comment.creator_id} @ #{comment.date}**
135
+ **#{comment.creator.full_name} (@#{comment.creator.username}) at #{comment.date}**
110
136
  #{comment.text}
111
137
  EOS
112
138
  end
139
+
140
+ def create_card_attachments(card)
141
+ return "" if card.attachments.nil? || card.attachments.empty?
142
+ <<~EOS
143
+
144
+ ## Attachments
145
+ #{card.attachments.map { |attachment| create_card_attachment(attachment) }.join}
146
+ EOS
147
+ end
148
+
149
+ def create_card_attachment(attachment)
150
+ <<~EOS
151
+
152
+ ### #{attachment.name}
153
+
154
+ **date**: #{attachment.date}
155
+ **url**: #{attachment.url}
156
+ **local copy**: #{create_attachment_local_link(attachment)}
157
+ EOS
158
+ end
159
+
160
+ def create_attachment_local_link(attachment)
161
+ return "-" unless attachment.is_upload
162
+ "[#{attachment.file_name}](#{File.join("./", ATTACHMENTS_DIRNAME, attachment.file_name)})"
163
+ end
113
164
  end
114
165
  end
115
166
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trellodon/schedulers/base"
4
+ require "concurrent"
5
+
6
+ module Trellodon
7
+ module Schedulers
8
+ class Concurrent < Base
9
+ using RubyNext
10
+
11
+ DEFAULT_MAX_THREADS = 50
12
+
13
+ attr_reader :max_threads
14
+
15
+ def initialize(threads_count = DEFAULT_MAX_THREADS)
16
+ @max_threads = threads_count
17
+ end
18
+
19
+ def post(&block)
20
+ ::Concurrent::Future.execute(executor:, &block)
21
+ end
22
+
23
+ private
24
+
25
+ def executor
26
+ ::Concurrent::FixedThreadPool.new(max_threads)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,3 +3,4 @@
3
3
  require "trellodon/schedulers/base"
4
4
  require "trellodon/schedulers/inline"
5
5
  require "trellodon/schedulers/threaded"
6
+ require "trellodon/schedulers/concurrent"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trello"
4
+ require "logger"
5
+
6
+ module Trello
7
+ # Nullify Trello client API logger
8
+ def self.logger
9
+ Logger.new(nil)
10
+ end
11
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trellodon
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/trellodon.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ruby-next"
4
+
5
+ require "ruby-next/language/setup"
6
+ RubyNext::Language.setup_gem_load_path(transpile: true)
7
+
3
8
  require "trellodon/version"
4
9
  require "trellodon/config"
5
10
  require "trellodon/api_executor"
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trellodon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fargelus
8
8
  - rinasergeeva
9
+ - palkan
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2022-04-08 00:00:00.000000000 Z
13
+ date: 2022-04-12 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: anyway_config
@@ -53,12 +54,27 @@ dependencies:
53
54
  - - ">="
54
55
  - !ruby/object:Gem::Version
55
56
  version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: concurrent-ruby
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
56
71
  description: "\n The main purpose of Trellodon is to make it possible to backup
57
72
  Trello boards to file system in a human-readable form (e.g., Markdown files).\n
58
73
  \ "
59
74
  email:
60
75
  - ddraudred@gmail.com
61
76
  - catherine.sergeeva@gmail.com
77
+ - dementiev.vm@gmail.com
62
78
  executables:
63
79
  - trellodon
64
80
  extensions: []
@@ -70,6 +86,8 @@ files:
70
86
  - bin/console
71
87
  - bin/setup
72
88
  - bin/trellodon
89
+ - lib/.rbnext/2.7/trellodon/api_executor.rb
90
+ - lib/.rbnext/3.1/trellodon/schedulers/concurrent.rb
73
91
  - lib/trellodon.rb
74
92
  - lib/trellodon/api_client.rb
75
93
  - lib/trellodon/api_executor.rb
@@ -79,15 +97,20 @@ files:
79
97
  - lib/trellodon/entities/attachment.rb
80
98
  - lib/trellodon/entities/board.rb
81
99
  - lib/trellodon/entities/card.rb
100
+ - lib/trellodon/entities/checklist.rb
101
+ - lib/trellodon/entities/checklist_item.rb
82
102
  - lib/trellodon/entities/comment.rb
83
103
  - lib/trellodon/entities/list.rb
104
+ - lib/trellodon/entities/member.rb
84
105
  - lib/trellodon/formatters.rb
85
106
  - lib/trellodon/formatters/base.rb
86
107
  - lib/trellodon/formatters/markdown.rb
87
108
  - lib/trellodon/schedulers.rb
88
109
  - lib/trellodon/schedulers/base.rb
110
+ - lib/trellodon/schedulers/concurrent.rb
89
111
  - lib/trellodon/schedulers/inline.rb
90
112
  - lib/trellodon/schedulers/threaded.rb
113
+ - lib/trellodon/trello.rb
91
114
  - lib/trellodon/version.rb
92
115
  - sig/trellodon.rbs
93
116
  homepage: https://github.com/evilmartians/trellodon