portable 1.0.0.pre.alpha.2 → 1.0.0.pre.alpha.7
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 +4 -4
- data/.rubocop.yml +1 -1
- data/.travis.yml +0 -2
- data/lib/portable.rb +5 -0
- data/lib/portable/data/provider.rb +10 -8
- data/lib/portable/document.rb +25 -4
- data/lib/portable/modeling/data_table.rb +8 -3
- data/lib/portable/modeling/sheet.rb +26 -12
- data/lib/portable/rendering/column.rb +31 -0
- data/lib/portable/rendering/row.rb +14 -11
- data/lib/portable/rendering/sheet.rb +13 -8
- data/lib/portable/util.rb +11 -0
- data/lib/portable/util/pivotable.rb +19 -0
- data/lib/portable/util/uniqueness.rb +25 -0
- data/lib/portable/version.rb +1 -1
- data/lib/portable/writers/base.rb +8 -0
- data/lib/portable/writers/csv.rb +18 -25
- data/lib/portable/writers/result.rb +24 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9a80aeca8e54c0e02bf414ee6c79b6e110e126a57fc64a3a9d3f8990481f940
|
4
|
+
data.tar.gz: 7ee0c481c4d15148857726d3644230a76a52f8ae178425c5e1c5defbc485c81b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e45f20154ff8471104bcdbd5fbbc32d8cf3c892606c077f418b3d29456c8221c080bc7ae2719f63920e54a8f139ad62b2104ecfee1a39699d54467134f6a9be
|
7
|
+
data.tar.gz: d11bdfc431b996f82b8f6b14f53862ff98236ad08f6c7e79d87fdd3153137c42bc71687506bf2bfa544a9c1a62e745ba9658a8e1ded37eba3e9e705cb173ed49
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
data/lib/portable.rb
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
require 'acts_as_hashable'
|
11
|
+
require 'benchmark'
|
11
12
|
require 'csv'
|
12
13
|
require 'fileutils'
|
13
14
|
require 'forwardable'
|
@@ -15,6 +16,10 @@ require 'objectable'
|
|
15
16
|
require 'realize'
|
16
17
|
require 'time'
|
17
18
|
|
19
|
+
# Shared modules/classes
|
20
|
+
require_relative 'portable/util'
|
21
|
+
|
22
|
+
# Main implementation points
|
18
23
|
require_relative 'portable/data'
|
19
24
|
require_relative 'portable/document'
|
20
25
|
require_relative 'portable/rendering'
|
@@ -14,27 +14,29 @@ module Portable
|
|
14
14
|
# Container of data sources that is inputted into a writer alongside a document.
|
15
15
|
# It contains all the data sources the writer will use to render a document.
|
16
16
|
class Provider
|
17
|
+
include Util::Pivotable
|
18
|
+
include Util::Uniqueness
|
17
19
|
acts_as_hashable
|
18
20
|
|
19
21
|
def initialize(data_sources: [])
|
20
|
-
|
22
|
+
sources = Source.array(data_sources)
|
23
|
+
@data_sources_by_name = pivot_by_name(sources)
|
24
|
+
|
25
|
+
assert_no_duplicate_names(sources)
|
21
26
|
|
22
27
|
freeze
|
23
28
|
end
|
24
29
|
|
30
|
+
# Fail hard if we cannot identify which data source to use. This should help prevent
|
31
|
+
# possible configuration issues (i.e. typos.)
|
25
32
|
def data_source(name)
|
26
|
-
data_sources_by_name
|
33
|
+
data_sources_by_name[name.to_s] ||
|
34
|
+
raise(ArgumentError, "data source: '#{name}' cannot be found.")
|
27
35
|
end
|
28
36
|
|
29
37
|
private
|
30
38
|
|
31
39
|
attr_reader :data_sources_by_name
|
32
|
-
|
33
|
-
def pivot_by_name(data_sources)
|
34
|
-
data_sources.each_with_object({}) do |data_source, memo|
|
35
|
-
memo[data_source.name] = data_source
|
36
|
-
end
|
37
|
-
end
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
data/lib/portable/document.rb
CHANGED
@@ -13,16 +13,37 @@ require_relative 'modeling/sheet'
|
|
13
13
|
module Portable
|
14
14
|
# Top-level object model for a renderable document.
|
15
15
|
class Document
|
16
|
+
include Util::Pivotable
|
17
|
+
include Util::Uniqueness
|
16
18
|
acts_as_hashable
|
17
19
|
|
18
|
-
attr_reader :
|
20
|
+
attr_reader :options
|
19
21
|
|
20
22
|
def initialize(sheets: [], options: {})
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@options = Modeling::Options.make(options)
|
23
|
+
@sheets_by_name = make_unique_sheets_by_name(sheets)
|
24
|
+
@options = Modeling::Options.make(options, nullable: false)
|
24
25
|
|
25
26
|
freeze
|
26
27
|
end
|
28
|
+
|
29
|
+
def sheet(name)
|
30
|
+
sheets_by_name.fetch(name.to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def sheets
|
34
|
+
sheets_by_name.values
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :sheets_by_name
|
40
|
+
|
41
|
+
def make_unique_sheets_by_name(sheets)
|
42
|
+
sheets = Modeling::Sheet.array(sheets)
|
43
|
+
sheets << Modeling::Sheet.new if sheets.empty?
|
44
|
+
|
45
|
+
assert_no_duplicate_names(sheets)
|
46
|
+
pivot_by_name(sheets)
|
47
|
+
end
|
27
48
|
end
|
28
49
|
end
|
@@ -16,10 +16,15 @@ module Portable
|
|
16
16
|
class DataTable
|
17
17
|
acts_as_hashable
|
18
18
|
|
19
|
-
attr_reader :columns
|
19
|
+
attr_reader :auto, :columns, :include_headers
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
alias include_headers? include_headers
|
22
|
+
alias auto? auto
|
23
|
+
|
24
|
+
def initialize(auto: true, columns: [], include_headers: true)
|
25
|
+
@auto = auto || false
|
26
|
+
@columns = Column.array(columns)
|
27
|
+
@include_headers = include_headers || false
|
23
28
|
|
24
29
|
freeze
|
25
30
|
end
|
@@ -17,31 +17,45 @@ module Portable
|
|
17
17
|
# one file.
|
18
18
|
class Sheet
|
19
19
|
acts_as_hashable
|
20
|
+
extend Forwardable
|
20
21
|
|
21
|
-
|
22
|
+
def_delegators :data_table,
|
23
|
+
:auto?,
|
24
|
+
:columns,
|
25
|
+
:include_headers?
|
26
|
+
|
27
|
+
attr_reader :data_source_name,
|
28
|
+
:data_table,
|
22
29
|
:footer_rows,
|
23
30
|
:header_rows,
|
24
|
-
:name
|
25
|
-
:include_headers
|
31
|
+
:name
|
26
32
|
|
27
33
|
def initialize(
|
34
|
+
data_source_name: '',
|
28
35
|
data_table: nil,
|
29
36
|
footer_rows: [],
|
30
37
|
header_rows: [],
|
31
|
-
name: ''
|
32
|
-
include_headers: true
|
38
|
+
name: ''
|
33
39
|
)
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@
|
38
|
-
@
|
40
|
+
@data_source_name = decide_data_source_name(data_source_name, name)
|
41
|
+
@name = name.to_s
|
42
|
+
@data_table = DataTable.make(data_table, nullable: false)
|
43
|
+
@footer_rows = footer_rows || []
|
44
|
+
@header_rows = header_rows || []
|
39
45
|
|
40
46
|
freeze
|
41
47
|
end
|
42
48
|
|
43
|
-
|
44
|
-
|
49
|
+
# Use exact name if possible, if not then use the sheet name or else use the
|
50
|
+
# "default" one (noted by a blank name).
|
51
|
+
def decide_data_source_name(data_source_name, sheet_name)
|
52
|
+
if !data_source_name.to_s.empty?
|
53
|
+
data_source_name.to_s
|
54
|
+
elsif !sheet_name.to_s.empty?
|
55
|
+
sheet_name.to_s
|
56
|
+
else
|
57
|
+
''
|
58
|
+
end
|
45
59
|
end
|
46
60
|
end
|
47
61
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Portable
|
11
|
+
module Rendering
|
12
|
+
class Column # :nodoc: all
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
attr_reader :column, :pipeline
|
16
|
+
|
17
|
+
def_delegators :column, :header
|
18
|
+
|
19
|
+
def_delegators :pipeline, :transform
|
20
|
+
|
21
|
+
def initialize(column, resolver: Objectable.resolver)
|
22
|
+
raise ArgumentError, 'column is required' unless column
|
23
|
+
|
24
|
+
@column = column
|
25
|
+
@pipeline = Realize::Pipeline.new(column.transformers, resolver: resolver)
|
26
|
+
|
27
|
+
freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -7,34 +7,37 @@
|
|
7
7
|
# LICENSE file in the root directory of this source tree.
|
8
8
|
#
|
9
9
|
|
10
|
+
require_relative 'column'
|
11
|
+
|
10
12
|
module Portable
|
11
13
|
module Rendering
|
12
14
|
# Internal intermediary class that knows how to combine columns specification
|
13
15
|
# instances with their respective Realize pipelines.
|
14
16
|
class Row # :nodoc: all
|
15
|
-
attr_reader :
|
17
|
+
attr_reader :columns, :resolver
|
16
18
|
|
17
19
|
def initialize(columns, resolver: Objectable.resolver)
|
18
|
-
@
|
19
|
-
|
20
|
-
end
|
20
|
+
@resolver = resolver
|
21
|
+
@columns = columns.map { |column| Column.new(column, resolver: resolver) }
|
21
22
|
|
22
23
|
freeze
|
23
24
|
end
|
24
25
|
|
25
26
|
def render(object, time)
|
26
|
-
|
27
|
-
memo[column.header] = pipeline.transform(object, time)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def columns
|
32
|
-
column_pipelines.keys
|
27
|
+
columns.map { |column| column.transform(object, time) }
|
33
28
|
end
|
34
29
|
|
35
30
|
def headers
|
36
31
|
columns.map(&:header)
|
37
32
|
end
|
33
|
+
|
34
|
+
def model_columns
|
35
|
+
columns.map(&:column)
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge(other)
|
39
|
+
self.class.new(model_columns + other.model_columns, resolver: resolver)
|
40
|
+
end
|
38
41
|
end
|
39
42
|
end
|
40
43
|
end
|
@@ -21,27 +21,32 @@ module Portable
|
|
21
21
|
@resolver = resolver
|
22
22
|
|
23
23
|
@row_renderers = @document.sheets.each_with_object({}) do |sheet, memo|
|
24
|
-
|
25
|
-
|
26
|
-
memo[sheet.name] = Row.new(sheet.data_table.columns, resolver: resolver)
|
24
|
+
memo[sheet.name] = Row.new(sheet.columns, resolver: resolver)
|
27
25
|
end
|
28
26
|
|
29
27
|
freeze
|
30
28
|
end
|
31
29
|
|
32
30
|
def row_renderer(sheet_name, fields)
|
33
|
-
|
31
|
+
sheet = document.sheet(sheet_name)
|
32
|
+
row_renderer = row_renderers.fetch(sheet_name.to_s)
|
33
|
+
|
34
|
+
return row_renderer unless sheet.auto?
|
35
|
+
|
36
|
+
dynamic_row_renderer(fields).merge(row_renderer)
|
34
37
|
end
|
35
38
|
|
36
39
|
private
|
37
40
|
|
38
41
|
attr_reader :row_renderers
|
39
42
|
|
40
|
-
def
|
41
|
-
fields
|
42
|
-
|
43
|
+
def fields_to_columns(fields)
|
44
|
+
fields = (fields || []).map { |f| { header: f.to_s } }
|
45
|
+
Modeling::Column.array(fields)
|
46
|
+
end
|
43
47
|
|
44
|
-
|
48
|
+
def dynamic_row_renderer(fields)
|
49
|
+
Row.new(fields_to_columns(fields), resolver: resolver)
|
45
50
|
end
|
46
51
|
end
|
47
52
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'util/pivotable'
|
11
|
+
require_relative 'util/uniqueness'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Portable
|
11
|
+
module Util # :nodoc: all
|
12
|
+
# Mixes in helpers for asserting uniqueness across collections
|
13
|
+
module Pivotable
|
14
|
+
def pivot_by_name(array)
|
15
|
+
array.each_with_object({}) { |object, memo| memo[object.name] = object }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Portable
|
11
|
+
module Util # :nodoc: all
|
12
|
+
# Mixes in helpers for asserting uniqueness across collections
|
13
|
+
module Uniqueness
|
14
|
+
class DuplicateNameError < StandardError; end
|
15
|
+
|
16
|
+
def assert_no_duplicate_names(array)
|
17
|
+
names = array.map { |a| a.name.downcase }
|
18
|
+
|
19
|
+
return if names.uniq.length == array.length
|
20
|
+
|
21
|
+
raise DuplicateNameError, "cannot contain duplicate names: #{names}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/portable/version.rb
CHANGED
data/lib/portable/writers/csv.rb
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
require_relative 'base'
|
11
|
+
require_relative 'result'
|
11
12
|
|
12
13
|
module Portable
|
13
14
|
module Writers
|
@@ -18,16 +19,18 @@ module Portable
|
|
18
19
|
|
19
20
|
ensure_directory_exists(filename)
|
20
21
|
|
21
|
-
sheet_filenames = extrapolate_filenames(filename)
|
22
|
+
sheet_filenames = extrapolate_filenames(filename, document.sheets.length)
|
22
23
|
|
23
|
-
document.sheets.
|
24
|
-
data_source = data_provider.data_source(sheet.
|
25
|
-
sheet_filename = sheet_filenames[
|
24
|
+
document.sheets.map.with_index do |sheet, index|
|
25
|
+
data_source = data_provider.data_source(sheet.data_source_name)
|
26
|
+
sheet_filename = sheet_filenames[index]
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
time_in_seconds = Benchmark.measure do
|
29
|
+
write_sheet(sheet_filename, sheet, data_source, time)
|
30
|
+
end.real
|
29
31
|
|
30
|
-
|
32
|
+
Result.new(sheet_filename, time_in_seconds)
|
33
|
+
end
|
31
34
|
end
|
32
35
|
|
33
36
|
private
|
@@ -54,7 +57,7 @@ module Portable
|
|
54
57
|
csv << row_renderer.headers if sheet.include_headers?
|
55
58
|
|
56
59
|
data_source.data_rows.each do |row|
|
57
|
-
csv << row_renderer.render(row, time)
|
60
|
+
csv << row_renderer.render(row, time)
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
@@ -64,27 +67,17 @@ module Portable
|
|
64
67
|
sheet.footer_rows.each { |row| csv << row }
|
65
68
|
end
|
66
69
|
|
67
|
-
def
|
68
|
-
path = File.dirname(filename)
|
69
|
-
|
70
|
-
FileUtils.mkdir_p(path) unless File.exist?(path)
|
71
|
-
end
|
72
|
-
|
73
|
-
def extrapolate_filenames(filename)
|
74
|
-
index = 0
|
70
|
+
def extrapolate_filenames(filename, count)
|
75
71
|
dir = File.dirname(filename)
|
76
72
|
ext = File.extname(filename)
|
77
73
|
basename = File.basename(filename, ext)
|
78
74
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
index += 1
|
75
|
+
(0..count).map do |index|
|
76
|
+
if index.positive?
|
77
|
+
File.join(dir, "#{basename}.#{index}#{ext}")
|
78
|
+
else
|
79
|
+
filename
|
80
|
+
end
|
88
81
|
end
|
89
82
|
end
|
90
83
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Portable
|
11
|
+
module Writers
|
12
|
+
# Result return object from a Writer#write! call.
|
13
|
+
class Result
|
14
|
+
attr_reader :filename, :time_in_seconds
|
15
|
+
|
16
|
+
def initialize(filename, time_in_seconds)
|
17
|
+
@filename = filename
|
18
|
+
@time_in_seconds = time_in_seconds
|
19
|
+
|
20
|
+
freeze
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: portable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.pre.alpha.
|
4
|
+
version: 1.0.0.pre.alpha.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Ruggio
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: acts_as_hashable
|
@@ -183,12 +183,17 @@ files:
|
|
183
183
|
- lib/portable/modeling/options.rb
|
184
184
|
- lib/portable/modeling/sheet.rb
|
185
185
|
- lib/portable/rendering.rb
|
186
|
+
- lib/portable/rendering/column.rb
|
186
187
|
- lib/portable/rendering/row.rb
|
187
188
|
- lib/portable/rendering/sheet.rb
|
189
|
+
- lib/portable/util.rb
|
190
|
+
- lib/portable/util/pivotable.rb
|
191
|
+
- lib/portable/util/uniqueness.rb
|
188
192
|
- lib/portable/version.rb
|
189
193
|
- lib/portable/writers.rb
|
190
194
|
- lib/portable/writers/base.rb
|
191
195
|
- lib/portable/writers/csv.rb
|
196
|
+
- lib/portable/writers/result.rb
|
192
197
|
- portable.gemspec
|
193
198
|
homepage: https://github.com/bluemarblepayroll/portable
|
194
199
|
licenses:
|