trellodon 0.3.0 → 0.4.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: a61911e92509a77f13aa90f9f56c14d038dbe443b4e1063d28f66b6745b04432
4
- data.tar.gz: '079609a773107e786a7d795fe7d23564ded2aa66e8d587a6fd22b4e288e15478'
3
+ metadata.gz: 7210dc1a4a18474871607069363ab1d1acdfeb283588ef52403a38699e24bebb
4
+ data.tar.gz: 465aa0681c0c9e2a3744a94ad06b17e8eaf35bfdc90204e36d6aa35359765134
5
5
  SHA512:
6
- metadata.gz: 617119b48c1f2385c87d57ffd2683933097ed1aae11a51577f65e16012ab1f58b65187f57884123ed04ba522797925826e3cf202e7d4dad9d1fd63a3d736bcef
7
- data.tar.gz: 30c1f1bcb2df2c69a3c8d0cebc93a597f16d2207d33bef979670134d306bd0173cc97948f38f739cf1d6b5eef0a7a602112fb0de40021d078d0188280ca4d039
6
+ metadata.gz: 3895577372463c4d917c0c91f557789d298d5183263b72475bf213a44bb4d17deefac8e1ce5eb13b65b93540ae8442dfb43a4123d255abbe9a403700307353dc
7
+ data.tar.gz: d7e099a0bdb52d541152522f317e3b66ed89aa0128639be8731bb0c99c677e26b815bf1d6d1b61754bfa72998df7b5649967b8a28c23c05d5d561abd775b841c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [master]
4
4
 
5
+ ## [0.4.0] - 2022-04-27 🗂️
6
+
7
+ - Resolve folder name collisions in markdown formatter ([@rinasergeeva][])
8
+ - Add user info to attachments and checklists ([@rinasergeeva][])
9
+ - Verbose option in cli. ([@fargelus][])
10
+
5
11
  ## [0.3.0] - 2022-04-12 🚀🚀🚀
6
12
 
7
13
  - Handle missing members. ([@palkan][])
@@ -29,3 +35,4 @@
29
35
 
30
36
  [@fargelus]: https://github.com/fargelus
31
37
  [@rinasergeeva]: https://github.com/rinasergeeva
38
+ [@palkan]: https://github.com/palkan
data/README.md CHANGED
@@ -9,6 +9,7 @@ A Ruby tool to export a Trello board and convert it into a set of folders and ma
9
9
  * Description
10
10
  * Labels
11
11
  * Comments
12
+ * Checklists
12
13
  * Attachments
13
14
 
