kitcat 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f6c930e9f26d08132fc15fa533107ebd5d8f9c43
4
+ data.tar.gz: 898f7a3fa58d3b38cf85692aee414b0b9dab82d3
5
+ SHA512:
6
+ metadata.gz: 16cec3058a3b816289dfc6b2cf7adc8bea0b210a07103596c4096e66b436012056cfee8d8ae55d5019a27966d1e7951510d7f764e557a5bfc952235d5a21f1db
7
+ data.tar.gz: d3af7d89498d41509cd026c0ee3f72c1294584d28a2f18e5e8daf19fafa1834ff8777b8ee775072bae413499dcf61b5c4336942a87aa6a3fb5bbcf7a08ac5a16
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,11 @@
1
+ # Contributing
2
+
3
+ 1. Submit a ticket with your issue here at Github. Make sure your ticket clearly describes the bug (with steps to reproduce it) or desired feature.
4
+ 1. Fork the repository.
5
+ 1. Create a topic branch based on master branch if you want your feature/bugfix to be released in the next versions. Alternatively, you can base your branch on
6
+ a release branch if you want your bugfix to be part of a particular version.
7
+ 1. Make sure you split your work into reasonable commits (`git rebase` can be your friend)
8
+ 1. Write a test for your new feature/bugfix
9
+ 1. Run whole test suite to make sure you didn't break anything.
10
+ 1. Create a PR to the main repository.
11
+ 1. Link the PR to the Github Issue ticket.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Simply Business
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ [![Build Status](https://travis-ci.org/simplybusiness/kitcat.svg?branch=master)](https://travis-ci.org/simplybusiness/kitcat)
2
+ [![Coverage Status](https://coveralls.io/repos/github/simplybusiness/kitcat/badge.svg?branch=develop)](https://coveralls.io/github/simplybusiness/kitcat?branch=develop)
3
+ [![Code Climate](https://codeclimate.com/github/simplybusiness/kitcat/badges/gpa.svg)](https://codeclimate.com/github/simplybusiness/kitcat)
4
+
5
+ # KitCat
6
+
7
+ *Sometimes schema migrations are just not enough.*
8
+
9
+ Data migration framework written in plain Ruby. Although originally created for migrating data in MongoDb, currently it is pretty **generic**, since it **does not depend** on any web framework.
10
+
11
+ ## Features of Framework
12
+
13
+ This is a small migration framework that offers the following functionality for free:
14
+
15
+ 1. Logging
16
+ 2. Progress Bar
17
+ 3. Gracefully handling when user interrupts (Ctrl+C or `kill <process_id>`)
18
+
19
+ ## Example Usage
20
+
21
+ Assuming that the migration strategy is implemented with a class `MigrationStrategy`, then executing a migration is quite simple:
22
+
23
+ ``` ruby
24
+ migration_strategy = MigrationStrategy.new
25
+ migration_framework = KitCat::Framework.new(migration_strategy)
26
+ migration_framework.execute
27
+ ```
28
+
29
+ The above will run the migration and will create a log file inside the `log` directory (which is created if not present)
30
+
31
+ [How to implement MigrationStrategy](./docs/STRATEGY.md)
32
+
33
+ # Testing
34
+
35
+ ```bash
36
+ bundle exec rake
37
+ ```
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'rubocop/rake_task'
3
+ require 'bundler/audit/task'
4
+
5
+ RSpec::Core::RakeTask.new :spec
6
+ RuboCop::RakeTask.new :rubocop
7
+ Bundler::Audit::Task.new
8
+
9
+ task default: [:spec, :rubocop, 'bundle:audit']
@@ -0,0 +1,191 @@
1
+ require 'ruby-progressbar'
2
+ require 'active_model'
3
+ require 'active_support/core_ext'
4
+
5
+ module KitCat
6
+ class Framework
7
+ attr_reader :last_item_processed,
8
+ :migration_name,
9
+ :number_of_items_processed,
10
+ :migration_strategy
11
+
12
+ # @params migration_strategy {Object}
13
+ # Instance implementing the methods of +KitCat::Callbacks+
14
+ #
15
+ # migration_name {String} Optional.
16
+ # The name of the migration. Used as tag in log file name. If not given, a random/unique one is used.
17
+ #
18
+ # number_of_items_to_process {Integer} Optional.
19
+ # If given, the processing will stop after processing that many number of items.
20
+ #
21
+ # progress_bar {Boolean} Optional.
22
+ # When +True+. it will instantiate a use a progress bar, incrementing that by 1 every time
23
+ # the +migration_strategy+ finishes processing an item. The total load will be calculated based on the
24
+ # result of +migration_strategy#criteria#count+.
25
+ # When +False+, progress bar will not be used
26
+ #
27
+ # progress_bar_output Optional. Defaults to STDOUT. Anything that responds to
28
+ # #print, #flush, #tty? and #puts.
29
+ # It is taken into account only if progress bar is enabled.
30
+ #
31
+ def initialize(migration_strategy,
32
+ migration_name: nil,
33
+ number_of_items_to_process: nil,
34
+ progress_bar: true,
35
+ progress_bar_output: STDOUT)
36
+ @migration_strategy = migration_strategy
37
+ @migration_name = build_migration_name(migration_name)
38
+ @number_of_items_to_process = number_of_items_to_process
39
+ @last_item_processed = nil
40
+ @progress_bar = initialize_progress_bar(progress_bar, progress_bar_output)
41
+ end
42
+
43
+ def execute
44
+ @interrupted = false
45
+ Signal.trap('TERM') { @interrupted = true }
46
+ Signal.trap('INT') { @interrupted = true }
47
+ start_logging
48
+
49
+ @number_of_items_processed = 0
50
+
51
+ items.each do |item|
52
+ if migration_strategy.process(item)
53
+
54
+ log_success(item)
55
+
56
+ @number_of_items_processed += 1
57
+
58
+ increment_progress_bar
59
+
60
+ @last_item_processed = item
61
+
62
+ break unless process_more?
63
+ else
64
+ log_failure(item)
65
+
66
+ break
67
+ end
68
+ if @interrupted
69
+ handle_user_interrupt
70
+ break
71
+ end
72
+ end
73
+ end_logging
74
+ end
75
+
76
+ def number_of_items_to_process
77
+ @number_of_items_to_process ||= migration_strategy.criteria.count
78
+ end
79
+
80
+ def log_file_path
81
+ @log_file_path ||= File.join(log_dir, build_log_file_name)
82
+ end
83
+
84
+ def progress_bar?
85
+ !@progress_bar.nil?
86
+ end
87
+
88
+ def progress
89
+ return -1 unless progress_bar?
90
+ @progress_bar.progress
91
+ end
92
+
93
+ private
94
+
95
+ def items
96
+ return enum_for(:items) unless block_given?
97
+
98
+ enum = migration_strategy.criteria.each
99
+
100
+ loop do
101
+ yield enum.next
102
+ end
103
+ end
104
+
105
+ def log_dir
106
+ @log_dir ||= FileUtils.mkdir_p(File.join(Dir.pwd, 'log'))
107
+ end
108
+
109
+ def build_log_file_name
110
+ "migration-#{migration_name}-#{timestamp}.log"
111
+ end
112
+
113
+ def timestamp
114
+ Time.now.to_s(:number)
115
+ end
116
+
117
+ def build_migration_name(migration_name)
118
+ result = migration_name || migration_strategy.class.name.delete(':').underscore.upcase
119
+ result.gsub(/\W/, '').upcase
120
+ end
121
+
122
+ def initialize_progress_bar(progress_bar_flag, output)
123
+ create_progress_bar(output) if progress_bar_flag || progress_bar_flag.nil?
124
+ end
125
+
126
+ def create_progress_bar(output)
127
+ @progress_bar = ProgressBar.create(total: migration_strategy.criteria.count,
128
+ output: output,
129
+ progress_mark: ' ',
130
+ remainder_mark: '-',
131
+ length: terminal_width,
132
+ format: "%a %bᗧ%i %p%% %e")
133
+ end
134
+
135
+ def start_logging
136
+ logger.info 'Start Processing...'
137
+ end
138
+
139
+ def end_logging
140
+ logger.info '...end of processing'
141
+ end
142
+
143
+ def logger
144
+ @logger ||= Logger.new(log_file_path)
145
+ end
146
+
147
+ def log_success(item)
148
+ log_line(item) { |method| logger.info "...successfully processed item: #{item.try(method)}" }
149
+ end
150
+
151
+ def log_failure(item)
152
+ log_line(item) { |method| logger.error "...error while processing item: #{item.try(method)}" }
153
+ end
154
+
155
+ def log_line(item)
156
+ method = item.respond_to?(:to_log) ? :to_log : :to_s
157
+ yield method
158
+ end
159
+
160
+ def log_interrupt_callback_start
161
+ logger.info '...user interrupted, calling interrupt callback on migration strategy...'
162
+ end
163
+
164
+ def log_interrupt_callback_finish
165
+ logger.info '......end of interrupt callback after user interruption'
166
+ end
167
+
168
+ def process_more?
169
+ @number_of_items_to_process.nil? || @number_of_items_processed < @number_of_items_to_process
170
+ end
171
+
172
+ def increment_progress_bar
173
+ return unless progress_bar?
174
+ @progress_bar.increment
175
+ end
176
+
177
+ def handle_user_interrupt
178
+ log_interrupt_callback_start
179
+ migration_strategy.interrupt_callback if migration_strategy.respond_to?(:interrupt_callback)
180
+ log_interrupt_callback_finish
181
+ true
182
+ end
183
+
184
+ # The following is to correctly calculate the width of the terminal
185
+ # so that the progress bar occupies the whole width
186
+ #
187
+ def terminal_width
188
+ TerminalWidthCalculator.calculate
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,39 @@
1
+ module KitCat
2
+ module TerminalWidthCalculator
3
+ class << self
4
+ def calculate
5
+ default_width = 80
6
+
7
+ term_width = calculate_term_width
8
+
9
+ term_width > 0 ? term_width : default_width
10
+ end
11
+
12
+ private
13
+
14
+ def calculate_term_width
15
+ if ENV['COLUMNS'] =~ /^\d+$/
16
+ ENV['COLUMNS'].to_i
17
+ elsif tput_case?
18
+ `tput cols`.to_i
19
+ elsif stty_case?
20
+ `stty size`.scan(/\d+/).map(&:to_i)[1]
21
+ end
22
+ rescue
23
+ 0
24
+ end
25
+
26
+ def tput_case?
27
+ (RUBY_PLATFORM =~ /java/ || !STDIN.tty? && ENV['TERM']) && shell_command_exists?('tput')
28
+ end
29
+
30
+ def stty_case?
31
+ STDIN.tty? && shell_command_exists?('stty')
32
+ end
33
+
34
+ def shell_command_exists?(command)
35
+ ENV['PATH'].split(File::PATH_SEPARATOR).any? { |d| File.exist? File.join(d, command) }
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/kitcat.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'kitcat/framework'
2
+ require 'kitcat/terminal_width_calculator'
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitcat
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Simply Business
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-progressbar
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activemodel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: coveralls
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: bundler-audit
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
+ description: initially created for data migrations. Provides logging, progess bar
126
+ and graceful handling
127
+ email:
128
+ - tech@simplybusiness.co.uk
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - Rakefile
134
+ - lib/kitcat/framework.rb
135
+ - lib/kitcat/terminal_width_calculator.rb
136
+ - lib/kitcat.rb
137
+ - LICENSE
138
+ - CONTRIBUTING.md
139
+ - README.md
140
+ homepage: https://github.com/simplybusiness/kitcat
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.0.14
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: a framework to support data processing
164
+ test_files: []