trellodon 0.3.0 → 0.4.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: 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