file_composer 1.0.0.pre.alpha → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b20f3a6535e696278e0963870386a581303c62d227ad900dc434d6b5c48ba628
4
- data.tar.gz: 39ed8e5cc2a575991931bbe525955af31081e82db860dc2b0e69b14d162c5d8c
3
+ metadata.gz: ad520dd24873c117d6f7e9ab5ef900ed90b01b3292cb196ce844f06d2902dae2
4
+ data.tar.gz: fde077efef63fd9b162015a9c1d8283aac7658a7d38b53a43cc0a64d5fa6498c
5
5
  SHA512:
6
- metadata.gz: 66ef27c3b5f2c608d1ba988e095287caae3c1b3ef563d7129088bddeb01d00010943d5ed59708b2f20572fc2cc1c8b1e1a84fea11040b4573cdb73f56868ada5
7
- data.tar.gz: ee35c5159e6020032754a4cd7067a930da1d2c7a4aa59e806c4837ffa0847206e904fa5e18b91af39027fc42f924ebdb05d989cd63e0dce245d90440323782d7
6
+ metadata.gz: bfa28568e0235ca9b78000c1ee8ebb300242bf490d57ae437360c9aecba8a4671588fe3e86938e675cd1940cb09084adea6fc80a6d5e4b895eece6f755cc24b1
7
+ data.tar.gz: fdcf7bacac07f821b2f62709551ea205680d1e59e8284e0c8431f69ef688659356a69975a5c0ce608081fb2e1438b80c0bf4362b57b71de095d694e2631eecc7
@@ -1,3 +1,3 @@
1
- # 1.0.0 (TBD)
1
+ # 1.0.0 (September 16th, 2020)
2
2
 
3
3
  Initial Release
