trello_backup_renderer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []