trellodon 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +3 -0
- data/bin/console +8 -0
- data/bin/setup +6 -0
- data/bin/trellodon +6 -0
- data/lib/trellodon/api_client.rb +104 -0
- data/lib/trellodon/api_executor.rb +68 -0
- data/lib/trellodon/cli.rb +115 -0
- data/lib/trellodon/config.rb +16 -0
- data/lib/trellodon/entities/attachment.rb +5 -0
- data/lib/trellodon/entities/board.rb +10 -0
- data/lib/trellodon/entities/card.rb +5 -0
- data/lib/trellodon/entities/comment.rb +5 -0
- data/lib/trellodon/entities/list.rb +5 -0
- data/lib/trellodon/entities.rb +7 -0
- data/lib/trellodon/formatters/base.rb +25 -0
- data/lib/trellodon/formatters/markdown.rb +115 -0
- data/lib/trellodon/formatters.rb +4 -0
- data/lib/trellodon/schedulers/base.rb +11 -0
- data/lib/trellodon/schedulers/inline.rb +15 -0
- data/lib/trellodon/schedulers/threaded.rb +13 -0
- data/lib/trellodon/schedulers.rb +5 -0
- data/lib/trellodon/version.rb +5 -0
- data/lib/trellodon.rb +9 -0
- data/sig/trellodon.rbs +4 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bf1b8715b6cbad2c1bd3ab4fce72198a442380cf4f2d7bb39c55928ef72b032f
|
4
|
+
data.tar.gz: 2caa2a3a24fe463cc1cb730a3aad68928323ec0fea04fea8e3a0f10ebf051ea7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b50b3279f7a9e787b2f2e718109902a9cda88729f7d7b330aa5db9db944f913af33d6f26fab7b1f6c4d2866a1241d62d99c4dbe989ea468499bcaaa4908a153f
|
7
|
+
data.tar.gz: 52d86ebd0b43ad0b064facbfb66332623422b2c688eff732188b7ea2ce8baba2842a331a941e1aafb410f114c31aef3ec80f7588b7f6a07808070019f9eb12ba
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2022 Evil Martians
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/bin/trellodon
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "trello"
|
4
|
+
|
5
|
+
require "trellodon/entities"
|
6
|
+
|
7
|
+
module Trellodon
|
8
|
+
class APIClient
|
9
|
+
def initialize(api_key:, api_token:, logger: Config.logger)
|
10
|
+
@logger = logger
|
11
|
+
@client = Trello::Client.new(
|
12
|
+
developer_public_key: api_key,
|
13
|
+
member_token: api_token
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch_board(board_id)
|
18
|
+
retrying do
|
19
|
+
board = @client.find(:boards, board_id)
|
20
|
+
|
21
|
+
Board.new(
|
22
|
+
id: board.id,
|
23
|
+
short_id: board.short_url.chars.last(8).join,
|
24
|
+
name: board.name,
|
25
|
+
lists: lists_from(board),
|
26
|
+
card_ids: @client.find_many(Trello::Card, "/boards/#{board_id}/cards", fields: "id").map(&:id),
|
27
|
+
last_activity_date: board.last_activity_date
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch_card(card_id)
|
33
|
+
retrying do
|
34
|
+
card = @client.find(:cards, card_id)
|
35
|
+
|
36
|
+
Card.new(
|
37
|
+
id: card.id,
|
38
|
+
short_id: card.short_url.chars.last(8).join,
|
39
|
+
name: card.name,
|
40
|
+
desc: card.desc,
|
41
|
+
labels: card.labels.map(&:name),
|
42
|
+
list_id: card.list_id,
|
43
|
+
comments: comments_from(card),
|
44
|
+
attachments: attachments_from(card),
|
45
|
+
last_activity_date: card.last_activity_date
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :logger
|
53
|
+
|
54
|
+
def retrying(&block)
|
55
|
+
attempt = 0
|
56
|
+
begin
|
57
|
+
block.call
|
58
|
+
rescue Trello::Error => err
|
59
|
+
raise unless err.status_code == 429
|
60
|
+
|
61
|
+
attempt += 1
|
62
|
+
|
63
|
+
cooldown = 2**attempt + rand(2**attempt) - 2**(attempt - 1)
|
64
|
+
|
65
|
+
logger.warn "API limit exceeded, cool down for #{cooldown}s"
|
66
|
+
|
67
|
+
sleep cooldown
|
68
|
+
|
69
|
+
retry
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def comments_from(card)
|
74
|
+
card.comments.map do |comment|
|
75
|
+
Comment.new(
|
76
|
+
text: comment.data["text"],
|
77
|
+
date: comment.date,
|
78
|
+
creator_id: comment.creator_id
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def attachments_from(card)
|
84
|
+
card.attachments.map do |attach|
|
85
|
+
Attachment.new(
|
86
|
+
file_name: attach.file_name,
|
87
|
+
mime_type: attach.mime_type,
|
88
|
+
bytes: attach.bytes,
|
89
|
+
url: attach.url
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def lists_from(board)
|
95
|
+
board.lists.map do |list|
|
96
|
+
List.new(
|
97
|
+
id: list.id,
|
98
|
+
name: list.name,
|
99
|
+
short_id: board.short_url.chars.last(8).join
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -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 { 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 { 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,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "trellodon"
|
4
|
+
|
5
|
+
require "tty-prompt"
|
6
|
+
|
7
|
+
module Trellodon
|
8
|
+
class CLI
|
9
|
+
class Prompt
|
10
|
+
def initialize
|
11
|
+
@prompt = TTY::Prompt.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def ask_api_key
|
15
|
+
@prompt.mask(
|
16
|
+
"Provide your Developer API Key (see https://trello.com/app-key):"
|
17
|
+
) { |q| q.required true }
|
18
|
+
end
|
19
|
+
|
20
|
+
def ask_api_token
|
21
|
+
@prompt.mask(
|
22
|
+
"Provide your API token:"
|
23
|
+
) { |q| q.required true }
|
24
|
+
end
|
25
|
+
|
26
|
+
def ask_board
|
27
|
+
@prompt.ask("Which board would you like to dump? (URL or ID)") do |q|
|
28
|
+
q.required true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def ask_folder
|
33
|
+
@prompt.ask("Destination folder?", default: "./")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Config < Trellodon::Config
|
38
|
+
attr_config :board, :out
|
39
|
+
|
40
|
+
ignore_options :api_key, :api_token
|
41
|
+
|
42
|
+
describe_options(
|
43
|
+
board: "Board URL or ID",
|
44
|
+
out: "Destination folder path"
|
45
|
+
)
|
46
|
+
|
47
|
+
extend_options do |opts, _|
|
48
|
+
opts.banner = "Usage: trellodon dump\n"\
|
49
|
+
"Options:\n"
|
50
|
+
|
51
|
+
opts.on_tail("-v", "--version", "Print current version") do
|
52
|
+
puts Trellodon::VERSION
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on_tail("-h", "--help", "Print help") do
|
57
|
+
puts opts
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private def prompt
|
63
|
+
@prompt ||= Prompt.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def api_key
|
67
|
+
super || (self.api_key = prompt.ask_api_key)
|
68
|
+
end
|
69
|
+
|
70
|
+
def api_token
|
71
|
+
super || (self.api_token = prompt.ask_api_token)
|
72
|
+
end
|
73
|
+
|
74
|
+
def board
|
75
|
+
super || (self.board = prompt.ask_board)
|
76
|
+
end
|
77
|
+
|
78
|
+
def out
|
79
|
+
super || (self.out = prompt.ask_folder)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize
|
84
|
+
@config = Config.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def run
|
88
|
+
check_command!
|
89
|
+
|
90
|
+
executor = Trellodon::APIExecutor.new(
|
91
|
+
api_key: config.api_key,
|
92
|
+
api_token: config.api_token,
|
93
|
+
formatter: Trellodon::Formatters::Markdown.new(output_dir: config.out),
|
94
|
+
scheduler: Schedulers::Threaded.new
|
95
|
+
)
|
96
|
+
executor.download(config.board)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
attr_reader :config
|
102
|
+
|
103
|
+
def check_command!
|
104
|
+
command, = config.option_parser.permute!(ARGV)
|
105
|
+
unless command
|
106
|
+
puts config.option_parser.help
|
107
|
+
exit
|
108
|
+
end
|
109
|
+
|
110
|
+
return if command == "dump"
|
111
|
+
|
112
|
+
raise "Unknown command: #{command}. Available commands: dump"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "anyway_config"
|
4
|
+
require "logger"
|
5
|
+
|
6
|
+
module Trellodon
|
7
|
+
class Config < Anyway::Config
|
8
|
+
config_name :trellodon
|
9
|
+
|
10
|
+
attr_config :api_token, :api_key
|
11
|
+
|
12
|
+
def self.logger
|
13
|
+
Logger.new($stdout)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trellodon
|
4
|
+
Board = Struct.new("Board", :id, :short_id, :name, :card_ids, :lists, :last_activity_date, keyword_init: true) do
|
5
|
+
def get_list(list_id)
|
6
|
+
return nil if lists.nil?
|
7
|
+
lists.find { |list| list.id == list_id }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trellodon
|
4
|
+
module Formatters
|
5
|
+
class Base
|
6
|
+
attr_reader :board, :logger
|
7
|
+
|
8
|
+
def initialize(logger: Config.logger)
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def board_added(board)
|
13
|
+
@board = board
|
14
|
+
end
|
15
|
+
|
16
|
+
def card_added(card)
|
17
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
18
|
+
end
|
19
|
+
|
20
|
+
def finish
|
21
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open-uri"
|
4
|
+
require "fileutils"
|
5
|
+
require "trellodon/formatters/base"
|
6
|
+
|
7
|
+
module Trellodon
|
8
|
+
module Formatters
|
9
|
+
class Markdown < Base
|
10
|
+
attr_reader :output_dir
|
11
|
+
|
12
|
+
def initialize(output_dir:, **opts)
|
13
|
+
super(**opts)
|
14
|
+
@output_dir = output_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
def card_added(card)
|
18
|
+
card_mdfile = File.join(card_path(card), "index.md")
|
19
|
+
raise "File #{card_mdfile} already exists" if File.exist?(card_mdfile)
|
20
|
+
|
21
|
+
File.write(card_mdfile, format_card(card))
|
22
|
+
download_attachments(card)
|
23
|
+
end
|
24
|
+
|
25
|
+
def finish
|
26
|
+
logger.info "Markdown dump is here: #{@output_dir}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def card_path(card)
|
32
|
+
raise "Board is undefined" if @board.nil?
|
33
|
+
raise "List id is undefined" if card.list_id.nil? || card.list_id.empty?
|
34
|
+
list = @board.get_list(card.list_id)
|
35
|
+
raise "List #{card.list_id} is not found" if list.nil?
|
36
|
+
|
37
|
+
card_dir = File.join(@output_dir,
|
38
|
+
"#{board.name} [#{board.short_id}]",
|
39
|
+
"#{list.name} [#{list.short_id}]",
|
40
|
+
"#{card.name} [#{card.short_id}]")
|
41
|
+
FileUtils.mkdir_p(card_dir) unless File.directory?(card_dir)
|
42
|
+
card_dir
|
43
|
+
end
|
44
|
+
|
45
|
+
def card_attachments_path(card)
|
46
|
+
attachments_dir = File.join(card_path(card), "attachments")
|
47
|
+
FileUtils.mkdir_p(attachments_dir) unless File.directory?(attachments_dir)
|
48
|
+
attachments_dir
|
49
|
+
end
|
50
|
+
|
51
|
+
def download_attachments(card)
|
52
|
+
return # FIXME: 401 Unauthorized ???
|
53
|
+
return if card.attachments.nil? || card.attachments.empty? # rubocop:disable Lint/UnreachableCode
|
54
|
+
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
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def format_card(card)
|
61
|
+
create_card_header(card) +
|
62
|
+
create_card_title(card) +
|
63
|
+
create_card_description(card) +
|
64
|
+
create_card_comments(card)
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_card_header(card)
|
68
|
+
<<~EOS
|
69
|
+
---
|
70
|
+
title: #{card.name}
|
71
|
+
last_updated_at: #{card.last_activity_date}
|
72
|
+
labels: #{create_card_labels(card)}
|
73
|
+
---
|
74
|
+
EOS
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_card_title(card)
|
78
|
+
<<~EOS
|
79
|
+
|
80
|
+
# #{card.name}
|
81
|
+
EOS
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_card_description(card)
|
85
|
+
<<~EOS
|
86
|
+
|
87
|
+
## Description
|
88
|
+
#{card.desc}
|
89
|
+
EOS
|
90
|
+
end
|
91
|
+
|
92
|
+
def create_card_labels(card)
|
93
|
+
return "" if card.labels.nil? || card.labels.empty?
|
94
|
+
card.labels.map { |label| "\n - " + label }.reduce(:+)
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_card_comments(card)
|
98
|
+
return "" if card.comments.nil? || card.comments.empty?
|
99
|
+
<<~EOS
|
100
|
+
|
101
|
+
## Comments
|
102
|
+
#{card.comments.map { |comment| create_card_comment(comment) }.reduce(:+)}
|
103
|
+
EOS
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_card_comment(comment)
|
107
|
+
<<~EOS
|
108
|
+
|
109
|
+
**#{comment.creator_id} @ #{comment.date}**
|
110
|
+
#{comment.text}
|
111
|
+
EOS
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/trellodon.rb
ADDED
data/sig/trellodon.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trellodon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- fargelus
|
8
|
+
- rinasergeeva
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2022-04-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: anyway_config
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: ruby-trello
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: tty-prompt
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: "\n The main purpose of Trellodon is to make it possible to backup
|
57
|
+
Trello boards to file system in a human-readable form (e.g., Markdown files).\n
|
58
|
+
\ "
|
59
|
+
email:
|
60
|
+
- ddraudred@gmail.com
|
61
|
+
- catherine.sergeeva@gmail.com
|
62
|
+
executables:
|
63
|
+
- trellodon
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- CHANGELOG.md
|
68
|
+
- LICENSE
|
69
|
+
- README.md
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- bin/trellodon
|
73
|
+
- lib/trellodon.rb
|
74
|
+
- lib/trellodon/api_client.rb
|
75
|
+
- lib/trellodon/api_executor.rb
|
76
|
+
- lib/trellodon/cli.rb
|
77
|
+
- lib/trellodon/config.rb
|
78
|
+
- lib/trellodon/entities.rb
|
79
|
+
- lib/trellodon/entities/attachment.rb
|
80
|
+
- lib/trellodon/entities/board.rb
|
81
|
+
- lib/trellodon/entities/card.rb
|
82
|
+
- lib/trellodon/entities/comment.rb
|
83
|
+
- lib/trellodon/entities/list.rb
|
84
|
+
- lib/trellodon/formatters.rb
|
85
|
+
- lib/trellodon/formatters/base.rb
|
86
|
+
- lib/trellodon/formatters/markdown.rb
|
87
|
+
- lib/trellodon/schedulers.rb
|
88
|
+
- lib/trellodon/schedulers/base.rb
|
89
|
+
- lib/trellodon/schedulers/inline.rb
|
90
|
+
- lib/trellodon/schedulers/threaded.rb
|
91
|
+
- lib/trellodon/version.rb
|
92
|
+
- sig/trellodon.rbs
|
93
|
+
homepage: https://github.com/evilmartians/trellodon
|
94
|
+
licenses: []
|
95
|
+
metadata:
|
96
|
+
homepage_uri: https://github.com/evilmartians/trellodon
|
97
|
+
source_code_uri: https://github.com/evilmartians/trellodon
|
98
|
+
changelog_uri: https://github.com/evilmartians/trellodon/CHANGELOG.md
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: 2.6.0
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubygems_version: 3.3.7
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: 'Trellodon: dump Trello boards to your file system'
|
118
|
+
test_files: []
|