data/README.md CHANGED
@@ -2,25 +2,142 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/file_composer.svg)](https://badge.fury.io/rb/file_composer) [![Build Status](https://travis-ci.org/bluemarblepayroll/file_composer.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/file_composer) [![Maintainability](https://api.codeclimate.com/v1/badges/5360d687b0e93a4c7cf5/maintainability)](https://codeclimate.com/github/bluemarblepayroll/file_composer/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/5360d687b0e93a4c7cf5/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/file_composer/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- TBD
5
+ The library can serve as a foundation for creating a composable and declarative file creation API. Out of the box, it only provides for the creation of text files and zip archives. Within zip archives you can compose N number of nested zip archives and text files. It is designed to first write to disk, then move the written files elsewhere, give then store passed in. This library is intended to be extended by registering your own document types and then using File Composer as a higher-order configuration layer.
6
6
 
7
7
  ## Installation
8
8
 
9
9
  To install through Rubygems:
10
10
 
11
- ````
12
- gem install install file_composer
11
+ ````bash
12
+ gem install file_composer
13
13
  ````
14
14
 
15
15
  You can also add this to your Gemfile:
16
16
 
17
- ````
17
+ ````bash
18
18
  bundle add file_composer
19
19
  ````
20
20
 
21
21
  ## Examples
22
22
 
23
- TBD
23
+ ### Writing Text Files
24
+
25
+ The simplest way to start would be to write a couple text files. We declare this as:
26
+
27
+ ````ruby
28
+ config = {
29
+ documents: [
30
+ {
31
+ type: :text,
32
+ filename: 'hello.txt',
33
+ data: 'hello world!'
34
+ },
35
+ {
36
+ type: :text,
37
+ filename: 'hello2.txt',
38
+ data: 'hello world again!'
39
+ }
40
+ ]
41
+ }
42
+ ````
43
+
44
+ And then execute it:
45
+
46
+ ````ruby
47
+ blueprint = FileComposer::Blueprint.make(config)
48
+
49
+ results = blueprint.write!
50
+ ````
51
+
52
+ This would generate two files within the current relative path.
53
+
54
+ ### Configuring the Temporary Store
55
+
56
+ In the example above, we did not specify a temporary directory to write the file to. We can do this simply by passing in a path as the first argument to `Blueprint#write!`:
57
+
58
+ ````ruby
59
+ temp_path = File.join('tmp', 'file_composer')
60
+ blueprint = FileComposer::Blueprint.make(config)
61
+
62
+ results = blueprint.write!(temp_path)
63
+ ````
64
+
65
+ The two files should now be located in `tmp/file_composer` within the relative path.
66
+
67
+ ### Writing to Permanent Storage
68
+
69
+ The second argument for `Blueprint#write!` can be a Ruby object instance that responds to `move!(local_filename)` and returns the permanent location. By default this library only ships with two stores:
70
+
71
+ * **FileComposer::Stores::Null**: perform no move and return the temporary file path.
72
+ * **FileComposer::Stores::Local**: perform a file move and return the new file path. The file path will also be sharded using the inputted date (defaults to the current date UTC). The sharding helps ensure a more even distribution of files so one directory does not end up with all the files.
73
+
74
+ An example using a local store would be:
75
+
76
+ ````ruby
77
+ root = 'storage'
78
+ store = FileComposer::Stores::Local.new(root: root)
79
+ temp_path = File.join('tmp', 'file_composer')
80
+ blueprint = FileComposer::Blueprint.make(config)
81
+
82
+ results = blueprint.write!(temp_path, store)
83
+ ````
84
+
85
+ This will now produce two files within a `storage/YYYY/MM/DD` directory in the relative path. These files were initially written within the temporary store, but then moved after completion.
86
+
87
+ ### Custom Stores
88
+
89
+ A store can be any Ruby object instance that responds to `move!(local_filename)` and returns the permanent location. For example, you may choose to implement a custom one that moves files to cloud-based storage (i.e. Amazon S3).
90
+
91
+ ### Writing Zip Archives
92
+
93
+ Say we wanted to also include a zip archive of some other files, we could update our configuration from above to:
94
+
95
+ ````ruby
96
+ config = {
97
+ documents: [
98
+ {
99
+ type: :text,
100
+ filename: 'hello.txt',
101
+ data: 'hello world!'
102
+ },
103
+ {
104
+ type: :text,
105
+ filename: 'hello2.txt',
106
+ data: 'hello world again!'
107
+ },
108
+ {
109
+ type: :zip,
110
+ filename: 'hello3.zip',
111
+ blueprint: {
112
+ documents: [
113
+ {
114
+ type: :text,
115
+ filename: 'hello4.txt',
116
+ data: 'hello world again... again!'
117
+ }
118
+ ]
119
+ }
120
+ }
121
+ ]
122
+ }
123
+ ````
124
+
125
+ And then execute it:
126
+
127
+ ````ruby
128
+ blueprint = FileComposer::Blueprint.make(config)
129
+
130
+ results = blueprint.write!
131
+ ````
132
+
133
+ This would now generate three files within the current relative path. Some notes:
134
+
135
+ * The zip archive document type allows for a fully nested hierarchy, where you can have zip archives within zip archives.
136
+ * Files within the same level need to have unique, case-insensitive filenames or else an error will be raised.
137
+
138
+ ### Plugging in Custom Documents
139
+
140
+ The documents, text files and zip archives, are meant to serve as 'core' documents. Consumer applications will not find much value in these types by themselves. The class `FileComposer::Documents` is a factory that is able to have document types plugged in using the [acts_as_hashable_factory](https://github.com/bluemarblepayroll/acts_as_hashable) API.
24
141
 
25
142
  ## Contributing
26
143
 
@@ -5,10 +5,10 @@ require './lib/file_composer/version'
5
5
  Gem::Specification.new do |s|
6
6
  s.name = 'file_composer'
7
7
  s.version = FileComposer::VERSION
8
- s.summary = 'High-level, pluggable and YAML-based API for creating files'
8
+ s.summary = 'High-level, pluggable, and declarative API for creating files'
9
9
 
10
10
  s.description = <<-DESCRIPTION
11
- This library provides a YAML-based configuration, called a Blueprint, that allows you to specify text files and zip files and it will create them. It is pluggable so other document types and storage mediums can be added.
11
+ This library provides a declarative API, called a Blueprint, that allows you to specify text files and zip files and it will create them. It is pluggable so other document types and storage mediums can be added.
12
12
  DESCRIPTION
13
13
 
14
14
  s.authors = ['Matthew Ruggio']
@@ -35,7 +35,7 @@ Gem::Specification.new do |s|
35
35
  s.add_development_dependency('pry', '~>0')
36
36
  s.add_development_dependency('rake', '~> 13')
37
37
  s.add_development_dependency('rspec', '~> 3.8')
38
- s.add_development_dependency('rubocop', '~>0.88.0')
38
+ s.add_development_dependency('rubocop', '~>0.90.0')
39
39
  s.add_development_dependency('simplecov', '~>0.18.5')
40
40
  s.add_development_dependency('simplecov-console', '~>0.7.0')
41
41
  end
@@ -19,10 +19,10 @@ module FileComposer
19
19
 
20
20
  def initialize(documents: [])
21
21
  @documents = Documents.array(documents)
22
+ filenames = @documents.map { |a| a.filename.downcase }
23
+ not_unique = filenames.uniq.length != @documents.length
22
24
 
23
- filenames = @documents.map { |a| a.filename.downcase }
24
-
25
- raise ArgumentError, "not unique: #{filenames}" if filenames.uniq.length != @documents.length
25
+ raise ArgumentError, "filenames not unique: #{filenames}" if not_unique
26
26
  end
27
27
 
28
28
  def write!(temp_root = '', store = Stores::Null.new)
@@ -11,7 +11,11 @@ require_relative 'documents/text'
11
11
  require_relative 'documents/zip'
12
12
 
13
13
  module FileComposer
14
- # Factory for building documents.
14
+ # Factory for building documents. To register new document types:
15
+ # - Implement a subclass for FileComposer::Documents::Base or a document compliant class.
16
+ # The only real constraint is that it is hashable using the acts_as_hashable class method
17
+ # and implements #write!(temp_root = '', store = Stores::Null.new)
18
+ # - Call FileComposer::Documents#register(name, class_constant_or_name)
15
19
  class Documents
16
20
  acts_as_hashable_factory
17
21
 
@@ -9,7 +9,9 @@
9
9
 
10
10
  module FileComposer
11
11
  class Documents
12
- # The production of a document.
12
+ # Returns the result of a FileComposer::Document#write! call. Each #write! call can produce
13
+ # N number of documents and each document will be represented in this instance's file_results
14
+ # attribute.
13
15
  class Result
14
16
  attr_reader :file_results,
15
17
  :time_in_seconds
@@ -23,8 +23,14 @@ module FileComposer
23
23
 
24
24
  def write!(temp_root = '', store = Stores::Null.new)
25
25
  results = blueprint.write!(temp_root)
26
- total_time_in_seconds = results.sum(&:time_in_seconds)
27
- physical_filename = zip!(temp_root, results)
26
+ write_time_in_seconds = results.sum(&:time_in_seconds)
27
+ physical_filename = nil
28
+
29
+ zip_time_in_seconds = Benchmark.measure do
30
+ physical_filename = zip!(temp_root, results)
31
+ end.real
32
+
33
+ total_time_in_seconds = write_time_in_seconds + zip_time_in_seconds
28
34
 
29
35
  cleanup(results)
30
36
 
@@ -9,7 +9,17 @@
9
9
 
10
10
  module FileComposer
11
11
  module Stores
12
- # File copier from local file system to the local file system.
12
+ # File copier from local file system to the local file system but with a sharded path
13
+ # with YYYY/MM/DD using the passed in date. The date will default
14
+ # to the current date in UTC unless specified otherwise. The filename passed in will be
15
+ # used to determine the extension but the destination will create a new GUID to use as
16
+ # the filename. For example:
17
+ # - input: some/path/input.txt
18
+ # - output: 2020/06/25/82d042eb-9592-47f1-8019-2f06f73dc053.txt
19
+ # The reason it returns a completely random name is to ensure the file truly has a unique
20
+ # place to live, independent of what was passed in. You are free to change these assumptions
21
+ # by creating and using your own store if any of these implementation details do not fit
22
+ # your specific use case.
13
23
  class Local
14
24
  attr_reader :date, :root
15
25
 
@@ -23,8 +33,7 @@ module FileComposer
23
33
  def move!(filename)
24
34
  make_final_filename(filename).tap do |final_filename|
25
35
  ensure_directory_exists(final_filename)
26
- FileUtils.cp(filename, final_filename)
27
- FileUtils.rm(filename, force: true)
36
+ FileUtils.mv(filename, final_filename)
28
37
  end
29
38
  end
30
39
 
@@ -38,17 +47,12 @@ module FileComposer
38
47
 
39
48
  def random_filename_parts(extension)
40
49
  [
50
+ root,
41
51
  date.year.to_s,
42
52
  date.month.to_s,
43
53
  date.day.to_s,
44
54
  "#{SecureRandom.uuid}#{extension}"
45
- ].tap do |parts|
46
- parts.unshift(root) if root?
47
- end
48
- end
49
-
50
- def root?
51
- !root.empty?
55
+ ].compact
52
56
  end
53
57
 
54
58
  def ensure_directory_exists(filename)
@@ -8,5 +8,5 @@
8
8
  #
9
9
 
10
10
  module FileComposer
11
- VERSION = '1.0.0-alpha'
11
+ VERSION = '1.0.0'
12
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file_composer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.alpha
4
+ version: 1.0.0
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-09-01 00:00:00.000000000 Z
11
+ date: 2020-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acts_as_hashable
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.88.0
103
+ version: 0.90.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 0.88.0
110
+ version: 0.90.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: simplecov
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -136,9 +136,9 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: 0.7.0
139
- description: " This library provides a YAML-based configuration, called a Blueprint,
140
- that allows you to specify text files and zip files and it will create them. It
141
- is pluggable so other document types and storage mediums can be added.\n"
139
+ description: " This library provides a declarative API, called a Blueprint, that
140
+ allows you to specify text files and zip files and it will create them. It is pluggable
141
+ so other document types and storage mediums can be added.\n"
142
142
  email:
143
143
  - mruggio@bluemarblepayroll.com
144
144
  executables: []
@@ -192,12 +192,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
192
192
  version: '2.5'
193
193
  required_rubygems_version: !ruby/object:Gem::Requirement
194
194
  requirements:
195
- - - ">"
195
+ - - ">="
196
196
  - !ruby/object:Gem::Version
197
- version: 1.3.1
197
+ version: '0'
198
198
  requirements: []
199
199
  rubygems_version: 3.0.3
200
200
  signing_key:
201
201
  specification_version: 4
202
- summary: High-level, pluggable and YAML-based API for creating files
202
+ summary: High-level, pluggable, and declarative API for creating files
203
203
  test_files: []