trellodon 0.1.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 +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: []
|