file-replicator 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +139 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/rjoin +5 -0
- data/exe/rsplit +5 -0
- data/file-split-join-binary.gemspec +32 -0
- data/lib/file_replicator/abstract_cmd_parse.rb +52 -0
- data/lib/file_replicator/checksum.rb +49 -0
- data/lib/file_replicator/exceptions.rb +7 -0
- data/lib/file_replicator/joiner.rb +56 -0
- data/lib/file_replicator/joiner_cmd_parse.rb +84 -0
- data/lib/file_replicator/replicator_helper.rb +29 -0
- data/lib/file_replicator/splitter.rb +152 -0
- data/lib/file_replicator/splitter_cmd_parse.rb +175 -0
- data/lib/file_replicator/version.rb +3 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2821c812b1a84db10f90aff6be2cef29ee814f50
|
4
|
+
data.tar.gz: 4a68856122d98a88117626ad646aa1cd1aba601a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f5b3071ba86f0a4673680899a5dcbad7153b311928aa657603797a261c7580b71065b69e93b545a105e8a1547b6693cb022ce06b1932ee5720f519ffd76e6022
|
7
|
+
data.tar.gz: 91320af8216bade935f9fce93ad2cb5e49ae35520b700e4201ca1379cec1f0e1faf66fe3871f7ab960dd602bbd5e544a428d3c375862d90f3402478567a9ae6c
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at vad.viktor@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Viktor (Icon) VAD
|
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,139 @@
|
|
1
|
+
# File replicator, a split-join command line tool
|
2
|
+
|
3
|
+
This Gem provides you with two command line tools, one to split up files and one to join them back togather again.
|
4
|
+
Splitting operation is binary only, making it mostly suitable for files like huge images, videos, archive formats, etc. Since it is binary it should not have any problems splitting and then joining text files, just mind that it can break reading part files as they may loose line ending info until they are joined.
|
5
|
+
|
6
|
+
## Features
|
7
|
+
|
8
|
+
- progress bar per file
|
9
|
+
- checksum algorithms: md5, sha1, sha224, sha256, sha318, sha512
|
10
|
+
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
gem install file-replicator
|
15
|
+
|
16
|
+
If you are using [rbenv](https://github.com/rbenv/rbenv), don't forget to `rehash` to pick up new executables:
|
17
|
+
|
18
|
+
rbenv rehash
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Splitting files
|
23
|
+
|
24
|
+
To split files use the `rsplit` command. When it is used without arguments it will print it's parameter list:
|
25
|
+
|
26
|
+
```
|
27
|
+
$ rsplit
|
28
|
+
usage: rsplit [options]
|
29
|
+
-f, --files Files to split, Ruby's Dir.glob style
|
30
|
+
-o, --output-dir Destination directory, (default: current directory)
|
31
|
+
-p, --pattern Output file pattern, details in the --readme (default: {orf}.{onu})
|
32
|
+
(...truncated...)
|
33
|
+
```
|
34
|
+
|
35
|
+
There is a built in, very valuable `manual` with the `--readme` parameter:
|
36
|
+
|
37
|
+
$ rsplit --readme
|
38
|
+
|
39
|
+
#### Examples
|
40
|
+
|
41
|
+
* For the examples below take the following structure:
|
42
|
+
|
43
|
+
```
|
44
|
+
$ tree -h /mnt
|
45
|
+
/mnt/
|
46
|
+
├── [ 0] rugby
|
47
|
+
│ └── [1.1G] match.mkv
|
48
|
+
├── [ 43M] tv1.mkv
|
49
|
+
├── [ 94M] tv2.mkv
|
50
|
+
└── [103M] tv3.mkv
|
51
|
+
```
|
52
|
+
|
53
|
+
* Split one file into 3 parts and store them in `/opt`:
|
54
|
+
|
55
|
+
```
|
56
|
+
$ rsplit -f /mnt/rugby/match.mkv -o /opt -e 3
|
57
|
+
```
|
58
|
+
|
59
|
+
* To split more files you can use [Ruby's Dir.glob patterns](https://ruby-doc.org/core-2.3.0/Dir.html#method-c-glob):
|
60
|
+
```
|
61
|
+
$ rsplit -f "/mnt/tv*.mkv"
|
62
|
+
```
|
63
|
+
|
64
|
+
will match tv1.mvk, tv2.mkv and tv3.mkv too.
|
65
|
+
|
66
|
+
**NOTE**: to avoid shells' path pattern translation you have to put the pattern in between quotes.
|
67
|
+
|
68
|
+
* Whenever you wish to split many files with mixed sizes, where there are a few files just too small you wish not to process you can use the `--min-size` option.
|
69
|
+
|
70
|
+
So let's exclude any file that is just smaller than 50 megabytes:
|
71
|
+
|
72
|
+
```
|
73
|
+
$ rsplit rsplit -f "/mnt/tv*.mkv" -m 50m
|
74
|
+
```
|
75
|
+
|
76
|
+
will match tv2.mkv and tv3.mkv and NOT tv1.mkv which is only 43M small.
|
77
|
+
|
78
|
+
* Apart from controlling the destination folder you may not want to rename the source file(s) just to get the expected output chunk files. This is where the output file name `--pattern` option come in play.
|
79
|
+
|
80
|
+
Use these building blocks to extract, reuse and add parts to construct output filenames.
|
81
|
+
The patterns are case insensitive and the surrounding curly braces are part of them (see example).
|
82
|
+
|
83
|
+
{ord} - Original, absolute directory
|
84
|
+
{orf} - Original filename, with extension
|
85
|
+
{ore} - File's (last) extension with the lead dot: .jpg
|
86
|
+
{orb} - File's name without it's extension
|
87
|
+
{num} - Incremental numbers starting at 1: [1, 2, 3, ...]
|
88
|
+
{onu} - Incremental numbers starting at 1 and padded with zeros: [01, 02, ... 10, 11]
|
89
|
+
{gcn} - Incremental numbers starting at 1: [1, 2, 3, ...], used in multi file scenario
|
90
|
+
|
91
|
+
E.g. you want to rename every file to a fixed name with a zero prefixed number prefix:
|
92
|
+
|
93
|
+
```
|
94
|
+
$ rsplit -f "/mnt/**/*.mkv" -p {onu}_tv-match-{gcn}.mkv
|
95
|
+
```
|
96
|
+
|
97
|
+
will name the split files like:
|
98
|
+
|
99
|
+
```
|
100
|
+
1_tv-match-1.mkv
|
101
|
+
2_tv-match-1.mkv
|
102
|
+
1_tv-match-2.mkv
|
103
|
+
2_tv-match-2.mkv
|
104
|
+
```
|
105
|
+
|
106
|
+
**NOTE**:
|
107
|
+
- You MUST use `{gcn}` in your file name as it will count the main files, so you will still be able to distinguish files.
|
108
|
+
- Files, when gathered according the Dir.glob pattern will be then sorted by the default sort algorithm for Ruby Arrays. That is going to affect how the files are going to be numbered by `{gcn}`.
|
109
|
+
|
110
|
+
### Joinging files
|
111
|
+
|
112
|
+
Joining file chunks is a more basic operation with less options.
|
113
|
+
Basically the supplied `rjoin` command is really just a little bit boosted linux `cat`.
|
114
|
+
|
115
|
+
YOu are able to combine a list of files in alphabetical order (as far as Ruby's Array#sort goes) by just defining the first and last file, and rjoin will get the file in between:
|
116
|
+
|
117
|
+
$ rjoin -f file.bin.001 -l file.bin.125 -o file.bin
|
118
|
+
|
119
|
+
will join the 125 chunks into file.bin. Work pretty good because the numbering is zero prefixed and alphabetical sorting will do it's job as you expect.
|
120
|
+
|
121
|
+
## Todo list
|
122
|
+
|
123
|
+
- [ ] Write a nice `--readme` for `rjoin`
|
124
|
+
- [ ] Do checksum verification upon joining
|
125
|
+
- [ ] Figure out more tests
|
126
|
+
|
127
|
+
## Contributing
|
128
|
+
|
129
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/vadviktor/whatever. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](CODE_OF_CONDUCT.md) code of conduct.
|
130
|
+
|
131
|
+
1. Fork it (https://github.com/vadviktor/file-replicator/fork)
|
132
|
+
2. Create your feature branch (git checkout -b my-new-feature)
|
133
|
+
3. Commit your changes (git commit -am 'Add some feature')
|
134
|
+
4. Push to the branch (git push origin my-new-feature)
|
135
|
+
5. Create a new Pull Request
|
136
|
+
|
137
|
+
## License
|
138
|
+
|
139
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "file/split/join/binary"
|
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
|
data/bin/setup
ADDED
data/exe/rjoin
ADDED
data/exe/rsplit
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'file_replicator/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'file-replicator'
|
8
|
+
spec.version = FileReplicator::VERSION
|
9
|
+
spec.authors = ['Viktor (Icon) VAD']
|
10
|
+
spec.email = ['vad.viktor@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = %q{Split and join files in binary mode}
|
13
|
+
spec.description = %q{Command line utility that can split files into chunks, join them together. All is done in binary mode making it encoding independent.}
|
14
|
+
spec.homepage = 'https://github.com/vadviktor/file-replicator'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = 'exe'
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_dependency 'slop', '4.4.1'
|
25
|
+
spec.add_dependency 'pastel', '0.6.1'
|
26
|
+
spec.add_dependency 'ruby-progressbar', '1.8.1'
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '1.13.6'
|
29
|
+
spec.add_development_dependency 'rake', '11.3.0'
|
30
|
+
spec.add_development_dependency 'minitest', '5.9.1'
|
31
|
+
spec.add_development_dependency 'minitest-reporters', '1.1.12'
|
32
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
require 'slop'
|
3
|
+
|
4
|
+
require_relative 'checksum'
|
5
|
+
require_relative 'exceptions'
|
6
|
+
require_relative 'version'
|
7
|
+
|
8
|
+
module FileReplicator
|
9
|
+
class AbstractCmdParse
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@colour = Pastel.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_options
|
16
|
+
options = parse_argv
|
17
|
+
|
18
|
+
# display parameter list to let user know how to use them
|
19
|
+
if ARGV.empty?
|
20
|
+
puts options
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
validate options
|
25
|
+
|
26
|
+
options
|
27
|
+
rescue MissingArgumentException,
|
28
|
+
ArgumentError,
|
29
|
+
Slop::MissingArgument,
|
30
|
+
Slop::UnknownOption => e
|
31
|
+
puts @colour.bright_red e.message
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def header(txt)
|
36
|
+
@colour.yellow.bold.underline txt
|
37
|
+
end
|
38
|
+
|
39
|
+
def highlight(txt)
|
40
|
+
@colour.green.bold txt
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_argv
|
44
|
+
raise 'Implementation missing'
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate(options)
|
48
|
+
raise 'Implementation missing'
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module FileReplicator
|
4
|
+
class Checksum
|
5
|
+
|
6
|
+
SUPPORTED = [:md5, :sha1, :sha224, :sha256, :sha384, :sha512]
|
7
|
+
|
8
|
+
def initialize(alg)
|
9
|
+
@alg = alg
|
10
|
+
@file_digest = Digest.const_get(alg.upcase).new
|
11
|
+
@chunk_digest = Digest.const_get(alg.upcase).new
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_chunk(data)
|
15
|
+
@chunk_digest << data
|
16
|
+
@file_digest << data
|
17
|
+
end
|
18
|
+
|
19
|
+
def start_new_file(file_path)
|
20
|
+
@file_path = file_path
|
21
|
+
@file_digest.reset
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_new_chunk(chunk_file_name)
|
25
|
+
@chunk_file_name = File.basename(chunk_file_name)
|
26
|
+
@chunk_digest.reset
|
27
|
+
end
|
28
|
+
|
29
|
+
def append_chunk_checksum
|
30
|
+
append_to_checksum_file @chunk_digest.hexdigest, @chunk_file_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def append_file_checksum
|
34
|
+
append_to_checksum_file @file_digest.hexdigest, @file_path
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def append_to_checksum_file(hexdigest, checksummed_file_path)
|
40
|
+
raise 'No checksum file path is defined' if @file_path.nil?
|
41
|
+
|
42
|
+
chk_filename = "#{@file_path}.#{@alg.downcase}"
|
43
|
+
File.open chk_filename, 'a' do |f|
|
44
|
+
f.write "#{hexdigest} #{File.basename(checksummed_file_path)}\n"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
require 'ruby-progressbar'
|
3
|
+
|
4
|
+
require_relative 'joiner_cmd_parse'
|
5
|
+
require_relative 'replicator_helper'
|
6
|
+
|
7
|
+
module FileReplicator
|
8
|
+
class Joiner
|
9
|
+
include ReplicatorHelper
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@options = JoinerCmdParse.new.get_options
|
13
|
+
@colour = Pastel.new enabled: !@options[:no_colour]
|
14
|
+
end
|
15
|
+
|
16
|
+
def join
|
17
|
+
files = Dir.glob(
|
18
|
+
File.join(
|
19
|
+
File.dirname(@options[:first]),
|
20
|
+
'*'
|
21
|
+
)
|
22
|
+
).sort
|
23
|
+
|
24
|
+
prepare @options[:output_path]
|
25
|
+
output_file = File.open @options[:output_path], 'ab'
|
26
|
+
|
27
|
+
first_index = files.index File.expand_path(@options[:first])
|
28
|
+
last_index = files.index File.expand_path(@options[:last])
|
29
|
+
|
30
|
+
if progress?
|
31
|
+
file_pb = ProgressBar.create(
|
32
|
+
total: (first_index..last_index).size,
|
33
|
+
title: colour.bright_blue("#{File.basename output_file}")
|
34
|
+
)
|
35
|
+
end
|
36
|
+
files[first_index..last_index].each do |file|
|
37
|
+
File.open file, 'rb' do |f|
|
38
|
+
while (data = f.read(READ_BUFFER))
|
39
|
+
output_file.write data
|
40
|
+
end
|
41
|
+
end
|
42
|
+
file_pb.increment if progress?
|
43
|
+
end
|
44
|
+
|
45
|
+
file_pb.finish if progress?
|
46
|
+
rescue StandardError => e
|
47
|
+
puts @colour.bright_red e.message unless quiet?
|
48
|
+
file_pb.stop if progress?
|
49
|
+
|
50
|
+
exit 1
|
51
|
+
ensure
|
52
|
+
output_file.close unless output_file.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require_relative 'abstract_cmd_parse'
|
2
|
+
|
3
|
+
module FileReplicator
|
4
|
+
class JoinerCmdParse < AbstractCmdParse
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
# Parse ARGV
|
9
|
+
# @return Slop options
|
10
|
+
def parse_argv
|
11
|
+
Slop.parse do |o|
|
12
|
+
o.string '-f', '--first', 'First file of the list to combine'
|
13
|
+
o.string '-l', '--last', 'Last file of the list to combine'
|
14
|
+
o.string '-o', '--output-path', 'Destination file path'
|
15
|
+
o.bool '--no-progress', 'Disable progressbar'
|
16
|
+
o.bool '--no-colour', 'Disable colours'
|
17
|
+
o.bool '--quiet', 'Suppress output'
|
18
|
+
|
19
|
+
o.on('--readme', 'Detailed description of some of the parameters and exit') {
|
20
|
+
puts readme
|
21
|
+
exit
|
22
|
+
}
|
23
|
+
|
24
|
+
o.on('--version', 'Display version information and exit') {
|
25
|
+
puts VERSION
|
26
|
+
exit
|
27
|
+
}
|
28
|
+
|
29
|
+
o.on('-h', '--help', 'Display this message') {
|
30
|
+
puts o
|
31
|
+
exit
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate(options)
|
37
|
+
validate_first_file options
|
38
|
+
validate_last_file options
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate_first_file(options)
|
42
|
+
unless options.first?
|
43
|
+
msg = 'Missing first input file (-f)'
|
44
|
+
raise MissingArgumentException.new msg
|
45
|
+
end
|
46
|
+
|
47
|
+
unless File.exist? options[:first]
|
48
|
+
msg = "#{options[:first]} does not exist (-f)"
|
49
|
+
raise ArgumentError.new msg
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_last_file(options)
|
54
|
+
unless options.last?
|
55
|
+
msg = 'Missing last input file (-l)'
|
56
|
+
raise MissingArgumentException.new msg
|
57
|
+
end
|
58
|
+
|
59
|
+
unless File.exist? options[:last]
|
60
|
+
msg = "#{options[:last]} does not exist (-l)"
|
61
|
+
raise ArgumentError.new msg
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Validates the existence of a directory
|
66
|
+
# @param [Slop] options
|
67
|
+
# @raise ArgumentError
|
68
|
+
def validate_directory(options)
|
69
|
+
unless options.output_path?
|
70
|
+
msg = 'Missing output path (-o)'
|
71
|
+
raise MissingArgumentException.new msg
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def readme
|
76
|
+
<<-TXT
|
77
|
+
README
|
78
|
+
TXT
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module FileReplicator
|
4
|
+
module ReplicatorHelper
|
5
|
+
READ_BUFFER = 256 * 1024
|
6
|
+
|
7
|
+
attr_reader :options, :colour, :checksum
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def prepare(file_path)
|
12
|
+
FileUtils.mkdir_p File.dirname(file_path)
|
13
|
+
FileUtils.rm_f file_path
|
14
|
+
FileUtils.touch file_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def quiet?
|
18
|
+
options.quiet?
|
19
|
+
end
|
20
|
+
|
21
|
+
def progress?
|
22
|
+
!options.no_progress? && !quiet?
|
23
|
+
end
|
24
|
+
|
25
|
+
def chksum?
|
26
|
+
!checksum.nil? && !quiet?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
require 'ruby-progressbar'
|
3
|
+
|
4
|
+
require_relative 'checksum'
|
5
|
+
require_relative 'replicator_helper'
|
6
|
+
require_relative 'splitter_cmd_parse'
|
7
|
+
|
8
|
+
module FileReplicator
|
9
|
+
class Splitter
|
10
|
+
include ReplicatorHelper
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@options = SplitterCmdParse.new.get_options
|
14
|
+
@colour = Pastel.new enabled: !@options[:no_colour]
|
15
|
+
|
16
|
+
if (alg = @options.to_h.fetch(:checksum, false))
|
17
|
+
@checksum = Checksum.new alg
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def split
|
22
|
+
file_no = 0
|
23
|
+
files = Dir.glob(File.expand_path(options[:files])).sort
|
24
|
+
files.each do |file_name|
|
25
|
+
file_no += 1
|
26
|
+
file_abs_path = File.expand_path(file_name)
|
27
|
+
file_size = File.size(file_abs_path)
|
28
|
+
|
29
|
+
next if options.min_size? and file_size < size_in_bytes(options[:min_size])
|
30
|
+
|
31
|
+
if options.size?
|
32
|
+
split_size = size_in_bytes options[:size]
|
33
|
+
number_of_elements = (file_size.to_f / split_size).ceil
|
34
|
+
elsif options.elements?
|
35
|
+
split_size = (file_size.to_f / options[:elements]).ceil
|
36
|
+
number_of_elements = options[:elements]
|
37
|
+
end
|
38
|
+
|
39
|
+
if progress?
|
40
|
+
file_pb = ProgressBar.create(
|
41
|
+
total: number_of_elements,
|
42
|
+
title: colour.bright_blue("#{File.basename file_name}")
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
@checksum.start_new_file File.join(options[:output_dir],
|
47
|
+
File.basename(file_name)) if chksum?
|
48
|
+
|
49
|
+
File.open file_abs_path, 'rb' do |f|
|
50
|
+
file_split_no = 0
|
51
|
+
|
52
|
+
until f.eof? or f.closed?
|
53
|
+
file_split_no += 1
|
54
|
+
output_file_path = File.join(
|
55
|
+
options[:output_dir],
|
56
|
+
self.class.pattern_to_path(
|
57
|
+
options[:pattern],
|
58
|
+
file_path: file_abs_path,
|
59
|
+
file_number: file_no,
|
60
|
+
chunk_number: file_split_no,
|
61
|
+
max_elements: number_of_elements
|
62
|
+
))
|
63
|
+
|
64
|
+
prepare output_file_path
|
65
|
+
|
66
|
+
bytes_written = 0
|
67
|
+
begin
|
68
|
+
# write to chunk file
|
69
|
+
@checksum.start_new_chunk output_file_path if chksum?
|
70
|
+
split_file = File.open output_file_path, 'ab'
|
71
|
+
while bytes_written <= split_size and (data = f.read(READ_BUFFER))
|
72
|
+
split_file.write data
|
73
|
+
bytes_written += READ_BUFFER
|
74
|
+
@checksum.add_chunk data if chksum?
|
75
|
+
end
|
76
|
+
rescue StandardError => e
|
77
|
+
puts colour.bright_red e.message unless quiet?
|
78
|
+
f.close
|
79
|
+
file_pb.stop if progress?
|
80
|
+
exit 1
|
81
|
+
ensure
|
82
|
+
split_file.close unless split_file.nil?
|
83
|
+
file_pb.increment if progress?
|
84
|
+
@checksum.append_chunk_checksum if chksum?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
file_pb.finish if progress?
|
90
|
+
@checksum.append_file_checksum if chksum?
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
def self.pattern_to_path(pattern, file_path: nil, chunk_number: nil,
|
99
|
+
max_elements: nil, file_number: nil)
|
100
|
+
# Patterns:
|
101
|
+
# {ord} - Original, absolute directory
|
102
|
+
# {orf} - Original filename, with extension
|
103
|
+
# {ore} - File's (last) extension with the lead dot: .jpg
|
104
|
+
# {orb} - File's name without it's extension
|
105
|
+
# {num} - Incremental numbers starting at 1: [1, 2, 3, ...]
|
106
|
+
# {onu} - Incremental numbers starting at 1 and padded with zeros: [01, 02, ... 10, 11]
|
107
|
+
# {gcn} - Incremental numbers starting at 1: [1, 2, 3, ...], used in multi file scenario
|
108
|
+
|
109
|
+
unless file_path.nil?
|
110
|
+
pattern = pattern.gsub(/\{ord\}/i, '%{ord}') % {
|
111
|
+
ord: File.dirname(file_path) }
|
112
|
+
|
113
|
+
pattern = pattern.gsub(/\{orf\}/i, '%{orf}') % {
|
114
|
+
orf: File.basename(file_path) }
|
115
|
+
|
116
|
+
pattern = pattern.gsub(/\{ore\}/i, '%{ore}') % {
|
117
|
+
ore: File.extname(file_path) }
|
118
|
+
|
119
|
+
pattern = pattern.gsub(/\{orb\}/i, '%{orb}') % {
|
120
|
+
orb: File.basename(file_path, File.extname(file_path)) }
|
121
|
+
end
|
122
|
+
|
123
|
+
pattern = pattern.gsub(/\{num\}/i, '%{num}') % {
|
124
|
+
num: chunk_number } unless chunk_number.nil?
|
125
|
+
|
126
|
+
pattern = pattern.gsub(/\{gcn\}/i, '%{gcn}') % {
|
127
|
+
gcn: file_number } unless file_number.nil?
|
128
|
+
|
129
|
+
unless chunk_number.nil? and max_elements.nil?
|
130
|
+
padded_num = chunk_number.to_s.rjust max_elements.to_s.length, '0'
|
131
|
+
pattern = pattern.gsub(/\{onu\}/i, '%{onu}') % { onu: padded_num }
|
132
|
+
end
|
133
|
+
|
134
|
+
# keep the path clean and valid
|
135
|
+
pattern.gsub "#{File::SEPARATOR}#{File::SEPARATOR}", File::SEPARATOR
|
136
|
+
end
|
137
|
+
|
138
|
+
def size_in_bytes(size_string)
|
139
|
+
case size_string[-1].downcase
|
140
|
+
when 'k'
|
141
|
+
size_string.to_i * 1024
|
142
|
+
when 'm'
|
143
|
+
size_string.to_i * 1024**2
|
144
|
+
when 'g'
|
145
|
+
size_string.to_i * 1024**3
|
146
|
+
else
|
147
|
+
size_string.to_i
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require_relative 'abstract_cmd_parse'
|
2
|
+
|
3
|
+
module FileReplicator
|
4
|
+
class SplitterCmdParse < AbstractCmdParse
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
# Parse ARGV
|
9
|
+
# @return Slop options
|
10
|
+
def parse_argv
|
11
|
+
Slop.parse do |o|
|
12
|
+
o.string '-f', '--files', "Files to split, Ruby's Dir.glob style"
|
13
|
+
o.string '-o', '--output-dir', 'Destination directory, (default: current directory)', default: '.'
|
14
|
+
o.string '-p', '--pattern', 'Output file pattern, details in the --readme (default: {orf}.{onu})', default: '{orf}.{onu}'
|
15
|
+
o.string '-s', '--size', 'Max size of split elements, details in the --readme'
|
16
|
+
o.integer '-e', '--elements', 'Number of parts to split into'
|
17
|
+
o.string '-m', '--min-size', 'Minimal size threshold of a file to consider splitting, details in the --readme'
|
18
|
+
o.string '--checksum', 'Create checksum with the specified algorithm'
|
19
|
+
o.bool '--no-progress', 'Disable progressbar'
|
20
|
+
o.bool '--no-colour', 'Disable colours'
|
21
|
+
o.bool '--quiet', 'Suppress output'
|
22
|
+
|
23
|
+
o.on('--readme', 'Detailed description of some of the parameters and exit') {
|
24
|
+
puts readme
|
25
|
+
exit
|
26
|
+
}
|
27
|
+
|
28
|
+
o.on('--version', 'Display version information and exit') {
|
29
|
+
puts VERSION
|
30
|
+
exit
|
31
|
+
}
|
32
|
+
|
33
|
+
o.on('-h', '--help', 'Display this message') {
|
34
|
+
puts o
|
35
|
+
exit
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate(options)
|
41
|
+
validate_files options
|
42
|
+
validate_directory options
|
43
|
+
validate_unknown_split_size options
|
44
|
+
validate_conflicting_split_size options
|
45
|
+
validate_size_format options
|
46
|
+
validate_minimal_size options
|
47
|
+
validate_checksum options
|
48
|
+
end
|
49
|
+
|
50
|
+
# Validates checksum algorithm
|
51
|
+
# @param [Slop] options
|
52
|
+
# @raise ArgumentError
|
53
|
+
def validate_checksum(options)
|
54
|
+
if options.checksum? and !Checksum::SUPPORTED.include?(
|
55
|
+
options[:checksum].downcase.to_sym)
|
56
|
+
msg = "#{options[:checksum]} is not a supported checksum algorithm"
|
57
|
+
raise ArgumentError.new msg
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Validates minimal size's format
|
62
|
+
# @param [Slop] options
|
63
|
+
# @raise ArgumentError
|
64
|
+
def validate_minimal_size(options)
|
65
|
+
if options.min_size? && !options[:min_size].match(/^[\d]+(k|m|g)?$/i)
|
66
|
+
msg = "#{options[:min_size]} is not an acceptable format for minimal size"
|
67
|
+
raise ArgumentError.new msg
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Validates size's format
|
72
|
+
# @param [Slop] options
|
73
|
+
# @raise ArgumentError
|
74
|
+
def validate_size_format(options)
|
75
|
+
if options.size? && !options[:size].match(/^[\d]+(k|m|g)?$/i)
|
76
|
+
msg = "#{options[:size]} is not an acceptable format for split size"
|
77
|
+
raise ArgumentError.new msg
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Check if only either size or number of elements options are used
|
82
|
+
# @param [Slop] options
|
83
|
+
# @raise ConflictingArgumentsExceptions
|
84
|
+
def validate_conflicting_split_size(options)
|
85
|
+
if options.size? and options.elements?
|
86
|
+
msg = 'Choose either size or number of elements (-s or -e)'
|
87
|
+
raise ConflictingArgumentsExceptions.new msg
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Check if size or number of elements options are set
|
92
|
+
# @param [Slop] options
|
93
|
+
# @raise MissingArgumentException
|
94
|
+
def validate_unknown_split_size(options)
|
95
|
+
unless options.size? or options.elements?
|
96
|
+
msg = 'Missing the size or number of elements to split files into (-s or -e)'
|
97
|
+
raise MissingArgumentException.new msg
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Validates the existence of files
|
102
|
+
# @param [Slop] options
|
103
|
+
# @raise MissingArgumentException
|
104
|
+
def validate_files(options)
|
105
|
+
unless options.files?
|
106
|
+
msg = 'Missing list of files to operate on (-f)'
|
107
|
+
raise MissingArgumentException.new msg
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param [Slop] options
|
112
|
+
# @raise ArgumentError
|
113
|
+
def validate_directory(options)
|
114
|
+
unless options.output_dir?
|
115
|
+
msg = 'Missing output path (-o)'
|
116
|
+
raise MissingArgumentException.new msg
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def readme
|
121
|
+
<<-TXT
|
122
|
+
|
123
|
+
#{header 'Input file pattern:'}
|
124
|
+
|
125
|
+
To define what file(s) are to be processed you can use Ruby's Dir.glob patterns (https://ruby-doc.org/core-2.3.0/Dir.html#method-c-glob).
|
126
|
+
For this, not to get shells' path translation to interfere, you must put the path pattern in between double (or single) quotes.
|
127
|
+
|
128
|
+
e.g.:
|
129
|
+
$ rsplit -f "/mnt/tv*.mkv"
|
130
|
+
will match a file like: /mnt/tv42.mkv.
|
131
|
+
|
132
|
+
#{header 'Output file patterns:'}
|
133
|
+
|
134
|
+
Use these building blocks to extract, reuse and add parts to construct output filenames.
|
135
|
+
The patterns are case insensitive and the surrounding curly braces are part of them (see example).
|
136
|
+
|
137
|
+
#{highlight '{ord}'} - Original, absolute directory
|
138
|
+
#{highlight '{orf}'} - Original filename, with extension
|
139
|
+
#{highlight '{ore}'} - File's (last) extension with the lead dot: .jpg
|
140
|
+
#{highlight '{orb}'} - File's name without it's extension
|
141
|
+
#{highlight '{num}'} - Incremental numbers starting at 1: [1, 2, 3, ...]
|
142
|
+
#{highlight '{onu}'} - Incremental numbers starting at 1 and padded with zeros: [01, 02, ... 10, 11]
|
143
|
+
#{highlight '{gcn}'} - Incremental numbers starting at 1: [1, 2, 3, ...], used in multi file scenario
|
144
|
+
|
145
|
+
|
146
|
+
#{header 'Output file sizes:'}
|
147
|
+
|
148
|
+
By default the number used as a file size is in bytes.
|
149
|
+
We can use a more human readable form to specify larger amounts:
|
150
|
+
e.g.: 256k => 256 kilobytes
|
151
|
+
|
152
|
+
Suffixes:
|
153
|
+
- #{highlight 'k'} => kilobyte
|
154
|
+
- #{highlight 'm'} => megabyte
|
155
|
+
- #{highlight 'g'} => gigabyte
|
156
|
+
|
157
|
+
#{header 'Minimal file sizes:'}
|
158
|
+
|
159
|
+
This option allows us to set a minimal size threshold for files.
|
160
|
+
Files smaller than this threshold will be skipped.
|
161
|
+
We can use a more human readable form to specify larger amounts just like for output sizes.
|
162
|
+
|
163
|
+
#{header 'Supported checksum algorithms:'}
|
164
|
+
- #{highlight 'md5'} #{colour.cyan '(considered broken)'}
|
165
|
+
- #{highlight 'sha1'} #{colour.cyan '(considered broken)'}
|
166
|
+
- #{highlight 'sha224'}
|
167
|
+
- #{highlight 'sha256'}
|
168
|
+
- #{highlight 'sha384'}
|
169
|
+
- #{highlight 'sha512'}
|
170
|
+
|
171
|
+
TXT
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: file-replicator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Viktor (Icon) VAD
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: slop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.4.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.4.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pastel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.6.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby-progressbar
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.8.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.8.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.13.6
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.13.6
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 11.3.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 11.3.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 5.9.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 5.9.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest-reporters
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.1.12
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.1.12
|
111
|
+
description: Command line utility that can split files into chunks, join them together.
|
112
|
+
All is done in binary mode making it encoding independent.
|
113
|
+
email:
|
114
|
+
- vad.viktor@gmail.com
|
115
|
+
executables:
|
116
|
+
- rjoin
|
117
|
+
- rsplit
|
118
|
+
extensions: []
|
119
|
+
extra_rdoc_files: []
|
120
|
+
files:
|
121
|
+
- ".gitignore"
|
122
|
+
- CODE_OF_CONDUCT.md
|
123
|
+
- Gemfile
|
124
|
+
- LICENSE.txt
|
125
|
+
- README.md
|
126
|
+
- Rakefile
|
127
|
+
- bin/console
|
128
|
+
- bin/setup
|
129
|
+
- exe/rjoin
|
130
|
+
- exe/rsplit
|
131
|
+
- file-split-join-binary.gemspec
|
132
|
+
- lib/file_replicator/abstract_cmd_parse.rb
|
133
|
+
- lib/file_replicator/checksum.rb
|
134
|
+
- lib/file_replicator/exceptions.rb
|
135
|
+
- lib/file_replicator/joiner.rb
|
136
|
+
- lib/file_replicator/joiner_cmd_parse.rb
|
137
|
+
- lib/file_replicator/replicator_helper.rb
|
138
|
+
- lib/file_replicator/splitter.rb
|
139
|
+
- lib/file_replicator/splitter_cmd_parse.rb
|
140
|
+
- lib/file_replicator/version.rb
|
141
|
+
homepage: https://github.com/vadviktor/file-replicator
|
142
|
+
licenses:
|
143
|
+
- MIT
|
144
|
+
metadata: {}
|
145
|
+
post_install_message:
|
146
|
+
rdoc_options: []
|
147
|
+
require_paths:
|
148
|
+
- lib
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
requirements: []
|
160
|
+
rubyforge_project:
|
161
|
+
rubygems_version: 2.5.1
|
162
|
+
signing_key:
|
163
|
+
specification_version: 4
|
164
|
+
summary: Split and join files in binary mode
|
165
|
+
test_files: []
|