flat_kit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +46 -0
  3. data/HISTORY.md +5 -0
  4. data/LICENSE.txt +21 -0
  5. data/Manifest.txt +66 -0
  6. data/README.md +80 -0
  7. data/Rakefile +20 -0
  8. data/bin/fk +4 -0
  9. data/lib/flat_kit.rb +23 -0
  10. data/lib/flat_kit/cli.rb +80 -0
  11. data/lib/flat_kit/command.rb +53 -0
  12. data/lib/flat_kit/command/cat.rb +93 -0
  13. data/lib/flat_kit/command/merge.rb +88 -0
  14. data/lib/flat_kit/command/sort.rb +88 -0
  15. data/lib/flat_kit/descendant_tracker.rb +27 -0
  16. data/lib/flat_kit/error.rb +5 -0
  17. data/lib/flat_kit/format.rb +34 -0
  18. data/lib/flat_kit/input.rb +32 -0
  19. data/lib/flat_kit/input/file.rb +53 -0
  20. data/lib/flat_kit/input/io.rb +54 -0
  21. data/lib/flat_kit/internal_node.rb +84 -0
  22. data/lib/flat_kit/jsonl.rb +8 -0
  23. data/lib/flat_kit/jsonl/format.rb +25 -0
  24. data/lib/flat_kit/jsonl/reader.rb +30 -0
  25. data/lib/flat_kit/jsonl/record.rb +84 -0
  26. data/lib/flat_kit/jsonl/writer.rb +45 -0
  27. data/lib/flat_kit/leaf_node.rb +71 -0
  28. data/lib/flat_kit/logger.rb +39 -0
  29. data/lib/flat_kit/merge.rb +35 -0
  30. data/lib/flat_kit/merge_tree.rb +104 -0
  31. data/lib/flat_kit/output.rb +32 -0
  32. data/lib/flat_kit/output/file.rb +55 -0
  33. data/lib/flat_kit/output/io.rb +73 -0
  34. data/lib/flat_kit/reader.rb +61 -0
  35. data/lib/flat_kit/record.rb +83 -0
  36. data/lib/flat_kit/sentinel_internal_node.rb +37 -0
  37. data/lib/flat_kit/sentinel_leaf_node.rb +37 -0
  38. data/lib/flat_kit/sort.rb +35 -0
  39. data/lib/flat_kit/writer.rb +38 -0
  40. data/lib/flat_kit/xsv.rb +8 -0
  41. data/lib/flat_kit/xsv/format.rb +25 -0
  42. data/lib/flat_kit/xsv/reader.rb +45 -0
  43. data/lib/flat_kit/xsv/record.rb +90 -0
  44. data/lib/flat_kit/xsv/writer.rb +70 -0
  45. data/tasks/default.rake +242 -0
  46. data/tasks/extension.rake +38 -0
  47. data/tasks/man.rake +7 -0
  48. data/tasks/this.rb +208 -0
  49. data/test/device_dataset.rb +117 -0
  50. data/test/input/test_file.rb +73 -0
  51. data/test/input/test_io.rb +93 -0
  52. data/test/jsonl/test_format.rb +22 -0
  53. data/test/jsonl/test_reader.rb +49 -0
  54. data/test/jsonl/test_record.rb +61 -0
  55. data/test/jsonl/test_writer.rb +68 -0
  56. data/test/output/test_file.rb +60 -0
  57. data/test/output/test_io.rb +104 -0
  58. data/test/test_conversions.rb +45 -0
  59. data/test/test_format.rb +24 -0
  60. data/test/test_helper.rb +26 -0
  61. data/test/test_merge.rb +40 -0
  62. data/test/test_merge_tree.rb +64 -0
  63. data/test/test_version.rb +11 -0
  64. data/test/xsv/test_format.rb +22 -0
  65. data/test/xsv/test_reader.rb +61 -0
  66. data/test/xsv/test_record.rb +69 -0
  67. data/test/xsv/test_writer.rb +68 -0
  68. metadata +237 -0