14
15
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
@@ -30,6 +31,14 @@ To generate them, go to the [trello.com/app-key](https://trello.com/app-key) pag
30
31
 
31
32
  ## Usage
32
33
 
34
+ Copy the board URL and run the following command:
35
+
36
+ ```sh
37
+ trellodon dump --board=<url>
38
+ ```
39
+
40
+ Other supported options:
41
+
33
42
  ```sh
34
43
  Usage: trellodon dump [options]
35
44
  Options:
@@ -37,7 +46,8 @@ Options:
37
46
  --out VALUE Destination folder path
38
47
  --concurrency VALUE Amount of processing threads (default: 4). Set to 0 to execute API requests in-line
39
48
  --clear-auth Remove saved api credentials
40
- -v, --version Print current version
49
+ -v, --verbose Print all processes output
50
+ -V, --version Print current version
41
51
  -h, --help Print help
42
52
  ```
43
53
 
@@ -90,3 +100,9 @@ Some card description
90
100
  **date**: 2022-03-16
91
101
  **url**: https://trello.com/1/cards/#{card_id}/attachments/#{attachment.id}/download/image.png
92
102
  ```
103
+
104
+ ### Trello API Limits 🚧
105
+
106
+ Trello API limits the total amount of requests for each API Key & token.
107
+ By default, 300 requests are available per 10 seconds for each API key and no more than 100 requests per 10 second interval for each token.
108
+ If a request exceeds the limit, Trello will return a 429 error. Additional information see [here](https://help.trello.com/article/838-api-rate-limits).
@@ -5,12 +5,12 @@ module Trellodon
5
5
  BOARD_ID_REGEX = /\/b\/([^\/]+)\//
6
6
  private_constant :BOARD_ID_REGEX
7
7
 
8
- def initialize(api_key:, api_token:, scheduler:, formatter:, logger: Config.logger)
9
- @api_key = api_key
10
- @api_token = api_token
11
- @formatter = formatter
12
- @logger = logger
13
- @scheduler = scheduler
8
+ def initialize(opts)
9
+ @api_key = opts.fetch(:api_key)
10
+ @api_token = opts.fetch(:api_token)
11
+ @formatter = opts.fetch(:formatter)
12
+ @logger = opts.fetch(:logger) { Config.logger }
13
+ @scheduler = opts.fetch(:scheduler)
14
14
  check_credentials!
15
15
 
16
16
  @client = Client.new(api_key: @api_key, api_token: @api_token)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
3
4
  require "trello"
4
5
  require "trellodon/entities"
5
6
 
@@ -17,7 +18,7 @@ module Trellodon
17
18
  developer_public_key: api_key,
18
19
  member_token: api_token
19
20
  )
20
- @members = {}
21
+ @members = Concurrent::Map.new
21
22
  end
22
23
 
23
24
  def fetch_board(board_id)
@@ -26,7 +27,6 @@ module Trellodon
26
27
 
27
28
  Board.new(
28
29
  id: board.id,
29
- short_id: board.short_url.chars.last(8).join,
30
30
  name: board.name,
31
31
  lists: lists_from(board),
32
32
  card_ids: @client.find_many(Trello::Card, "/boards/#{board_id}/cards", fields: "id").map(&:id),
@@ -41,7 +41,6 @@ module Trellodon
41
41
 
42
42
  Card.new(
43
43
  id: card.id,
44
- short_id: card.short_url.chars.last(8).join,
45
44
  name: card.name,
46
45
  desc: card.desc,
47
46
  labels: card.labels.map(&:name),
@@ -77,7 +76,7 @@ module Trellodon
77
76
  Comment.new(
78
77
  text: comment.data["text"],
79
78
  date: comment.date,
80
- creator: member_from(comment)
79
+ creator: member_from(comment.creator_id)
81
80
  )
82
81
  end
83
82
  end
@@ -85,12 +84,13 @@ module Trellodon
85
84
  def attachments_from(card)
86
85
  card.attachments.map do |attach|
87
86
  Attachment.new(
88
- file_name: attach.file_name,
87
+ file_name: attach.is_upload ? URI.decode_www_form_component(attach.file_name) : attach.file_name,
89
88
  mime_type: attach.mime_type,
90
89
  bytes: attach.bytes,
91
90
  date: attach.date,
92
91
  name: attach.name,
93
92
  is_upload: attach.is_upload,
93
+ added_by: member_from(attach.member_id),
94
94
  headers: attach.is_upload ? {"Authorization" => "OAuth oauth_consumer_key=\"#{@client.configuration.developer_public_key}\", oauth_token=\"#{@client.configuration.member_token}\""} : {},
95
95
  url: attach.url
96
96
  )
@@ -102,7 +102,7 @@ module Trellodon
102
102
  List.new(
103
103
  id: list.id,
104
104
  name: list.name,
105
- short_id: board.short_url.chars.last(8).join
105
+ board_id: list.board_id
106
106
  )
107
107
  end
108
108
  end
@@ -122,28 +122,31 @@ module Trellodon
122
122
  ChecklistItem.new(
123
123
  id: item.id,
124
124
  name: item.name,
125
- state: item.state
125
+ state: item.state,
126
+ member: member_from(item.member_id),
127
+ due_date: item.due_date
126
128
  )
127
129
  end
128
130
  end
129
131
 
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
- )
132
+ def member_from(id)
133
+ return nil unless id
134
+
135
+ @members.compute_if_absent(id) do
136
+ member =
137
+ begin
138
+ @client.find(:members, id)
139
+ rescue Trello::Error => err
140
+ raise unless err.status_code == 404
141
+
142
+ DeletedMember.new(id)
143
+ end
144
+ Member.new(
145
+ id: member.id,
146
+ full_name: member.full_name,
147
+ username: member.username
148
+ )
149
+ end
147
150
  end
148
151
  end
149
152
  end
data/lib/trellodon/cli.rb CHANGED
@@ -50,8 +50,9 @@ module Trellodon
50
50
  opts.on("--clear-auth", "Remove saved api credentials") do
51
51
  config.reload(api_token: nil, api_key: nil)
52
52
  end
53
+ opts.on("-v", "--verbose", "Print all processes output")
53
54
 
54
- opts.on_tail("-v", "--version", "Print current version") do
55
+ opts.on_tail("-V", "--version", "Print current version") do
55
56
  puts Trellodon::VERSION
56
57
  exit
57
58
  end
@@ -103,28 +104,25 @@ module Trellodon
103
104
  def initialize
104
105
  @config = Config.new
105
106
  @scheduler = Schedulers::Inline.new
107
+ @logger = Config.logger
106
108
  end
107
109
 
108
110
  def run
109
111
  check_command!
110
112
 
111
113
  need_to_fill_config = config.to_h[:api_key].nil?
112
-
113
- executor = Trellodon::Executor.new(
114
- api_key: config.api_key,
115
- api_token: config.api_token,
116
- formatter: Trellodon::Formatters::Markdown.new(output_dir: config.out),
117
- scheduler: scheduler
118
- )
114
+ executor = Trellodon::Executor.new(executor_options)
119
115
 
120
116
  ask_save_credentials if need_to_fill_config
121
117
 
122
118
  executor.download(config.board)
123
119
  end
124
120
 
121
+ attr_reader :scheduler, :logger
122
+
125
123
  private
126
124
 
127
- attr_reader :config, :scheduler
125
+ attr_reader :config
128
126
 
129
127
  def prompt
130
128
  Prompt.instance
@@ -139,13 +137,24 @@ module Trellodon
139
137
  end
140
138
 
141
139
  if command == "dump"
142
- change_scheduler(params) if params.key?(:sync) || params.key?(:concurrency)
140
+ change_scheduler(params)
141
+ @logger.level = 0 if params.key?(:verbose)
143
142
  return
144
143
  end
145
144
 
146
145
  raise "Unknown command: #{command}. Available commands: dump"
147
146
  end
148
147
 
148
+ def executor_options
149
+ {
150
+ api_key: config.api_key,
151
+ api_token: config.api_token,
152
+ formatter: Trellodon::Formatters::Markdown.new(output_dir: config.out, logger: logger),
153
+ scheduler: scheduler,
154
+ logger: logger
155
+ }
156
+ end
157
+
149
158
  def ask_save_credentials
150
159
  answer = prompt.yes?("Would you like to save api creds so next time trellodon won't asking it?", default: true)
151
160
  config.update_config_file! if answer
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
3
4
  require "trello"
4
5
  require "trellodon/entities"
5
6
 
@@ -17,7 +18,7 @@ module Trellodon
17
18
  developer_public_key: api_key,
18
19
  member_token: api_token
19
20
  )
20
- @members = {}
21
+ @members = Concurrent::Map.new
21
22
  end
22
23
 
23
24
  def fetch_board(board_id)
@@ -26,7 +27,6 @@ module Trellodon
26
27
 
27
28
  Board.new(
28
29
  id: board.id,
29
- short_id: board.short_url.chars.last(8).join,
30
30
  name: board.name,
31
31
  lists: lists_from(board),
32
32
  card_ids: @client.find_many(Trello::Card, "/boards/#{board_id}/cards", fields: "id").map(&:id),
@@ -41,7 +41,6 @@ module Trellodon
41
41
 
42
42
  Card.new(
43
43
  id: card.id,
44
- short_id: card.short_url.chars.last(8).join,
45
44
  name: card.name,
46
45
  desc: card.desc,
47
46
  labels: card.labels.map(&:name),
@@ -77,7 +76,7 @@ module Trellodon
77
76
  Comment.new(
78
77
  text: comment.data["text"],
79
78
  date: comment.date,
80
- creator: member_from(comment)
79
+ creator: member_from(comment.creator_id)
81
80
  )
82
81
  end
83
82
  end
@@ -85,12 +84,13 @@ module Trellodon
85
84
  def attachments_from(card)
86
85
  card.attachments.map do |attach|
87
86
  Attachment.new(
88
- file_name: attach.file_name,
87
+ file_name: attach.is_upload ? URI.decode_www_form_component(attach.file_name) : attach.file_name,
89
88
  mime_type: attach.mime_type,
90
89
  bytes: attach.bytes,
91
90
  date: attach.date,
92
91
  name: attach.name,
93
92
  is_upload: attach.is_upload,
93
+ added_by: member_from(attach.member_id),
94
94
  headers: attach.is_upload ? {"Authorization" => "OAuth oauth_consumer_key=\"#{@client.configuration.developer_public_key}\", oauth_token=\"#{@client.configuration.member_token}\""} : {},
95
95
  url: attach.url
96
96
  )
@@ -102,7 +102,7 @@ module Trellodon
102
102
  List.new(
103
103
  id: list.id,
104
104
  name: list.name,
105
- short_id: board.short_url.chars.last(8).join
105
+ board_id: list.board_id
106
106
  )
107
107
  end
108
108
  end
@@ -122,28 +122,31 @@ module Trellodon
122
122
  ChecklistItem.new(
123
123
  id: item.id,
124
124
  name: item.name,
125
- state: item.state
125
+ state: item.state,
126
+ member: member_from(item.member_id),
127
+ due_date: item.due_date
126
128
  )
127
129
  end
128
130
  end
129
131
 
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
- )
132
+ def member_from(id)
133
+ return nil unless id
134
+
135
+ @members.compute_if_absent(id) do
136
+ member =
137
+ begin
138
+ @client.find(:members, id)
139
+ rescue Trello::Error => err
140
+ raise unless err.status_code == 404
141
+
142
+ DeletedMember.new(id)
143
+ end
144
+ Member.new(
145
+ id: member.id,
146
+ full_name: member.full_name,
147
+ username: member.username
148
+ )
149
+ end
147
150
  end
148
151
  end
149
152
  end
@@ -10,7 +10,7 @@ module Trellodon
10
10
  attr_config :api_token, :api_key
11
11
 
12
12
  def self.logger
13
- Logger.new($stdout)
13
+ Logger.new($stdout, level: Logger::INFO)
14
14
  end
15
15
  end
16
16
  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, :date, :name, :is_upload, :headers, :url, keyword_init: true)
4
+ Attachment = Struct.new("Attachment", :file_name, :mime_type, :bytes, :date, :name, :is_upload, :headers, :url, :added_by, keyword_init: true)
5
5
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trellodon
4
- Board = Struct.new("Board", :id, :short_id, :name, :card_ids, :lists, :last_activity_date, keyword_init: true) do
4
+ Board = Struct.new("Board", :id, :name, :card_ids, :lists, :last_activity_date, keyword_init: true) do
5
5
  def get_list(list_id)
6
6
  return nil if lists.nil?
7
7
  lists.find { |list| list.id == list_id }
@@ -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, :checklists, :last_activity_date, keyword_init: true)
4
+ Card = Struct.new("Card", :id, :name, :desc, :list_id, :labels, :comments, :attachments, :checklists, :last_activity_date, keyword_init: true)
5
5
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trellodon
4
+ CREATE_CARD_TYPE = "createCard"
5
+ CardEvent = Struct.new(:type, :data, :card, :list, :board, :date, keyword_init: true)
6
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trellodon
4
- ChecklistItem = Struct.new("ChecklistItem", :id, :name, :state, keyword_init: true) do
4
+ ChecklistItem = Struct.new("ChecklistItem", :id, :name, :state, :member, :due_date, keyword_init: true) do
5
5
  def checked?
6
6
  state == "complete"
7
7
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trellodon
4
- List = Struct.new("List", :id, :short_id, :name, keyword_init: true)
4
+ List = Struct.new("List", :id, :board_id, :name, keyword_init: true)
5
5
  end
@@ -5,12 +5,12 @@ module Trellodon
5
5
  BOARD_ID_REGEX = /\/b\/([^\/]+)\//
6
6
  private_constant :BOARD_ID_REGEX
7
7
 
8
- def initialize(api_key:, api_token:, scheduler:, formatter:, logger: Config.logger)
9
- @api_key = api_key
10
- @api_token = api_token
11
- @formatter = formatter
12
- @logger = logger
13
- @scheduler = scheduler
8
+ def initialize(opts)
9
+ @api_key = opts.fetch(:api_key)
10
+ @api_token = opts.fetch(:api_token)
11
+ @formatter = opts.fetch(:formatter)
12
+ @logger = opts.fetch(:logger) { Config.logger }
13
+ @scheduler = opts.fetch(:scheduler)
14
14
  check_credentials!
15
15
 
16
16
  @client = Client.new(api_key: @api_key, api_token: @api_token)
@@ -11,10 +11,16 @@ module Trellodon
11
11
 
12
12
  MD_FILENAME = "README.md"
13
13
  ATTACHMENTS_DIRNAME = "attachments"
14
+ DIRNAME_MAXLENGTH = 50
14
15
 
15
16
  def initialize(output_dir:, **opts)
16
17
  super(**opts)
17
18
  @output_dir = output_dir
19
+ @board_dirname = Concurrent::Map.new
20
+ @board_subdirs = Concurrent::Map.new
21
+ @list_dirname = Concurrent::Map.new
22
+ @list_subdirs = Concurrent::Map.new
23
+ @card_dirname = Concurrent::Map.new
18
24
  end
19
25
 
20
26
  def card_added(card)
@@ -37,10 +43,7 @@ module Trellodon
37
43
  list = @board.get_list(card.list_id)
38
44
  raise "List #{card.list_id} is not found" if list.nil?
39
45
 
40
- card_dir = File.join(@output_dir,
41
- "#{board.name} [#{board.short_id}]",
42
- "#{list.name} [#{list.short_id}]",
43
- "#{card.name} [#{card.short_id}]")
46
+ card_dir = File.join(@output_dir, board_dirname(board), list_dirname(list), card_dirname(card))
44
47
  FileUtils.mkdir_p(card_dir) unless File.directory?(card_dir)
45
48
  card_dir
46
49
  end
@@ -51,11 +54,57 @@ module Trellodon
51
54
  attachments_dir
52
55
  end
53
56
 
57
+ def board_dirname(board)
58
+ @board_dirname.compute_if_absent(board.id) { sanitize(board.name) }
59
+ end
60
+
61
+ def list_dirname(list)
62
+ @list_dirname.compute_if_absent(list.id) do
63
+ uniq_dirname(
64
+ dir: sanitize(list.name),
65
+ entity: list,
66
+ dirlist: @board_subdirs,
67
+ parent_id: list.board_id
68
+ )
69
+ end
70
+ end
71
+
72
+ def card_dirname(card)
73
+ @card_dirname.compute_if_absent(card.id) do
74
+ uniq_dirname(
75
+ dir: sanitize(card.name),
76
+ entity: card,
77
+ dirlist: @list_subdirs,
78
+ parent_id: card.list_id
79
+ )
80
+ end
81
+ end
82
+
83
+ def uniq_dirname(dir:, entity:, dirlist:, parent_id:)
84
+ return dir unless [List, Card].include?(entity.class)
85
+
86
+ dirlist.put_if_absent(parent_id, Concurrent::Map.new)
87
+ dirlist[parent_id].put_if_absent(dir, Concurrent::Array.new([entity.id]))
88
+ dirlist[parent_id][dir].push(entity.id) unless dirlist[parent_id][dir].include?(entity.id)
89
+ idx = dirlist[parent_id][dir].index(entity.id)
90
+ idx > 0 ? dir + "(#{idx})" : dir
91
+ end
92
+
93
+ def sanitize(name, separator: "")
94
+ name.gsub(/[\/\\"*?<>|:]+/, separator).slice(0, DIRNAME_MAXLENGTH)
95
+ end
96
+
54
97
  def download_attachments(card)
55
98
  return if card.attachments.nil? || card.attachments.empty?
99
+
56
100
  attachments_path = card_attachments_path(card)
57
101
  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))
102
+ att_file_path = File.join(attachments_path, att.file_name)
103
+ if File.file?(att_file_path)
104
+ logger.warn "Attachment file already exists: #{att_file_path}"
105
+ else
106
+ IO.copy_stream(URI.parse(att.url).open(att.headers), att_file_path)
107
+ end
59
108
  end
60
109
  end
61
110
 
@@ -73,6 +122,7 @@ module Trellodon
73
122
  ---
74
123
  title: #{card.name}
75
124
  last_updated_at: #{card.last_activity_date}
125
+ dumped_at: #{Time.now}
76
126
  labels: #{create_card_labels(card)}
77
127
  ---
78
128
  EOS
@@ -96,11 +146,13 @@ module Trellodon
96
146
 
97
147
  def create_card_labels(card)
98
148
  return "" if card.labels.nil? || card.labels.empty?
149
+
99
150
  card.labels.map { |label| "\n - " + label }.join
100
151
  end
101
152
 
102
153
  def create_card_checklists(card)
103
154
  return "" if card.checklists.nil? || card.checklists.empty?
155
+
104
156
  <<~EOS
105
157
 
106
158
  ## Checklists
@@ -117,11 +169,15 @@ module Trellodon
117
169
  end
118
170
 
119
171
  def create_card_checklist_item(item)
120
- "\n- [#{item.checked? ? "x" : " "}] #{item.name}"
172
+ formatted_item = "\n- [#{item.checked? ? "x" : " "}] #{item.name}"
173
+ formatted_item += " | *assigned_to: #{create_member(item.member)}*" if item.member
174
+ formatted_item += " | *due: #{item.due_date}*" if item.due_date
175
+ formatted_item
121
176
  end
122
177
 
123
178
  def create_card_comments(card)
124
179
  return "" if card.comments.nil? || card.comments.empty?
180
+
125
181
  <<~EOS
126
182
 
127
183
  ## Comments
@@ -132,13 +188,15 @@ module Trellodon
132
188
  def create_card_comment(comment)
133
189
  <<~EOS
134
190
 
135
- **#{comment.creator.full_name} (@#{comment.creator.username}) at #{comment.date}**
191
+ ### #{create_member(comment.creator)} at #{comment.date}
192
+
136
193
  #{comment.text}
137
194
  EOS
138
195
  end
139
196
 
140
197
  def create_card_attachments(card)
141
198
  return "" if card.attachments.nil? || card.attachments.empty?
199
+
142
200
  <<~EOS
143
201
 
144
202
  ## Attachments
@@ -152,6 +210,7 @@ module Trellodon
152
210
  ### #{attachment.name}
153
211
 
154
212
  **date**: #{attachment.date}
213
+ **added_by**: #{create_member(attachment.added_by)}
155
214
  **url**: #{attachment.url}
156
215
  **local copy**: #{create_attachment_local_link(attachment)}
157
216
  EOS
@@ -159,8 +218,14 @@ module Trellodon
159
218
 
160
219
  def create_attachment_local_link(attachment)
161
220
  return "-" unless attachment.is_upload
221
+
162
222
  "[#{attachment.file_name}](#{File.join("./", ATTACHMENTS_DIRNAME, attachment.file_name)})"
163
223
  end
224
+
225
+ def create_member(member)
226
+ return "" unless member
227
+ "#{member.full_name} (@#{member.username})"
228
+ end
164
229
  end
165
230
  end
166
231
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Trellodon
6
+ class Journal
7
+ FILENAME = "journal.yml"
8
+
9
+ def initialize(dir)
10
+ @location = File.join(dir, FILENAME)
11
+ File.write(location, "") unless File.exist?(location)
12
+ end
13
+
14
+ def <<(record)
15
+ File.write(location, record.to_yaml, mode: "a+")
16
+ end
17
+
18
+ private attr_reader :location
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trellodon
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fargelus
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-04-12 00:00:00.000000000 Z
13
+ date: 2022-04-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: anyway_config
@@ -69,7 +69,7 @@ dependencies:
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0'
71
71
  - !ruby/object:Gem::Dependency
72
- name: ruby-next-core
72
+ name: ruby-next
73
73
  requirement: !ruby/object:Gem::Requirement
74
74
  requirements:
75
75
  - - ">="
@@ -111,6 +111,7 @@ files:
111
111
  - lib/trellodon/entities/attachment.rb
112
112
  - lib/trellodon/entities/board.rb
113
113
  - lib/trellodon/entities/card.rb
114
+ - lib/trellodon/entities/card_event.rb
114
115
  - lib/trellodon/entities/checklist.rb
115
116
  - lib/trellodon/entities/checklist_item.rb
116
117
  - lib/trellodon/entities/comment.rb
@@ -120,6 +121,7 @@ files:
120
121
  - lib/trellodon/formatters.rb
121
122
  - lib/trellodon/formatters/base.rb
122
123
  - lib/trellodon/formatters/markdown.rb
124
+ - lib/trellodon/journal.rb
123
125
  - lib/trellodon/schedulers.rb
124
126
  - lib/trellodon/schedulers/base.rb
125
127
  - lib/trellodon/schedulers/concurrent.rb