trello_backup_renderer 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 730791c16eea8bd272b2ed9b8546e21de0eb7b7102da0ae4a101208527b472bf
4
+ data.tar.gz: 484ff65a7b9caf389cdb16cace8c6284a634908a38664b62c85a65e7c0c9e357
5
+ SHA512:
6
+ metadata.gz: 215b5edbc15c994be54cec08b91659a60ec5cfc436eaca89b7a30d8704c52b009a51518d017a328f9dbf8af5aa1b3b1a7ed75d1212529f6beb6c7870e8259744
7
+ data.tar.gz: 87ff3f13fba57368e12b2ca54fb7eda7c01462bf3b51f500b19e4f041eef2c7c312568d0c2d06baa4b4d5496087a844df6fee2b525192f8b3a7f6b15461549f2
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.6.5
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.5
7
+ before_install: gem install bundler -v 1.17.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in trello_backup_renderer.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ trello_backup_renderer (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (10.5.0)
11
+ rspec (3.9.0)
12
+ rspec-core (~> 3.9.0)
13
+ rspec-expectations (~> 3.9.0)
14
+ rspec-mocks (~> 3.9.0)
15
+ rspec-core (3.9.0)
16
+ rspec-support (~> 3.9.0)
17
+ rspec-expectations (3.9.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.9.0)
20
+ rspec-mocks (3.9.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.9.0)
23
+ rspec-support (3.9.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 1.17)
30
+ rake (~> 10.0)
31
+ rspec (~> 3.0)
32
+ trello_backup_renderer!
33
+
34
+ BUNDLED WITH
35
+ 1.17.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Jacob Williams
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # trello\_backup\_renderer
2
+
3
+ Generates HTML files for [Trello](http://trello.com) boards from backups created by [jtpio/trello-full-backup](https://github.com/jtpio/trello-full-backup).
4
+
5
+ * Unlike Trello's built-in print functionality, it includes all card cover images and comments for the board in a single document.
6
+ * Can be run locally without authorizing a third-party service to access your Trello data.
7
+
8
+ You can view a sample file generated by the tool [here](spec/test_board/Renderer%20Test%20Board_full.html).
9
+
10
+ (I am not affiliated with Trello in any way.)
11
+
12
+ ## Installation
13
+
14
+ $ gem install trello_backup_renderer
15
+
16
+ ## Usage
17
+
18
+ First, use [jtpio/trello-full-backup](https://github.com/jtpio/trello-full-backup) to create a backup of your board.
19
+ It's important to use the `-t` option.
20
+
21
+ Then find the folder in which the backup was created.
22
+ This should contain a file `BOARD_NAME_full.json`, and folders such as `0_someLongLIstId`.
23
+ `trello_backup_renderer` uses the full.json file and also uses the files in the nested `attachments` directories, and depends on the folder name format that trello-full-backup generates.
24
+ (It does not use the `card.json` or `description.md` files that trello-full-backup generates.)
25
+
26
+ Run the tool like this:
27
+
28
+ $ trello_backup_renderer PATH_TO_BOARD_BACKUP_DIR
29
+
30
+ This will create a `BOARD_NAME_full.html` file in the given directory.
31
+ This file may contain relative links to the attachment files in that directory.
32
+
33
+ ## Options
34
+
35
+ A default stylesheet is embedded into the HTML file; if you don't want it included, use the `--omit-styles` option.
36
+ You can also link to your own stylesheet using the `--head-insert` option:
37
+
38
+ $ trello_backup_renderer PATH_TO_BOARD_BACKUP_DIR --omit-styles --insert-head='<link href="path/to/stylesheet.css" rel="stylesheet">'
39
+
40
+ By default, the full name of every comment author is included along with the comment.
41
+ If the board is private and has only one comment author, you may wish to omit their name.
42
+ To do this, use the `--hide-authorship` option:
43
+
44
+ $ trello_backup_renderer PATH_TO_BOARD_BACKUP_DIR --hide-authorship
45
+
46
+ ## Supported Features
47
+
48
+ Currently only the following data is included in the generated HTML page:
49
+
50
+ - Lists
51
+ - Names
52
+ - Cards
53
+ - Covers (when they're attachments)
54
+ - Names
55
+ - Labels (name and color)
56
+ - Descriptions (without formatting)
57
+ - Comments (without formatting)
58
+
59
+ ## Development
60
+
61
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
62
+
63
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
64
+
65
+ A script `bin/scrub_test_data` is included which can be run against a board backup directory to remove attributes and files which are unused by trello_backup_renderer and replace IDs and member names with placeholder values.
66
+ This was used in creating the test data seen in `spec/test_board`.
67
+ Warning: The `scrub_test_data` script deletes data from the specified directory in-place.
68
+
69
+ ## Contributing
70
+
71
+ Bug reports and pull requests are welcome on GitHub at https://github.com/brokensandals/trello_backup_renderer.
72
+
73
+ ## License
74
+
75
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "trello_backup_renderer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+
5
+ unless ARGV.count == 1
6
+ puts "Usage: scrub_test_data BOARD_DIRECTORY"
7
+ exit 1
8
+ end
9
+
10
+ RULES = {
11
+ 'actions' => {
12
+ 'data' => {
13
+ 'card' => {
14
+ 'id' => :id
15
+ },
16
+ 'text' => true
17
+ },
18
+ 'date' => true,
19
+ 'memberCreator' => {
20
+ 'fullName' => :memberFullName
21
+ },
22
+ 'type' => true
23
+ },
24
+ 'cards' => {
25
+ 'name' => true,
26
+ 'closed' => true,
27
+ 'cover' => {
28
+ 'idAttachment' => :id
29
+ },
30
+ 'desc' => true,
31
+ 'id' => :id,
32
+ 'idList' => :id,
33
+ 'labels' => ['color', 'name'],
34
+ 'pos' => true
35
+ },
36
+ 'lists' => {
37
+ 'closed' => true,
38
+ 'id' => :id,
39
+ 'name' => true,
40
+ 'pos' => true
41
+ }
42
+ }
43
+
44
+ ANONYMIZATION_MAPPINGS = {}
45
+ ANONYMIZATION_VALUES = {
46
+ id: (1..100).map { |i| "id#{i}"},
47
+ memberFullName: ['Smerson McPerson']
48
+ }
49
+
50
+ def anonymize(value, type)
51
+ mappings = (ANONYMIZATION_MAPPINGS[type] ||= {})
52
+ mappings[value] ||= ((ANONYMIZATION_VALUES[type] || []).shift or raise "Ran out of anonymizations for #{type}")
53
+ end
54
+
55
+ def scrub_json(json, rules)
56
+ if json.is_a?(Array)
57
+ json.each { |item| scrub_json(item, rules) }
58
+ elsif json.is_a?(Hash)
59
+ json.keys.each do |key|
60
+ if rules.include?(key)
61
+ if rules.is_a?(Hash)
62
+ case rules[key]
63
+ when Array, Hash
64
+ scrub_json(json[key], rules[key])
65
+ when Symbol
66
+ json[key] = anonymize(json[key], rules[key])
67
+ end
68
+ end
69
+ else
70
+ json.delete(key)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def scrub_dir(dir)
77
+ board_file_paths = Dir.glob("#{dir}/*.json").to_a
78
+ raise "Expected exactly one json file in #{dir}" unless board_file_paths.count == 1
79
+
80
+ board_json = JSON.parse(File.read(board_file_paths.first))
81
+ scrub_json(board_json, RULES)
82
+ File.write(board_file_paths.first, JSON.pretty_generate(board_json))
83
+
84
+ Dir.glob("#{dir}/*_*/*_*/attachments/*.*").to_a.each do |attachment_path|
85
+ basename = File.basename(attachment_path)
86
+ if match = /\A(?<pos>\d+)_(?<id>[a-z0-9]+)(?<rest>.*)?\z/.match(basename)
87
+ new_id = anonymize(match[:id], :id)
88
+ new_path = File.join(File.dirname(attachment_path), "#{match[:pos]}_#{new_id}#{match[:rest]}")
89
+ File.rename(attachment_path, new_path)
90
+ end
91
+ end
92
+
93
+ File.delete(*Dir.glob("#{dir}/**/card.json").to_a)
94
+ File.delete(*Dir.glob("#{dir}/**/description.md").to_a)
95
+
96
+ Dir.glob("#{dir}/*_*/*_*").to_a.each do |card_dir_path|
97
+ next unless File.directory?(card_dir_path)
98
+ if Dir.empty?(card_dir_path)
99
+ Dir.rmdir(card_dir_path)
100
+ else
101
+ basename = File.basename(card_dir_path)
102
+ pos, id = basename.split('_')
103
+ new_id = anonymize(id, :id)
104
+ new_path = File.join(File.dirname(card_dir_path), "#{pos}_#{new_id}")
105
+ File.rename(card_dir_path, new_path)
106
+ end
107
+ end
108
+
109
+ Dir.glob("#{dir}/*_*").to_a.each do |list_dir_path|
110
+ next unless File.directory?(list_dir_path)
111
+ if Dir.empty?(list_dir_path)
112
+ Dir.rmdir(list_dir_path)
113
+ else
114
+ basename = File.basename(list_dir_path)
115
+ pos, id = basename.split('_')
116
+ new_id = anonymize(id, :id)
117
+ new_path = File.join(File.dirname(list_dir_path), "#{pos}_#{new_id}")
118
+ File.rename(list_dir_path, new_path)
119
+ end
120
+ end
121
+ end
122
+
123
+ scrub_dir(ARGV[0])
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'trello_backup_renderer'
5
+
6
+ options = {}
7
+ optparse = OptionParser.new do |opts|
8
+ opts.banner = 'Usage: trello_backup_renderer BOARD_BACKUP_DIR [options]'
9
+
10
+ opts.on('--head-insert=HTML', 'Add HTML inside the <head> tag') do |html|
11
+ options[:head_insert] = html
12
+ end
13
+
14
+ opts.on('--hide-authorship', 'Do not display the authors of comments. (Done via CSS.)') do
15
+ options[:hide_authorship] = true
16
+ end
17
+
18
+ opts.on('--omit-styles', 'Do not include default stylesheet.') do
19
+ options[:omit_styles] = true
20
+ end
21
+
22
+ opts.on('--version', 'Print program version and exit') do
23
+ puts "trello_backup_renderer #{TrelloBackupRenderer::VERSION}"
24
+ exit 0
25
+ end
26
+ end
27
+
28
+ optparse.parse!
29
+ if ARGV.length != 1
30
+ puts optparse
31
+ exit -1
32
+ end
33
+
34
+ rendering_options = TrelloBackupRenderer::Rendering::Options.new(options)
35
+
36
+ board_dir = ARGV.shift
37
+ board = TrelloBackupRenderer.load_board_dir(board_dir)
38
+ html = TrelloBackupRenderer.generate_board_html(board, rendering_options)
39
+ out_file = File.join(board_dir, File.basename(TrelloBackupRenderer.find_board_json_file(board_dir)).sub(/json\z/, 'html'))
40
+ File.write(out_file, html)
@@ -0,0 +1,11 @@
1
+ require 'trello_backup_renderer/version'
2
+ require 'trello_backup_renderer/models'
3
+ require 'trello_backup_renderer/parsing'
4
+ require 'trello_backup_renderer/rendering'
5
+
6
+ module TrelloBackupRenderer
7
+ class Error < StandardError; end
8
+
9
+ extend Parsing
10
+ extend Rendering
11
+ end
@@ -0,0 +1,83 @@
1
+ module TrelloBackupRenderer
2
+ module Models
3
+ class Board
4
+ attr_reader :lists
5
+
6
+ def initialize(args = {})
7
+ @lists = (args[:lists] || []).sort_by(&:pos)
8
+ end
9
+ end
10
+
11
+ class List
12
+ attr_reader :cards
13
+ attr_reader :closed
14
+ attr_reader :id
15
+ attr_reader :name
16
+ attr_reader :pos
17
+
18
+ def initialize(args)
19
+ @cards = (args[:cards] || []).sort_by(&:pos)
20
+ @closed = args[:closed]
21
+ @id = args[:id]
22
+ @name = args[:name]
23
+ @pos = args[:pos]
24
+ end
25
+ end
26
+
27
+ class Card
28
+ attr_reader :closed
29
+ attr_reader :comments
30
+ attr_reader :cover_attachment
31
+ attr_reader :desc
32
+ attr_reader :id
33
+ attr_reader :id_list
34
+ attr_reader :labels
35
+ attr_reader :name
36
+ attr_reader :pos
37
+
38
+ def initialize(args)
39
+ @closed = args[:closed]
40
+ @comments = (args[:comments] || []).sort_by(&:date)
41
+ @cover_attachment = args[:cover_attachment]
42
+ @desc = args[:desc]
43
+ @id = args[:id]
44
+ @id_list = args[:id_list]
45
+ @labels = (args[:labels] || []).sort_by(&:name)
46
+ @name = args[:name]
47
+ @pos = args[:pos]
48
+ end
49
+ end
50
+
51
+ class Attachment
52
+ attr_reader :path
53
+
54
+ def initialize(args)
55
+ @path = args[:path]
56
+ end
57
+ end
58
+
59
+ class Label
60
+ attr_reader :color
61
+ attr_reader :name
62
+
63
+ def initialize(args)
64
+ @color = args[:color]
65
+ @name = args[:name]
66
+ end
67
+ end
68
+
69
+ class Comment
70
+ attr_reader :creator_full_name
71
+ attr_reader :date
72
+ attr_reader :id_card
73
+ attr_reader :text
74
+
75
+ def initialize(args)
76
+ @creator_full_name = args[:creator_full_name]
77
+ @date = args[:date]
78
+ @id_card = args[:id_card]
79
+ @text = args[:text]
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,89 @@
1
+ require 'json'
2
+
3
+ module TrelloBackupRenderer
4
+ module Parsing
5
+ include Models
6
+
7
+ def parse_comment_json(action_json)
8
+ return nil unless action_json['type'] == 'commentCard'
9
+ Comment.new(
10
+ creator_full_name: action_json['memberCreator']['fullName'],
11
+ date: action_json['date'],
12
+ id_card: action_json['data']['card']['id'],
13
+ text: action_json['data']['text']
14
+ )
15
+ end
16
+
17
+ def parse_label_json(label_json)
18
+ Label.new(
19
+ color: label_json['color'],
20
+ name: label_json['name']
21
+ )
22
+ end
23
+
24
+ def parse_card_json(card_json, attachments_by_id, comments)
25
+ cover_id_attachment = card_json.fetch('cover', {})['idAttachment']
26
+
27
+ Card.new(
28
+ closed: card_json['closed'],
29
+ comments: comments.select { |comment| comment.id_card == card_json['id'] },
30
+ cover_attachment: attachments_by_id[cover_id_attachment],
31
+ desc: (card_json['desc'] if card_json['desc'] && !card_json['desc'].empty?),
32
+ id: card_json['id'],
33
+ id_list: card_json['idList'],
34
+ labels: (card_json['labels'] || []).map { |label_json| parse_label_json(label_json) },
35
+ name: card_json['name'],
36
+ pos: card_json['pos']
37
+ )
38
+ end
39
+
40
+ def parse_list_json(list_json, cards)
41
+ List.new(
42
+ cards: (cards.select { |card| card.id_list == list_json['id'] }),
43
+ closed: list_json['closed'],
44
+ id: list_json['id'],
45
+ name: list_json['name'],
46
+ pos: list_json['pos']
47
+ )
48
+ end
49
+
50
+ def parse_board_json(board_json, attachment_paths)
51
+ attachments_by_id = attachment_paths.transform_values { |path| Attachment.new(path: path) }
52
+ comments = (board_json['actions'] || []).map { |action_json| parse_comment_json(action_json) }.compact
53
+ cards = (board_json['cards'] || []).map { |card_json| parse_card_json(card_json, attachments_by_id, comments) }
54
+ Board.new(
55
+ lists: (board_json['lists'] || []).map { |list_json| parse_list_json(list_json, cards) }
56
+ )
57
+ end
58
+
59
+ def load_attachment_paths(dir)
60
+ paths = {}
61
+ Dir.glob(File.join(dir, '*_*', '*_*', 'attachments', '*')) do |path|
62
+ if File.file?(path) && match = /\A\d+_(?<id>[a-z0-9]+).*\z/.match(File.basename(path))
63
+ # I'm just assuming the attachment IDs are globally unique rather than scoped to the card,
64
+ # although I wasn't able to confirm this in the API docs.
65
+ paths[match[:id]] = path
66
+ end
67
+ end
68
+ paths
69
+ end
70
+
71
+ def relativize_attachment_paths(paths, base_dir)
72
+ paths.transform_values { |path| path.sub(/\A#{Regexp.quote(base_dir)}\/?/, '')}
73
+ end
74
+
75
+ def find_board_json_file(dir)
76
+ board_file_paths = Dir.glob(File.join(dir, '*_full.json')).to_a
77
+ raise Error, "Expected one <board_name>_full.json file in: #{dir}" unless board_file_paths.count == 1
78
+ board_file_paths.first
79
+ end
80
+
81
+ def load_board_dir(dir)
82
+ raise Error, "Not a directory: #{dir}" unless File.directory?(dir)
83
+ board_file_path = find_board_json_file(dir)
84
+ board_json = JSON.parse(File.read(board_file_path))
85
+ attachment_paths = relativize_attachment_paths(load_attachment_paths(dir), dir)
86
+ parse_board_json(board_json, attachment_paths)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,95 @@
1
+ require 'erb'
2
+
3
+ module TrelloBackupRenderer
4
+ module Rendering
5
+ BOARD_HTML_TEMPLATE = File.read(File.join(__dir__, 'templates', 'board.html.erb'))
6
+ CSS_FILE = File.read(File.join(__dir__, 'styles', 'default.css'))
7
+ NO_AUTHORSHIP_CSS_FILE = File.read(File.join(__dir__, 'styles', 'no-authorship.css'))
8
+
9
+ class Options
10
+ attr_reader :hide_authorship
11
+ attr_reader :omit_styles
12
+ attr_reader :head_insert
13
+
14
+ def initialize(args = {})
15
+ @hide_authorship = args[:hide_authorship]
16
+ @omit_styles = args[:omit_styles]
17
+ @head_insert = args[:head_insert]
18
+ end
19
+ end
20
+
21
+ class BoardPage
22
+ def initialize(board, options)
23
+ @board = board
24
+ @options = options
25
+ end
26
+
27
+ def lists
28
+ @board.lists.reject(&:closed).map { |list| ListPresenter.new(list) }
29
+ end
30
+
31
+ def stylesheet_tags
32
+ tags = ''
33
+ tags << '<style type="text/css">' + CSS_FILE + '</style>' unless @options.omit_styles
34
+ tags << '<style type="text/css">' + NO_AUTHORSHIP_CSS_FILE + '</style>' if @options.hide_authorship
35
+ tags
36
+ end
37
+
38
+ def head_insert
39
+ @options.head_insert || ''
40
+ end
41
+
42
+ def render
43
+ ERB.new(BOARD_HTML_TEMPLATE).result(binding)
44
+ end
45
+ end
46
+
47
+ class ListPresenter < SimpleDelegator
48
+ def cards
49
+ super.reject(&:closed).map { |card| CardPresenter.new(card) }
50
+ end
51
+
52
+ def name
53
+ ERB::Util.h(super)
54
+ end
55
+ end
56
+
57
+ class CardPresenter < SimpleDelegator
58
+ def desc
59
+ ERB::Util.h(super) if super
60
+ end
61
+
62
+ def comments
63
+ super.map { |comment| CommentPresenter.new(comment) }
64
+ end
65
+
66
+ def labels
67
+ super.map { |label| LabelPresenter.new(label) }
68
+ end
69
+
70
+ def name
71
+ ERB::Util.h(super)
72
+ end
73
+ end
74
+
75
+ class LabelPresenter < SimpleDelegator
76
+ def name
77
+ ERB::Util.h(super)
78
+ end
79
+ end
80
+
81
+ class CommentPresenter < SimpleDelegator
82
+ def creator_full_name
83
+ ERB::Util.h(super)
84
+ end
85
+
86
+ def text
87
+ ERB::Util.h(super)
88
+ end
89
+ end
90
+
91
+ def generate_board_html(board, options = Options.new)
92
+ BoardPage.new(board, options).render
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,58 @@
1
+ .lists {
2
+ padding-left: 0px;
3
+ }
4
+
5
+ .list {
6
+ list-style-type: none;
7
+ }
8
+
9
+ .list-name {
10
+ padding-bottom: 5px;
11
+ border-bottom: 1px solid black;
12
+ }
13
+
14
+ .cards {
15
+ padding-left: 5px;
16
+ }
17
+
18
+ .card {
19
+ width: 300px;
20
+ list-style-type: none;
21
+ border: 1px solid gray;
22
+ border-radius: 3px;
23
+ margin-bottom: 10px;
24
+ padding: 10px;
25
+ }
26
+
27
+ .card-cover {
28
+ object-fit: scale-down;
29
+ margin: 0;
30
+ }
31
+
32
+ .card-name {
33
+ font-size: 120%;
34
+ margin: 0;
35
+ padding: 0;
36
+ }
37
+
38
+ .labels {
39
+ margin-top: 5px;
40
+ padding-left: 0;
41
+ }
42
+
43
+ .label {
44
+ list-style-type: none;
45
+ display: inline-block;
46
+ padding: 3px;
47
+ border-radius: 3px;
48
+ font-size: 75%;
49
+ }
50
+
51
+ .comments {
52
+ padding-left: 0;
53
+ }
54
+
55
+ .comment {
56
+ list-style-type: none;
57
+ border-top: 1px solid gray;
58
+ }
@@ -0,0 +1,3 @@
1
+ .authorship {
2
+ display: none;
3
+ }
@@ -0,0 +1,55 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+
6
+ <%= stylesheet_tags %>
7
+
8
+ <%= head_insert %>
9
+ </head>
10
+ <body>
11
+
12
+ <ul class="lists">
13
+ <% lists.each do |list| %>
14
+ <li class="list">
15
+ <h1 class="list-name"><%= list.name %></h1>
16
+
17
+ <ul class="cards">
18
+ <% list.cards.each do |card| %>
19
+ <li class="card">
20
+ <% if card.cover_attachment %>
21
+ <img class="card-cover" src="<%= card.cover_attachment.path %>" width="300px"/>
22
+ <% end %>
23
+
24
+ <h2 class="card-name"><%= card.name %></h2>
25
+
26
+ <% if card.labels.any? %>
27
+ <ul class="labels">
28
+ <% card.labels.each do |label| %>
29
+ <li class="label" style="background-color: <%= label.color %>"><%= label.name %></li>
30
+ <% end %>
31
+ </ul>
32
+ <% end %>
33
+
34
+ <% if card.desc %>
35
+ <p class="card-desc"><%= card.desc %></p>
36
+ <% end %>
37
+
38
+ <% if card.comments.any? %>
39
+ <ul class="comments">
40
+ <% card.comments.each do |comment| %>
41
+ <li class="comment">
42
+ <p><span class="authorship"><%= comment.creator_full_name %> commented:</span> <%= comment.text %></p>
43
+ </li>
44
+ <% end %>
45
+ </ul>
46
+ <% end %>
47
+ </li>
48
+ <% end %>
49
+ </ul>
50
+ </li>
51
+ <% end %>
52
+ </ul>
53
+
54
+ </body>
55
+ </html>
@@ -0,0 +1,3 @@
1
+ module TrelloBackupRenderer
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,32 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "trello_backup_renderer/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "trello_backup_renderer"
8
+ spec.version = TrelloBackupRenderer::VERSION
9
+ spec.authors = ["Jacob Williams"]
10
+ spec.email = ["jacobaw@gmail.com"]
11
+
12
+ spec.summary = %q{Renders Trello backups to HTML files. Operates on backups created by https://github.com/jtpio/trello-full-backup}
13
+ spec.homepage = "https://github.com/brokensandals/trello_backup_renderer"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/brokensandals/trello_backup_renderer"
18
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.17"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trello_backup_renderer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jacob Williams
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - jacobaw@gmail.com
58
+ executables:
59
+ - trello_backup_renderer
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".ruby-version"
66
+ - ".travis.yml"
67
+ - Gemfile
68
+ - Gemfile.lock
69
+ - LICENSE.txt
70
+ - README.md
71
+ - Rakefile
72
+ - bin/console
73
+ - bin/scrub_test_data
74
+ - bin/setup
75
+ - exe/trello_backup_renderer
76
+ - lib/trello_backup_renderer.rb
77
+ - lib/trello_backup_renderer/models.rb
78
+ - lib/trello_backup_renderer/parsing.rb
79
+ - lib/trello_backup_renderer/rendering.rb
80
+ - lib/trello_backup_renderer/styles/default.css
81
+ - lib/trello_backup_renderer/styles/no-authorship.css
82
+ - lib/trello_backup_renderer/templates/board.html.erb
83
+ - lib/trello_backup_renderer/version.rb
84
+ - trello_backup_renderer.gemspec
85
+ homepage: https://github.com/brokensandals/trello_backup_renderer
86
+ licenses:
87
+ - MIT
88
+ metadata:
89
+ homepage_uri: https://github.com/brokensandals/trello_backup_renderer
90
+ source_code_uri: https://github.com/brokensandals/trello_backup_renderer
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.0.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Renders Trello backups to HTML files. Operates on backups created by https://github.com/jtpio/trello-full-backup
110
+ test_files: []