@@ -0,0 +1,8 @@
1
+ module FlatKit
2
+ module Xsv
3
+ end
4
+ end
5
+ require 'flat_kit/xsv/record'
6
+ require 'flat_kit/xsv/reader'
7
+ require 'flat_kit/xsv/writer'
8
+ require 'flat_kit/xsv/format'
@@ -0,0 +1,25 @@
1
+ module FlatKit
2
+ module Xsv
3
+ class Format < ::FlatKit::Format
4
+ def self.format_name
5
+ "xsv"
6
+ end
7
+
8
+ def self.handles?(filename)
9
+ parts = filename.split(".")
10
+ %w[ csv tsv txt ].each do |ext|
11
+ return true if parts.include?(ext)
12
+ end
13
+ return false
14
+ end
15
+
16
+ def self.reader
17
+ ::FlatKit::Xsv::Reader
18
+ end
19
+
20
+ def self.writer
21
+ ::FlatKit::Xsv::Writer
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,45 @@
1
+ require 'csv'
2
+
3
+ module FlatKit
4
+ module Xsv
5
+ class Reader < ::FlatKit::Reader
6
+ attr_reader :input
7
+ attr_reader :count
8
+ attr_reader :fields
9
+
10
+ def self.format_name
11
+ ::FlatKit::Xsv::Format.format_name
12
+ end
13
+
14
+ def self.default_csv_options
15
+ {
16
+ headers: :first_row,
17
+ converters: :numeric,
18
+ return_headers: false
19
+ }
20
+ end
21
+
22
+ def initialize(source:, compare_fields: :none, **csv_options)
23
+ super(source: source, compare_fields: compare_fields)
24
+ @input = ::FlatKit::Input.from(source)
25
+ @count = 0
26
+ @csv_options = Reader.default_csv_options.merge(csv_options)
27
+ @fields = nil
28
+ @csv = CSV.new(input.io, **@csv_options)
29
+ end
30
+
31
+ def each
32
+ @csv.each do |row|
33
+ @fields = row.headers if @fields.nil?
34
+ record = ::FlatKit::Xsv::Record.new(data: row, compare_fields: compare_fields)
35
+ @count += 1
36
+ yield record
37
+ end
38
+ input.close
39
+ rescue => e
40
+ ::FlatKit.logger.error "Error reading xsv records from #{input.name}: #{e}"
41
+ raise ::FlatKit::Error, e
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,90 @@
1
+ require 'csv'
2
+ require 'flat_kit/record'
3
+
4
+ module FlatKit
5
+ module Xsv
6
+ class Record < ::FlatKit::Record
7
+ attr_reader :ordered_fields
8
+
9
+ def self.format_name
10
+ ::FlatKit::Xsv::Format.format_name
11
+ end
12
+
13
+ def self.from_record(record, ordered_fields: nil)
14
+ if record.instance_of?(FlatKit::Xsv::Record) then
15
+ new(data: record.data, compare_fields: record.compare_fields)
16
+ else
17
+ new(data: nil, compare_fields: record.compare_fields,
18
+ ordered_fields: nil,
19
+ complete_structured_data: record.to_hash)
20
+ end
21
+ end
22
+
23
+ def initialize(data:, compare_fields: :none,
24
+ ordered_fields: :auto,
25
+ complete_structured_data: nil)
26
+ super(data: data, compare_fields: compare_fields)
27
+
28
+ @complete_structured_data = complete_structured_data
29
+ @ordered_fields = ordered_fields
30
+
31
+ if data.nil? && (complete_structured_data.nil? || complete_structured_data.empty?) then
32
+ raise FlatKit::Error,
33
+ "#{self.class} requires initialization from data: or complete_structured_data:"
34
+ end
35
+
36
+ resolve_ordered_fields
37
+ end
38
+
39
+ def [](key)
40
+ return nil unless @compare_fields.include?(key)
41
+ if data.nil? && !@complete_structured_data.nil? then
42
+ @complete_structured_data[key]
43
+ else
44
+ data[key]
45
+ end
46
+ end
47
+
48
+ def complete_structured_data
49
+ @complete_structured_data ||= data.to_hash
50
+ end
51
+ alias to_hash complete_structured_data
52
+
53
+ def to_a
54
+ return data.fields unless data.nil?
55
+
56
+ Array.new.tap do |a|
57
+ @ordered_fields.each do |field|
58
+ a << @complete_structured_data[field]
59
+ end
60
+ end
61
+ end
62
+
63
+ # convert to a csv line,
64
+ #
65
+ # First we use data if it is there since that should be a CSV::Row
66
+ #
67
+ # Next, if that doesn't work - iterate over the ordered fields and use the
68
+ # yield the values from complete_structured_data in that order
69
+ #
70
+ # And finally, if that doesn'twork, then just use complete structured data
71
+ # values in that order.
72
+ def to_s
73
+ return data.to_csv unless data.nil?
74
+ CSV.generate_line(to_a)
75
+ end
76
+
77
+ private
78
+
79
+ def resolve_ordered_fields
80
+ if (@ordered_fields == :auto) || (@ordered_fields.nil? || @ordered_fields.empty?) then
81
+ if @data.nil? || @data.empty? then
82
+ @ordered_fields = complete_structured_data.keys
83
+ else
84
+ @ordered_fields = @data.headers
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,70 @@
1
+ module FlatKit
2
+ module Xsv
3
+ class Writer < ::FlatKit::Writer
4
+ attr_reader :output
5
+ attr_reader :count
6
+ attr_reader :fields
7
+
8
+ def self.format_name
9
+ ::FlatKit::Xsv::Format.format_name
10
+ end
11
+
12
+ def self.default_csv_options
13
+ {
14
+ headers: nil,
15
+ write_headers: true
16
+ }
17
+ end
18
+
19
+ def initialize(destination:, fields: :auto, **csv_options)
20
+ super(destination: destination)
21
+ @fields = fields
22
+ @output = ::FlatKit::Output.from(@destination)
23
+ @count = 0
24
+ @we_write_the_header = nil
25
+ @csv_options = Writer.default_csv_options.dup
26
+
27
+ if @fields == :auto then
28
+ @we_write_the_header = true
29
+ else
30
+ @csv_options.merge!(headers: fields)
31
+ @we_write_the_header = false
32
+ end
33
+
34
+ @csv_options.merge!(csv_options)
35
+ @csv = CSV.new(output.io, **@csv_options)
36
+ end
37
+
38
+ def write(record)
39
+ case record
40
+ when FlatKit::Xsv::Record
41
+ write_record(record)
42
+ when FlatKit::Record
43
+ converted_record = ::FlatKit::Xsv::Record.from_record(record, ordered_fields: @fields)
44
+ write_record(converted_record)
45
+ else
46
+ raise FlatKit::Error, "Unable to write records of type #{record.class}"
47
+ end
48
+ rescue FlatKit::Error => fe
49
+ raise fe
50
+ rescue => e
51
+ ::FlatKit.logger.error "Error reading jsonl records from #{output.name}: #{e}"
52
+ raise ::FlatKit::Error, e
53
+ end
54
+
55
+ def close
56
+ @output.close
57
+ end
58
+
59
+ private
60
+
61
+ def write_record(record)
62
+ if @we_write_the_header && @count == 0 then
63
+ @csv << record.ordered_fields
64
+ end
65
+ @count += 1
66
+ @csv << record.to_a
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,242 @@
1
+ # vim: syntax=ruby
2
+ require 'rake/clean'
3
+ require 'digest'
4
+ #------------------------------------------------------------------------------
5
+ # If you want to Develop on this project just run 'rake develop' and you'll
6
+ # have all you need to get going. If you want to use bundler for development,
7
+ # then run 'rake develop:using_bundler'
8
+ #------------------------------------------------------------------------------
9
+ namespace :develop do
10
+
11
+ # Install all the development and runtime dependencies of this gem using the
12
+ # gemspec.
13
+ task :default => 'Gemfile' do
14
+ require 'rubygems/dependency_installer'
15
+ installer = ::Gem::DependencyInstaller.new
16
+ puts "Installing bundler..."
17
+ installer.install 'bundler'
18
+ sh 'bundle install'
19
+ puts "\n\nNow run 'rake test'"
20
+ end
21
+
22
+ # Create a Gemfile that just references the gemspec
23
+ file 'Gemfile' => :gemspec do
24
+ File.open( "Gemfile", "w+" ) do |f|
25
+ f.puts "# DO NOT EDIT - This file is automatically generated"
26
+ f.puts "# Make changes to Manifest.txt and/or Rakefile and regenerate"
27
+ f.puts 'source "https://rubygems.org"'
28
+ f.puts 'gemspec'
29
+ end
30
+ end
31
+ end
32
+ desc "Bootstrap development"
33
+ task :develop => "develop:default"
34
+
35
+ #------------------------------------------------------------------------------
36
+ # Minitest - standard TestTask
37
+ #------------------------------------------------------------------------------
38
+ begin
39
+ require 'rake/testtask'
40
+ Rake::TestTask.new( :test ) do |t|
41
+ t.ruby_opts = %w[ -w ]
42
+ t.libs = %w[ lib spec test ]
43
+ t.pattern = "{test,spec}/**/{test_*,*_spec}.rb"
44
+ end
45
+
46
+ task :test_requirements
47
+ task :test => :test_requirements
48
+ task :default => :test
49
+ rescue LoadError
50
+ This.task_warning( 'test' )
51
+ end
52
+
53
+ #------------------------------------------------------------------------------
54
+ # RDoc - standard rdoc rake task, although we must make sure to use a more
55
+ # recent version of rdoc since it is the one that has 'tomdoc' markup
56
+ #------------------------------------------------------------------------------
57
+ begin
58
+ gem 'rdoc' # otherwise we get the wrong task from stdlib
59
+ require 'rdoc/task'
60
+ RDoc::Task.new do |t|
61
+ t.markup = 'tomdoc'
62
+ t.rdoc_dir = 'doc'
63
+ t.main = 'README.md'
64
+ t.title = "#{This.name} #{This.version}"
65
+ t.rdoc_files.include( FileList['*.{rdoc,md,txt}'], FileList['ext/**/*.c'],
66
+ FileList['lib/**/*.rb'] )
67
+ end
68
+ rescue StandardError, LoadError
69
+ This.task_warning( 'rdoc' )
70
+ end
71
+
72
+ #------------------------------------------------------------------------------
73
+ # Coverage - optional code coverage, rcov for 1.8 and simplecov for 1.9, so
74
+ # for the moment only rcov is listed.
75
+ #------------------------------------------------------------------------------
76
+ begin
77
+ require 'simplecov'
78
+ desc 'Run tests with code coverage'
79
+ task :coverage do
80
+ ENV['COVERAGE'] = 'true'
81
+ Rake::Task[:test].execute
82
+ end
83
+ CLOBBER << 'coverage' if File.directory?( 'coverage' )
84
+ rescue LoadError
85
+ This.task_warning( 'simplecov' )
86
+ end
87
+
88
+ #------------------------------------------------------------------------------
89
+ # Manifest - We want an explicit list of thos files that are to be packaged in
90
+ # the gem. Most of this is from Hoe.
91
+ #------------------------------------------------------------------------------
92
+ namespace 'manifest' do
93
+ desc "Check the manifest"
94
+ task :check => :clean do
95
+ files = FileList["**/*", ".*"].exclude( This.exclude_from_manifest ).to_a.sort
96
+ files = files.select{ |f| File.file?( f ) }
97
+
98
+ tmp = "Manifest.tmp"
99
+ File.open( tmp, 'w' ) do |f|
100
+ f.puts files.join("\n")
101
+ end
102
+
103
+ begin
104
+ sh "diff -du Manifest.txt #{tmp}"
105
+ ensure
106
+ rm tmp
107
+ end
108
+ puts "Manifest looks good"
109
+ end
110
+
111
+ desc "Generate the manifest"
112
+ task :generate => :clean do
113
+ files = %x[ git ls-files ].split("\n").sort
114
+ files.reject! { |f| f =~ This.exclude_from_manifest }
115
+ File.open( "Manifest.txt", "w" ) do |f|
116
+ f.puts files.join("\n")
117
+ end
118
+ end
119
+ end
120
+
121
+ #------------------------------------------------------------------------------
122
+ # Fixme - look for fixmes and report them
123
+ #------------------------------------------------------------------------------
124
+ namespace :fixme do
125
+ task :default => 'manifest:check' do
126
+ This.manifest.each do |file|
127
+ next if file == __FILE__
128
+ next unless file =~ %r/(txt|rb|md|rdoc|css|html|xml|css)\Z/
129
+ puts "FIXME: Rename #{file}" if file =~ /fixme/i
130
+ IO.readlines( file ).each_with_index do |line, idx|
131
+ prefix = "FIXME: #{file}:#{idx+1}".ljust(42)
132
+ puts "#{prefix} => #{line.strip}" if line =~ /fixme/i
133
+ end
134
+ end
135
+ end
136
+
137
+ def fixme_project_root
138
+ This.project_path( '../fixme' )
139
+ end
140
+
141
+ def fixme_project_path( subtree )
142
+ fixme_project_root.join( subtree )
143
+ end
144
+
145
+ def local_fixme_files
146
+ This.manifest.select { |p| p =~ %r|^tasks/| }
147
+ end
148
+
149
+ def outdated_fixme_files
150
+ local_fixme_files.select do |local|
151
+ upstream = fixme_project_path( local )
152
+ upstream.exist? &&
153
+ ( Digest::SHA256.file( local ) != Digest::SHA256.file( upstream ) )
154
+ end
155
+ end
156
+
157
+ def fixme_up_to_date?
158
+ outdated_fixme_files.empty?
159
+ end
160
+
161
+ desc "See if the fixme tools are outdated"
162
+ task :outdated do
163
+ if fixme_up_to_date? then
164
+ puts "Fixme files are up to date."
165
+ else
166
+ outdated_fixme_files.each do |f|
167
+ puts "#{f} is outdated"
168
+ end
169
+ end
170
+ end
171
+
172
+ desc "Update outdated fixme files"
173
+ task :update do
174
+ if fixme_up_to_date? then
175
+ puts "Fixme files are already up to date."
176
+ else
177
+ puts "Updating fixme files:"
178
+ outdated_fixme_files.each do |local|
179
+ upstream = fixme_project_path( local )
180
+ puts " * #{local}"
181
+ FileUtils.cp( upstream, local )
182
+ end
183
+ puts "Use your git commands as appropriate."
184
+ end
185
+ end
186
+ end
187
+ desc "Look for fixmes and report them"
188
+ task :fixme => "fixme:default"
189
+
190
+ #------------------------------------------------------------------------------
191
+ # Gem Specification
192
+ #------------------------------------------------------------------------------
193
+ # Really this is only here to support those who use bundler
194
+ desc "Build the #{This.name}.gemspec file"
195
+ task :gemspec do
196
+ File.open( This.gemspec_file, "wb+" ) do |f|
197
+ f.puts "# DO NOT EDIT - This file is automatically generated"
198
+ f.puts "# Make changes to Manifest.txt and/or Rakefile and regenerate"
199
+ f.write This.platform_gemspec.to_ruby
200
+ end
201
+ end
202
+
203
+ # .rbc files from ruby 2.0
204
+ CLOBBER << FileList["**/*.rbc"]
205
+
206
+ # The standard gem packaging task, everyone has it.
207
+ require 'rubygems/package_task'
208
+ ::Gem::PackageTask.new( This.platform_gemspec ) do
209
+ # nothing
210
+ end
211
+
212
+ #------------------------------------------------------------------------------
213
+ # Release - the steps we go through to do a final release, this is pulled from
214
+ # a compbination of mojombo's rakegem, hoe and hoe-git
215
+ #
216
+ # 1) make sure we are on the main branch
217
+ # 2) make sure there are no uncommitted items
218
+ # 3) check the manifest and make sure all looks good
219
+ # 4) build the gem
220
+ # 5) do an empty commit to have the commit message of the version
221
+ # 6) tag that commit as the version
222
+ # 7) push main
223
+ # 8) push the tag
224
+ # 7) pus the gem
225
+ #------------------------------------------------------------------------------
226
+ task :release_check do
227
+ unless `git branch` =~ /^\* main/
228
+ abort "You must be on the main branch to release!"
229
+ end
230
+ unless `git status` =~ /^nothing to commit/m
231
+ abort "Nope, sorry, you have unfinished business"
232
+ end
233
+ end
234
+
235
+ desc "Create tag v#{This.version}, build and push #{This.platform_gemspec.full_name} to rubygems.org"
236
+ task :release => [ :release_check, 'manifest:check', :gem ] do
237
+ sh "git commit --allow-empty -a -m 'Release #{This.version}'"
238
+ sh "git tag -a -m 'v#{This.version}' v#{This.version}"
239
+ sh "git push origin main"
240
+ sh "git push origin v#{This.version}"
241
+ sh "gem push pkg/#{This.platform_gemspec.full_name}.gem"
242
+ end