sequential_file 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e61c7c5b4f4c0cbfb6097b0110a843ddd6a4e4d1
4
+ data.tar.gz: 80ea9b4d801cdbf48db74c9e4224c4e2998b136a
5
+ SHA512:
6
+ metadata.gz: a8937ecceba9d2ac7b78c8e128b128eef2598e38ddd81a7a30ce5680a9cf9cf0aa05fb14268a0308db2dac8a82050a22af69f9970a2d2c2a5259f90223d4963d
7
+ data.tar.gz: be2b9618b48c2482f57eb0dafa2b3c19bb2a1743c074a3d226605bb49072028a20c337ca1e64e62a105ef2f3a57bf5c6fc7071466f4626a668a50e84fcfa92c7
@@ -0,0 +1 @@
1
+ service_name: travis-ci
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
5
+ - ruby-head
@@ -0,0 +1,2 @@
1
+ Version 0.0.1 - FEB.26.2014
2
+ * Initial Release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sequential_file.gemspec
4
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Peter Boling
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,143 @@
1
+ # sequential_file
2
+
3
+ Sequential File makes determination of which file to process (read or write) easy!
4
+
5
+ | Project | Sequential File |
6
+ |------------------------ | ----------------- |
7
+ | gem name | sequential_file |
8
+ | license | MIT |
9
+ | moldiness | [![Maintainer Status](http://stillmaintained.com/pboling/sequential_file.png)](http://stillmaintained.com/pboling/sequential_file) |
10
+ | version | [![Gem Version](https://badge.fury.io/rb/sequential_file.png)](http://badge.fury.io/rb/sequential_file) |
11
+ | dependencies | [![Dependency Status](https://gemnasium.com/pboling/sequential_file.png)](https://gemnasium.com/pboling/sequential_file) |
12
+ | code quality | [![Code Climate](https://codeclimate.com/github/pboling/sequential_file.png)](https://codeclimate.com/github/pboling/sequential_file) |
13
+ | continuous integration | [![Build Status](https://secure.travis-ci.org/pboling/sequential_file.png?branch=master)](https://travis-ci.org/pboling/sequential_file) |
14
+ | test coverage | [![Coverage Status](https://coveralls.io/repos/pboling/sequential_file/badge.png)](https://coveralls.io/r/pboling/sequential_file) |
15
+ | homepage | [https://github.com/pboling/sequential_file][homepage] |
16
+ | documentation | [http://rdoc.info/github/pboling/sequential_file/frames][documentation] |
17
+ | author | [Peter Boling](https://coderbits.com/pboling) |
18
+ | Spread ~♡ⓛⓞⓥⓔ♡~ | [![Endorse Me](https://api.coderwall.com/pboling/endorsecount.png)](http://coderwall.com/pboling) |
19
+
20
+ ## Summary
21
+
22
+ It allows you to create files that are named sequentially *and* intelligently.
23
+
24
+ Files will have four primary parts in their filenames:
25
+
26
+ 1. First part, which will be useful for list files in the shell.
27
+ 2. Second part is a date stamp, which will be useful in not having unwanted junk in our globs.
28
+ 3. Third part makes it easy to find the counter
29
+ 4. Fourth part is the counter
30
+
31
+ ## Compatibility
32
+
33
+ Tested with Ruby 2.0.0+. Due to the use of Ruby's 'prepend' initialization feature, will not work with anything older.
34
+
35
+ There are no dependencies, gem or otherwise, this is a pure Ruby library, and will work with any sort of File-type class.
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application's Gemfile:
40
+
41
+ gem 'sequential_file'
42
+
43
+ And then execute:
44
+
45
+ $ bundle
46
+
47
+ Or install it yourself as:
48
+
49
+ $ gem install sequential_file
50
+
51
+ ## Usage
52
+
53
+ If you have your own File-like class then you may just need to use the SequentialFile::Namer by including it.
54
+
55
+ ```
56
+ class MyFileLikeThing
57
+ include SequentialFile::Namer
58
+ end
59
+ ```
60
+
61
+ This gem ships with a reference implementation of a File-wrapper class that uses the Namer to determine which file to process.
62
+
63
+ Have a look at `lib/sequential_file/base.rb` or require it and play with it:
64
+
65
+ ```
66
+ require 'sequential_file/base'
67
+
68
+ file = SequentialFile::Base.new({
69
+ directory_path: '/tmp',
70
+ name: 'access_log.FileBase.csv',
71
+ process_date: Date.today
72
+ })
73
+
74
+ file.write('this is new text') # => write ensures that the file is open for writing, but you have to remember to close!
75
+ file.close
76
+ file.write('this is after close text')
77
+ file.read =~ /this is after close text/ # => integer representation of the position in the file where the text match starts
78
+ file.close
79
+ ```
80
+
81
+ Also, see the specs.
82
+
83
+ ## Contributors
84
+
85
+ See the [Network View](https://github.com/pboling/sequential_file/network) and the [CHANGELOG](https://github.com/pboling/sequential_file/blob/master/CHANGELOG.md)
86
+
87
+ ## How you can help!
88
+
89
+ Take a look at the `reek` and `roodi` lists. These are the files names `REEK` and `ROODI` in the root of the gem.
90
+
91
+ Currently they are clean, and we like to keep them that way! Once you complete a change, run the tests:
92
+
93
+ ```
94
+ bundle exec rake spec
95
+ ```
96
+
97
+ If the tests pass refresh the `reek` and `roodi` lists and make sure things have not gotten worse:
98
+
99
+ ```
100
+ bundle exec rake reek > REEK
101
+ bundle exec rake reek > ROODI
102
+ ```
103
+
104
+ Follow the instructions for "Contributing" below.
105
+
106
+ ## Contributing
107
+
108
+ 1. Fork it
109
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
110
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
111
+ 4. Push to the branch (`git push origin my-new-feature`)
112
+ 5. Create new Pull Request
113
+
114
+ ## Versioning
115
+
116
+ This library aims to adhere to [Semantic Versioning 2.0.0][semver].
117
+ Violations of this scheme should be reported as bugs. Specifically,
118
+ if a minor or patch version is released that breaks backward
119
+ compatibility, a new version should be immediately released that
120
+ restores compatibility. Breaking changes to the public API will
121
+ only be introduced with new major versions.
122
+
123
+ As a result of this policy, you can (and should) specify a
124
+ dependency on this gem using the [Pessimistic Version Constraint][pvc] with two digits of precision.
125
+
126
+ For example:
127
+
128
+ spec.add_dependency 'sequential_file', '~> 1.0.8'
129
+
130
+ ## Legal
131
+
132
+ * MIT License - See LICENSE file in this project
133
+ * Copyright (c) 2014 [Peter H. Boling][peterboling] of [Rails Bling][railsbling]
134
+
135
+ [semver]: http://semver.org/
136
+ [pvc]: http://docs.rubygems.org/read/chapter/16#page74
137
+ [railsbling]: http://www.railsbling.com
138
+ [peterboling]: http://www.peterboling.com
139
+ [documentation]: http://rdoc.info/github/pboling/sequential_file/frames
140
+ [homepage]: https://github.com/pboling/sequential_file
141
+
142
+
143
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/pboling/sequential_file/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
data/REEK ADDED
@@ -0,0 +1,2 @@
1
+
2
+ 0 total warnings
data/ROODI ADDED
@@ -0,0 +1,5 @@
1
+
2
+ Running Roodi checks
3
+
4
+ Checked 9 files
5
+ Found 0 errors.
@@ -0,0 +1,33 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = FileList['spec/**/*_spec.rb']
7
+ end
8
+
9
+ require 'reek/rake/task'
10
+ Reek::Rake::Task.new do |t|
11
+ t.fail_on_error = true
12
+ t.verbose = false
13
+ t.source_files = 'lib/**/*.rb'
14
+ end
15
+
16
+ require 'roodi'
17
+ require 'roodi_task'
18
+ RoodiTask.new do |t|
19
+ t.verbose = false
20
+ end
21
+
22
+ task :default => :spec
23
+
24
+ require File.expand_path('../lib/sequential_file/version', __FILE__)
25
+ require 'rdoc'
26
+ require 'rdoc/task'
27
+ RDoc::Task.new do |rdoc|
28
+ rdoc.rdoc_dir = 'rdoc'
29
+ rdoc.title = "SequentialFile #{SequentialFile::VERSION}"
30
+ rdoc.options << '--line-numbers'
31
+ rdoc.rdoc_files.include('README*')
32
+ rdoc.rdoc_files.include('lib/**/*.rb')
33
+ end
@@ -0,0 +1,10 @@
1
+ require "sequential_file/version"
2
+ require "sequential_file/namer"
3
+
4
+ # SequentialFile::Base is not loaded by default, because it is a reference implementation, and third party libs may not need or want it.
5
+ # It can be used by adding the following to any third party lib:
6
+ #require "sequential_file/base"
7
+
8
+ module SequentialFile
9
+ # Just a namespace here...
10
+ end
@@ -0,0 +1,109 @@
1
+ require 'date'
2
+
3
+ module SequentialFile
4
+ # This is a reference implementation of a File-wrapper class that uses the Namer to determine which file to process
5
+ class Base
6
+
7
+ include SequentialFile::Namer
8
+
9
+ attr_accessor :complete_path # the complete path to the file
10
+ attr_accessor :read_position # the byte position in the file where read stopped and monitor will begin.
11
+ attr_accessor :file, :permission_style
12
+ attr_accessor :buffer_size, :buffer_limit, :buffer_position, :verbose
13
+
14
+ def initialize(options = {}, &block)
15
+ yield if block_given?
16
+ @complete_path ||= File.join(self.directory_path, self.name)
17
+ @buffer_limit = options[:buffer_limit] || 200 # bytes
18
+ @buffer_size = 0
19
+ @permission_style = options[:permission_style] || File::RDWR|File::CREAT
20
+ @verbose = !!options[:verbose]
21
+ end
22
+
23
+ def info_string
24
+ "PERM: #{@permission_style}\n\tPATH: #{@complete_path}"
25
+ end
26
+
27
+ def file_handle
28
+ # autoclose: false => Keep the file descriptor open until we want to close it.
29
+ File.new(@complete_path, @permission_style, {autoclose: false})
30
+ self.file ||= File.new(@complete_path, @permission_style, {autoclose: false})
31
+ end
32
+
33
+ def num_lines
34
+ file_handle.seek(0, IO::SEEK_SET)
35
+ file_handle.readlines.size
36
+ end
37
+
38
+ # Need to remember to close file with self.close after done using the file descriptor
39
+ def write(msg)
40
+ prep_write
41
+ puts "#-------> @[ #{@file_handle.pos} ] B[ #{@buffer_size} ] in #{@complete_path} <---------#\n#{msg}" if verbose
42
+ add_to_buffer(msg)
43
+ flush_if_buffer_full
44
+ end
45
+
46
+ def flush_if_buffer_full
47
+ if buffer_size >= buffer_limit
48
+ file_handle.flush
49
+ @buffer_size = 0
50
+ end
51
+ end
52
+
53
+ # Read file from beginning to end, recording position when done
54
+ # Need to remember to close file with self.close after done using the file descriptor
55
+ def read &block
56
+ prep_read
57
+ data = block_given? ?
58
+ file_handle.each {|line| yield line } :
59
+ file_handle.read
60
+ @read_position = file_handle.pos
61
+ data
62
+ end
63
+
64
+ def close
65
+ file_handle.close
66
+ # Set the file to nil, so it will be reopened when needed.
67
+ self.file = nil
68
+ end
69
+
70
+ def clear
71
+ file_handle.truncate 0
72
+ # Set the file to nil, so it will be reopened when needed.
73
+ close
74
+ end
75
+
76
+ def delete
77
+ if file
78
+ close # just in case
79
+ File.delete(complete_path)
80
+ end
81
+ end
82
+
83
+ protected
84
+ def prep_write
85
+ file_handle.flock(File::LOCK_EX) # an exclusive lock on the file.
86
+ file_handle.seek(0, IO::SEEK_END)
87
+ end
88
+ def prep_read
89
+ file_handle.flock(File::LOCK_SH) # a shared lock on the file.
90
+ file_handle.seek(0, IO::SEEK_SET)
91
+ end
92
+ def add_to_buffer(msg)
93
+ @buffer_size += msg.length
94
+ file_handle.write(msg)
95
+ end
96
+ end
97
+ end
98
+
99
+ # Read file from #position where read stopped and continue to monitor for additional lines added
100
+ # Need to remember to close file with self.close after done using the file descriptor
101
+ # WARNING: this is an infinite loop, so know how you will stop it before you start it.
102
+ #def monitor &block
103
+ # f = File.open(self.complete_path,"r")
104
+ # f.seek(self.read_position, IO::SEEK_SET)
105
+ # while true do
106
+ # select([f]) # Kernel#select
107
+ # yield f.gets # a line
108
+ # end
109
+ #end
@@ -0,0 +1,30 @@
1
+ module SequentialFile
2
+
3
+ # This class takes a filename and an extension, and finds the counter (Integer) part of the file name
4
+ class CounterFinder
5
+ NUMBER_REGEX = /\d*/
6
+
7
+ # determines the counter value of the filename passed in.
8
+ # params:
9
+ # filename - string, complete filename
10
+ # extension - string, the part of the filename that is the extension (e.g. .log or .csv)
11
+ def initialize(filename, extension)
12
+ @filename = File.basename(filename, extension)
13
+ @length = @filename.length
14
+ @index_of_extension_separator = @filename.rindex('.')
15
+ end
16
+
17
+ def has_extension_separator?
18
+ !!@index_of_extension_separator
19
+ end
20
+
21
+ # returns:
22
+ # an integer representation of the counter
23
+ def counter
24
+ has_extension_separator? ?
25
+ @filename[@index_of_extension_separator + 1,@length][NUMBER_REGEX].to_i :
26
+ 0
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,107 @@
1
+ require 'sequential_file/counter_finder'
2
+
3
+ module SequentialFile
4
+ module Namer
5
+
6
+ module Initializer
7
+ def initialize(options = {})
8
+ super do
9
+ name = options[:name]
10
+ name ?
11
+ derive_name_parts_from_name(name) :
12
+ set_name_parts(options[:filename_first_part], options[:filename_third_part], options[:file_extension])
13
+ @directory_path = options[:directory_path]
14
+ @process_date = options[:process_date] || Date.today
15
+ if options[:append]
16
+ @last_filename_counter = self.last_used_counter
17
+ else
18
+ @last_filename_counter = self.get_next_available_counter
19
+ end
20
+ @name = self.determine_name
21
+ end
22
+ end
23
+ end
24
+
25
+ # :directory_path - directory path where file will be located
26
+ # :name - complete intelligent *and* sequential filename
27
+ # :process_date - date stamp of invocation, used as part of filename
28
+ # :filename_first_part - first part of filename, separated from third part by the chronometer
29
+ # :filename_third_part - third part of filename, separated from first part by the chronometer
30
+ # :last_filename_counter - the last-used file counter; prevent files from stepping on each other.
31
+ # :file_extension - the file extension
32
+ def self.included(klass)
33
+ klass.send :prepend, Initializer
34
+ klass.send :attr_accessor, :directory_path, :name, :process_date, :filename_first_part, :filename_third_part, :last_filename_counter, :file_extension
35
+ end
36
+
37
+ protected
38
+ def set_name_parts(first_part, third_part, name_extension)
39
+ @filename_first_part = first_part
40
+ @filename_third_part = third_part
41
+ @file_extension = name_extension
42
+ end
43
+
44
+ def derive_name_parts_from_name(name_option)
45
+ parts = name_option.split('.')
46
+ raise ArgumentError, ":name must have three parts separated by dots ('.') in order to be a valid option for #{self.class.name}" unless parts.length == 3
47
+ set_name_parts(parts[0], parts[1], '.' << parts[2])
48
+ end
49
+
50
+ def globular_file_parts
51
+ chrono = get_chronometer
52
+ with_dot = chrono.empty? ? '' : ".#{chrono}"
53
+ "#{@filename_first_part}#{with_dot}.#{@filename_third_part}"
54
+ end
55
+
56
+ def determine_name
57
+ "#{globular_file_parts}.#{@last_filename_counter}#{@file_extension}"
58
+ end
59
+
60
+ def get_chronometer
61
+ @process_date.respond_to?(:strftime) ?
62
+ @process_date.strftime("%Y%m%d") :
63
+ @process_date.to_s
64
+ end
65
+
66
+ # determines the last used file counter value
67
+ # returns:
68
+ # an integer representation of the counter
69
+ def last_used_counter
70
+ directory_glob.inject(0) do |high_value, file|
71
+ counter = get_counter_for_file(file)
72
+ ((high_value <=> counter) == 1) ? high_value : counter
73
+ end
74
+ end
75
+
76
+ # determines the counter value for the next bump file to create
77
+ # returns:
78
+ # an integer representation of the next counter to use
79
+ def get_next_available_counter
80
+ if @last_filename_counter
81
+ @last_filename_counter + 1
82
+ else
83
+ last_used_counter + 1
84
+ end
85
+ end
86
+
87
+ # determines the counter value of the filename passed in.
88
+ # params:
89
+ # none
90
+ # returns:
91
+ # an array of file names in the directory matching the glob
92
+ def directory_glob
93
+ glob = File.join(directory_path, "#{globular_file_parts}.*")
94
+ Dir.glob(glob)
95
+ end
96
+
97
+ # determines the counter value of the filename passed in.
98
+ # params:
99
+ # filename - string, complete filename
100
+ # returns:
101
+ # an integer representation of the counter
102
+ def get_counter_for_file(filename)
103
+ CounterFinder.new(filename, @file_extension).counter
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,3 @@
1
+ module SequentialFile
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,63 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sequential_file/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sequential_file"
8
+ spec.version = SequentialFile::VERSION
9
+ spec.authors = ["Peter Boling"]
10
+ spec.email = ["peter.boling@gmail.com"]
11
+ spec.description = %q{Create Files Named Sequentially Intelligently}
12
+ spec.summary = %q{Create Files Named Sequentially Intelligently}
13
+ spec.homepage = "http://railsbling.com"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ ############################
22
+ # RUNTIME DEPENDENCIES #
23
+ ############################
24
+ #
25
+ # NONE!
26
+
27
+ ############################
28
+ # DEVELOPMENT DEPENDENCIES #
29
+ ############################
30
+
31
+ # Gem dependency manager
32
+ spec.add_development_dependency(%q<bundler>, ["~> 1.5.3"])
33
+
34
+ # The ruby workhorse command line tool.
35
+ spec.add_development_dependency(%q<rake>, ["~> 10.1.1"])
36
+
37
+ # For a test suite.
38
+ # https://github.com/rspec/rspec
39
+ spec.add_development_dependency(%q<rspec>, ["~> 2.14"])
40
+
41
+ # Give the test suite super powers (and less verbosity).
42
+ # https://github.com/rspec/rspec
43
+ spec.add_development_dependency(%q<shoulda>, ["~> 3.5.0"])
44
+ spec.add_development_dependency(%q<shoulda-matchers>, ["~> 1.5.6"])
45
+
46
+ # For detecting code smells.
47
+ # https://github.com/troessner/reek
48
+ spec.add_development_dependency(%q<reek>, ["~> 1.3.6"])
49
+
50
+ # For detecting more code smells.
51
+ # https://github.com/roodi/roodi
52
+ spec.add_development_dependency(%q<roodi>, ["~> 3.3.1"])
53
+
54
+ # For code coverage
55
+ spec.add_development_dependency "coveralls"
56
+
57
+ # Useful to work with relative paths to file, and to derive absolute paths
58
+ spec.add_dependency(%q<rbx-require-relative>, ["~> 0.0.9"])
59
+
60
+ # For autotest-like sweetness
61
+ spec.add_dependency(%q<guard-rspec>, ["~> 2.4.0"])
62
+
63
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'shared_examples_for_files'
3
+ require 'sequential_file/base'
4
+
5
+ describe SequentialFile::Base do
6
+ FILE_BASE_TEST_PATH = File.join(GEM_ROOT, 'spec/tmp')
7
+ describe "#initialize" do
8
+ context "process date as empty string" do
9
+ let(:file){ SequentialFile::Base.new({
10
+ directory_path: FILE_BASE_TEST_PATH,
11
+ name: 'access_log.FileBase.tsv',
12
+ process_date: ''
13
+ }) }
14
+ subject { file }
15
+ after(:each) do
16
+ subject.delete
17
+ end
18
+ it_behaves_like "a SequentialFile::Base"
19
+
20
+ it('- class') { subject.class.should == SequentialFile::Base }
21
+ it('- complete_filename') { subject.name.should == 'access_log.FileBase.1.tsv' }
22
+ it('- complete_path') { subject.complete_path.should == File.join(subject.directory_path, 'access_log.FileBase.1.tsv') }
23
+ end
24
+
25
+ context "process date as nil" do
26
+ let(:file){ SequentialFile::Base.new({
27
+ directory_path: FILE_BASE_TEST_PATH,
28
+ name: 'access_log.FileBase.tsv',
29
+ process_date: nil
30
+ }) }
31
+ subject { file }
32
+ after(:each) do
33
+ subject.delete
34
+ end
35
+ it_behaves_like "a SequentialFile::Base"
36
+
37
+ it('- class') { subject.class.should == SequentialFile::Base }
38
+ it('- complete_filename') { subject.name.should == "access_log.#{Date.today.strftime("%Y%m%d")}.FileBase.1.tsv" }
39
+ it('- complete_path') { subject.complete_path.should == File.join(subject.directory_path, "access_log.#{Date.today.strftime("%Y%m%d")}.FileBase.1.tsv") }
40
+ end
41
+
42
+ context "process date as word" do
43
+ let(:file){ SequentialFile::Base.new({
44
+ directory_path: FILE_BASE_TEST_PATH,
45
+ name: 'access_log.FileBase.tsv',
46
+ process_date: 'today'
47
+ }) }
48
+ subject { file }
49
+ after(:each) do
50
+ subject.delete
51
+ end
52
+ it_behaves_like "a SequentialFile::Base"
53
+
54
+ it('- class') { subject.class.should == SequentialFile::Base }
55
+ it('- complete_filename') { subject.name.should == 'access_log.today.FileBase.1.tsv' }
56
+ it('- complete_path') { subject.complete_path.should == File.join(subject.directory_path, 'access_log.today.FileBase.1.tsv') }
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe SequentialFile::CounterFinder::NUMBER_REGEX do
4
+ it('- should be a Regexp') { SequentialFile::CounterFinder::NUMBER_REGEX.should be_a(Regexp)}
5
+ it('- should match numbers') { ('asdf5' =~ SequentialFile::CounterFinder::NUMBER_REGEX.should) == 4}
6
+ end
7
+
8
+ describe SequentialFile::CounterFinder do
9
+
10
+ describe "#initialize" do
11
+ let(:counter_finder){ SequentialFile::CounterFinder.new('access_log.FileBase.1.tsv', '.tsv') }
12
+ subject { counter_finder }
13
+ it('- class') { subject.class.should == SequentialFile::CounterFinder }
14
+ end
15
+
16
+ context "with counter" do
17
+ let(:counter_finder){ SequentialFile::CounterFinder.new('access_log.FileBase.1.tsv', '.tsv') }
18
+ subject { counter_finder }
19
+ it('- has_extension_separator?') { subject.has_extension_separator?.should == true }
20
+ it('- counter') { subject.counter.should == 1 }
21
+ end
22
+
23
+ context "without counter" do
24
+ let(:counter_finder){ SequentialFile::CounterFinder.new('access_log.FileBase.tsv', '.tsv') }
25
+ subject { counter_finder }
26
+ it('- has_extension_separator?') { subject.has_extension_separator?.should == true }
27
+ it('- counter') { subject.counter.should == 0 }
28
+ end
29
+
30
+ context "with large counter" do
31
+ let(:counter_finder){ SequentialFile::CounterFinder.new('access_log.FileBase.10234856.tsv', '.tsv') }
32
+ subject { counter_finder }
33
+ it('- has_extension_separator?') { subject.has_extension_separator?.should == true }
34
+ it('- counter') { subject.counter.should == 10234856 }
35
+ end
36
+
37
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'shared_examples_for_files'
3
+ require 'sequential_file/base'
4
+
5
+ class BumpFileB < SequentialFile::Base
6
+ include SequentialFile
7
+ end
8
+
9
+ class BumpFile < BumpFileB
10
+ def initialize(options = {})
11
+ options[:file_extension] = '.json'
12
+ super options
13
+ end
14
+ end
15
+
16
+ describe BumpFile do
17
+ NAMER_TEST_PATH = File.join(GEM_ROOT, 'spec/tmp')
18
+ describe "as a file" do
19
+ let(:file){ BumpFile.new({ directory_path: NAMER_TEST_PATH, process_date: Date.new(2014,2,14) }) }
20
+ subject { file }
21
+ after(:each) do
22
+ subject.delete
23
+ end
24
+ it_behaves_like "a SequentialFile::Base"
25
+ end
26
+ describe "#initialize" do
27
+ context "basic options" do
28
+ before(:each) do
29
+ @bump_file = BumpFile.new({ directory_path: NAMER_TEST_PATH, process_date: Date.new(2014,2,14) })
30
+ end
31
+ it('- class') { @bump_file.class.should == BumpFile }
32
+ it('- name') { @bump_file.name.should == '.20140214..1.json' }
33
+ it('- complete_path') { @bump_file.complete_path.should == File.join(@bump_file.directory_path, '.20140214..1.json') }
34
+ end
35
+ context "realistic options" do
36
+ before(:each) do
37
+ @bump_file = BumpFile.new({
38
+ directory_path: NAMER_TEST_PATH,
39
+ filename_first_part: 'asdf',
40
+ filename_third_part: 'qwer',
41
+ process_date: Date.new(2014,2,14)
42
+ })
43
+ end
44
+ it('- filename_counter') { @bump_file.last_filename_counter.should == 1 }
45
+ it('- name') { @bump_file.name.should == 'asdf.20140214.qwer.1.json' }
46
+ it('- complete_path') { @bump_file.complete_path.should == File.join(@bump_file.directory_path, 'asdf.20140214.qwer.1.json') }
47
+ end
48
+ context "name option" do
49
+ before(:each) do
50
+ @bump_file = BumpFile.new({
51
+ directory_path: NAMER_TEST_PATH,
52
+ name: 'abra.cadabra.log',
53
+ process_date: Date.new(2014,3,14)
54
+ })
55
+ end
56
+ it('- filename_counter') { @bump_file.last_filename_counter.should == 1 }
57
+ it('- name') { @bump_file.name.should == 'abra.20140314.cadabra.1.log' }
58
+ it('- complete_path') { @bump_file.complete_path.should == File.join(@bump_file.directory_path, 'abra.20140314.cadabra.1.log') }
59
+ end
60
+ context "append" do
61
+ before(:each) do
62
+ @bump_file = BumpFile.new({
63
+ directory_path: NAMER_TEST_PATH,
64
+ name: 'abra.cadabra.log',
65
+ process_date: Date.new(2014,3,14),
66
+ append: true
67
+ })
68
+ end
69
+ it('- filename_counter') { @bump_file.last_filename_counter.should == 0 }
70
+ it('- name') { @bump_file.name.should == 'abra.20140314.cadabra.0.log' }
71
+ it('- complete_path') { @bump_file.complete_path.should == File.join(@bump_file.directory_path, 'abra.20140314.cadabra.0.log') }
72
+ end
73
+ context "pre-existing file on counter #42" do
74
+ before(:each) do
75
+ @bump_file = BumpFile.new({
76
+ directory_path: File.join(GEM_ROOT,'spec/test_data'),
77
+ filename_first_part: 'asdf',
78
+ filename_third_part: 'qwer',
79
+ process_date: Date.new(2014,2,14)
80
+ })
81
+ end
82
+ it('- last_filename_counter') { @bump_file.last_filename_counter.should == 43 }
83
+ it('- name') { @bump_file.name.should == 'asdf.20140214.qwer.43.json' }
84
+ it('- complete_path') { @bump_file.complete_path.should == File.join(GEM_ROOT,'spec/test_data', 'asdf.20140214.qwer.43.json') }
85
+ end
86
+ end
87
+ describe "#write" do
88
+ context "data to a file" do
89
+ before(:each) do
90
+ @bump_file = BumpFile.new({
91
+ directory_path: File.join(GEM_ROOT,'spec/scratch'),
92
+ filename_first_part: 'asdf',
93
+ filename_third_part: 'qwer',
94
+ process_date: Date.new(2014,2,14)
95
+ })
96
+ end
97
+ after(:each) do
98
+ @bump_file.delete
99
+ end
100
+ it('- no exception') { lambda { @bump_file.write('This is bogus') }.should_not raise_exception }
101
+ it('- can read what was written') {
102
+ @bump_file.write('This is bogus')
103
+ # Because of the random seeding of test order this spec may run before the one above
104
+ # So "This is bogus" may be printed once or twice.
105
+ @bump_file.read.should =~ /This is bogus/
106
+ }
107
+ end
108
+ end
109
+ end
File without changes
@@ -0,0 +1,44 @@
1
+ shared_examples "a SequentialFile::Base" do
2
+
3
+ describe "ancestry" do
4
+ it('- class') { subject.is_a?( SequentialFile::Base ).should == true }
5
+ end
6
+
7
+ describe "#write" do
8
+ context "data to a file" do
9
+ it('- should succeed') {
10
+ lambda { subject.write('this is text') }.should_not raise_exception
11
+ }
12
+ it('- should be readable') {
13
+ subject.write('this is text')
14
+ subject.read.should =~ /this is text/
15
+ }
16
+ end
17
+ context "after close" do
18
+ it('- should write') {
19
+ subject.write('this is text')
20
+ subject.close
21
+ lambda { subject.write('this is text') }.should_not raise_exception
22
+ }
23
+ it('- should be readable') {
24
+ subject.write('this is new text')
25
+ subject.close
26
+ subject.write('this is after close text')
27
+ subject.read.should =~ /this is after close text/
28
+ }
29
+ end
30
+ end
31
+
32
+ describe "#read" do
33
+ context "data from a file" do
34
+ it('- without block') { lambda { subject.read {} }.should_not raise_exception }
35
+ it('- with block') { lambda { subject.read { |line| puts line } }.should_not raise_exception }
36
+ it('- advances position') {
37
+ subject.write('this is text')
38
+ subject.read
39
+ subject.read_position.should be == 12
40
+ }
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format
3
+ specdoc
4
+ --loadby
5
+ mtime
6
+ --reverse
7
+ --backtrace
@@ -0,0 +1,24 @@
1
+ require 'require_relative' # loads rbx-require-relative gem
2
+
3
+ # For code coverage, must be required before all application / gem / library code.
4
+ unless ENV['NOCOVER']
5
+ require 'coveralls'
6
+ Coveralls.wear!
7
+ end
8
+
9
+ require 'sequential_file'
10
+
11
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
12
+ RSpec.configure do |config|
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ config.run_all_when_everything_filtered = true
15
+ config.filter_run :focus
16
+
17
+ # Run specs in random order to surface order dependencies. If you find an
18
+ # order dependency and want to debug it, you can fix the order by providing
19
+ # the seed, which is printed after each run.
20
+ # --seed 1234
21
+ config.order = 'random'
22
+ end
23
+
24
+ GEM_ROOT = RequireRelative.abs_file.split("spec/spec_helper.rb")[0]
metadata ADDED
@@ -0,0 +1,218 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequential_file
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Peter Boling
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-26 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.5.3
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.3
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.1.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 10.1.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: shoulda
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 3.5.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 3.5.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: shoulda-matchers
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 1.5.6
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 1.5.6
83
+ - !ruby/object:Gem::Dependency
84
+ name: reek
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 1.3.6
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 1.3.6
97
+ - !ruby/object:Gem::Dependency
98
+ name: roodi
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 3.3.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 3.3.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: coveralls
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rbx-require-relative
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 0.0.9
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: 0.0.9
139
+ - !ruby/object:Gem::Dependency
140
+ name: guard-rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ version: 2.4.0
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: 2.4.0
153
+ description: Create Files Named Sequentially Intelligently
154
+ email:
155
+ - peter.boling@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - .coveralls.yml
161
+ - .gitignore
162
+ - .rspec
163
+ - .travis.yml
164
+ - CHANGELOG.md
165
+ - Gemfile
166
+ - Guardfile
167
+ - LICENSE.txt
168
+ - README.md
169
+ - REEK
170
+ - ROODI
171
+ - Rakefile
172
+ - lib/sequential_file.rb
173
+ - lib/sequential_file/base.rb
174
+ - lib/sequential_file/counter_finder.rb
175
+ - lib/sequential_file/namer.rb
176
+ - lib/sequential_file/version.rb
177
+ - sequential_file.gemspec
178
+ - spec/lib/sequential_file/base_spec.rb
179
+ - spec/lib/sequential_file/counter_finder_spec.rb
180
+ - spec/lib/sequential_file/namer_spec.rb
181
+ - spec/scratch/.gitkeep
182
+ - spec/shared_examples_for_files.rb
183
+ - spec/spec.opts
184
+ - spec/spec_helper.rb
185
+ - spec/test_data/asdf.20140214.qwer.42.json
186
+ homepage: http://railsbling.com
187
+ licenses:
188
+ - MIT
189
+ metadata: {}
190
+ post_install_message:
191
+ rdoc_options: []
192
+ require_paths:
193
+ - lib
194
+ required_ruby_version: !ruby/object:Gem::Requirement
195
+ requirements:
196
+ - - '>='
197
+ - !ruby/object:Gem::Version
198
+ version: '0'
199
+ required_rubygems_version: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - '>='
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
204
+ requirements: []
205
+ rubyforge_project:
206
+ rubygems_version: 2.2.2
207
+ signing_key:
208
+ specification_version: 4
209
+ summary: Create Files Named Sequentially Intelligently
210
+ test_files:
211
+ - spec/lib/sequential_file/base_spec.rb
212
+ - spec/lib/sequential_file/counter_finder_spec.rb
213
+ - spec/lib/sequential_file/namer_spec.rb
214
+ - spec/scratch/.gitkeep
215
+ - spec/shared_examples_for_files.rb
216
+ - spec/spec.opts
217
+ - spec/spec_helper.rb
218
+ - spec/test_data/asdf.20140214.qwer.42.json