rightmove_blm 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08bd05a65c0404a94ac0923c3ac48b338d7714b6f0b53dd6b879607e501adced'
4
- data.tar.gz: 294e256eb0bc617199816ef32c4ee5f92c785da180ef88e4fcd816e2eb59e86d
3
+ metadata.gz: a4ec5f3eee9950c331f0a1dd23129b47d6917c332fae762df37346f67c3ae15b
4
+ data.tar.gz: e803eb23307b33115103e2cd99c94398d272576db5206670eff24c64f13f72af
5
5
  SHA512:
6
- metadata.gz: 351167830c63ca9037d91300d1081d6afa8d665c5fc3bcdabbbc5f7e4304784ad5738c7d9111878b33a8c67cb8a2a56c1d715bf8db9654ef7d8413c6ee805753
7
- data.tar.gz: 9ecc44105fbebec4e3f07e6596f9ad0336368f1e7cac604c4d728a32232905d16f0740153b298976c2e2101ccb6cdd3455d1aa8017d586a25093823a16224b1a
6
+ metadata.gz: 784c8aad75336281c6e7bc59a0a16866929f62268542d0ebf93c5bccb5a21b1e651a91b1edeca885d571ab18bdd35289afd27937316ff8a5c473d44ad612c760
7
+ data.tar.gz: 9f8dc40c0e294360d4ca3b7b713da7922b4c9a90795517f98fcd71d13b9ba22e21435aa5f984618d3d30aabb91a6ffa0fad7640d248339a2b59c1c20453e4f96
data/.gitignore CHANGED
@@ -15,6 +15,8 @@ doc
15
15
  pkg
16
16
 
17
17
  .rspec_status
18
+
19
+ *.gem
18
20
  # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
19
21
  #
20
22
  # * Create a file at ~/.gitignore
data/.rubocop.yml CHANGED
@@ -1,6 +1,9 @@
1
+ require:
2
+ - rubocop-rspec
3
+
1
4
  AllCops:
2
5
  NewCops: enable
3
6
 
4
7
  Metrics/BlockLength:
5
8
  Exclude:
6
- - 'spec/blm_spec.rb'
9
+ - 'spec/**/*_spec.rb'
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.5.9
1
+ 2.7.7
data/Gemfile.lock CHANGED
@@ -3,16 +3,17 @@ GEM
3
3
  specs:
4
4
  ast (2.4.2)
5
5
  concurrent-ruby (1.1.8)
6
- devpack (0.3.2)
6
+ devpack (0.3.3)
7
7
  diff-lcs (1.4.4)
8
8
  i18n (1.8.10)
9
9
  concurrent-ruby (~> 1.0)
10
+ json (2.6.3)
10
11
  paint (2.2.1)
11
- parallel (1.20.1)
12
- parser (3.0.1.0)
12
+ parallel (1.22.1)
13
+ parser (3.1.3.0)
13
14
  ast (~> 2.4.1)
14
- rainbow (3.0.0)
15
- regexp_parser (2.1.1)
15
+ rainbow (3.1.1)
16
+ regexp_parser (2.6.1)
16
17
  rexml (3.2.5)
17
18
  rspec (3.10.0)
18
19
  rspec-core (~> 3.10.0)
@@ -32,17 +33,18 @@ GEM
32
33
  diff-lcs (>= 1.2.0, < 2.0)
33
34
  rspec-support (~> 3.10.0)
34
35
  rspec-support (3.10.2)
35
- rubocop (1.13.0)
36
+ rubocop (1.41.1)
37
+ json (~> 2.3)
36
38
  parallel (~> 1.10)
37
- parser (>= 3.0.0.0)
39
+ parser (>= 3.1.2.1)
38
40
  rainbow (>= 2.2.2, < 4.0)
39
41
  regexp_parser (>= 1.8, < 3.0)
40
- rexml
41
- rubocop-ast (>= 1.2.0, < 2.0)
42
+ rexml (>= 3.2.5, < 4.0)
43
+ rubocop-ast (>= 1.23.0, < 2.0)
42
44
  ruby-progressbar (~> 1.7)
43
45
  unicode-display_width (>= 1.4.0, < 3.0)
44
- rubocop-ast (1.4.1)
45
- parser (>= 2.7.1.5)
46
+ rubocop-ast (1.24.0)
47
+ parser (>= 3.1.1.0)
46
48
  rubocop-rspec (2.2.0)
47
49
  rubocop (~> 1.0)
48
50
  rubocop-ast (>= 1.1.0)
@@ -50,7 +52,7 @@ GEM
50
52
  strong_versions (0.4.5)
51
53
  i18n (>= 0.5)
52
54
  paint (~> 2.0)
53
- unicode-display_width (2.0.0)
55
+ unicode-display_width (2.3.0)
54
56
 
55
57
  PLATFORMS
56
58
  ruby
@@ -65,4 +67,4 @@ DEPENDENCIES
65
67
  strong_versions (~> 0.4.5)
66
68
 
67
69
  BUNDLED WITH
68
- 2.2.16
70
+ 2.3.7
data/README.md CHANGED
@@ -10,6 +10,8 @@ This library is not affiliated with or endorsed by _Rightmove Plc_ in any way.
10
10
 
11
11
  ### Loading a BLM file
12
12
 
13
+ #### RightmoveBLM::Document
14
+
13
15
  Load a BLM file by passing the `source` parameter to `RightmoveBLM::Document.new`:
14
16
 
15
17
  ```ruby
@@ -20,17 +22,32 @@ The returned `RightmoveBLM::Document` instance implements:
20
22
 
21
23
  * `#header` - the header containing information about the document's structure.
22
24
  * `#definition` - the field list contained in the document.
23
- * `#data` - an array of `RightmoveBLM::Row` objects (use dot notation to access fields, e.g. `row.foo` to access the "foo" field).
25
+ * `#rows` - an array of `RightmoveBLM::Row` objects.
26
+ * `#valid?` - `true` if no rows have errors, `false` if any rows have errors.
27
+ * `#errors` - all error messages for all invalid rows.
28
+ * `#version` - the version of the document format as a string (e.g. `'3'`).
29
+ * `#international?` - `true` if document meets the [Rightmove International](https://www.rightmove.co.uk/ps/pdf/guides/RightmoveDatafeedFormatV3iOVS_1.6.pdf) specification, `false` otherwise.
30
+
31
+ #### RightmoveBLM::Row
32
+
33
+ `RightmoveBLM::Row` implements:
24
34
 
25
- `RightmoveBLM::Row` also implements `#to_h` which provides a _Hash_ of the row data.
35
+ * `#valid?` - `true` if no errors were encountered, false otherwise.
36
+ * `#errors` - an array of error strings (empty if no errors present).
37
+ * `#to_h` (or `#attributes`) - a hash of row attributes (`nil` if errors encountered).
38
+ * `#method_missing` - allows accessing row attributes by dot notation (e.g. `row.address_1`).
26
39
 
27
40
  #### Example
28
41
 
29
42
  ```ruby
30
- blm.data.each do |row|
43
+ blm.data.select(&:valid?).each do |row|
31
44
  puts row.address_1
32
45
  puts row.to_h
33
46
  end
47
+
48
+ blm.data.reject(&:valid?).each do |row|
49
+ puts "Errors: #{row.errors.join(', ')}"
50
+ end
34
51
  ```
35
52
 
36
53
  ### Writing BLM data
@@ -5,7 +5,7 @@ module RightmoveBLM
5
5
  class Document
6
6
  def self.from_array_of_hashes(array)
7
7
  date = Time.now.utc.strftime('%d-%b-%Y %H:%M').upcase
8
- header = { version: '3', eof: '^', eor: '~', "property count": array.size.to_s, "generated date": date }
8
+ header = { version: '3', eof: '^', eor: '~', 'property count': array.size.to_s, 'generated date': date }
9
9
  new(header: header, definition: array.first.keys.map(&:to_sym), data: array)
10
10
  end
11
11
 
@@ -13,7 +13,15 @@ module RightmoveBLM
13
13
  @source = source
14
14
  @header = header
15
15
  @definition = definition
16
- @data = data&.map { |row| Row.new(row) }
16
+ initialize_with_data(data) unless data.nil?
17
+ end
18
+
19
+ def inspect
20
+ %(<##{self.class.name} version=#{version} rows=#{rows.size} valid=#{valid?} errors=#{errors.size}>)
21
+ end
22
+
23
+ def to_s
24
+ inspect
17
25
  end
18
26
 
19
27
  def to_blm
@@ -43,27 +51,49 @@ module RightmoveBLM
43
51
  end.compact
44
52
  end
45
53
 
46
- def data
47
- @data ||= contents.split(header[:eor]).map do |line|
48
- row(line)
49
- end
54
+ def rows
55
+ data
56
+ end
57
+
58
+ def errors
59
+ @errors ||= data.reject(&:valid?).flat_map(&:errors)
60
+ end
61
+
62
+ def valid?
63
+ errors.empty?
64
+ end
65
+
66
+ def version
67
+ header[:version]
68
+ end
69
+
70
+ def international?
71
+ %w[H1 3I].include?(version)
50
72
  end
51
73
 
52
74
  private
53
75
 
76
+ def initialize_with_data(data)
77
+ @data = data.each_with_index.map { |hash, index| Row.from_attributes(hash, index: index) }
78
+ end
79
+
80
+ def data
81
+ @data ||= contents.split(header[:eor]).each_with_index.map do |line, index|
82
+ Row.new(index: index, data: line, separator: header[:eof], definition: definition)
83
+ end
84
+ end
85
+
54
86
  def contents(section = :data)
55
87
  marker = "##{section.to_s.upcase}#"
56
- start = @source.index(marker) + marker.size
57
- finish = @source.index('#', start) - 1
88
+ start = verify(:start, @source.index(marker)) + marker.size
89
+ finish = verify(:end, @source.index('#', start)) - 1
58
90
  @source[start..finish].strip
59
91
  end
60
92
 
61
- def row(line)
62
- entry = {}
63
- line.split(header[:eof]).each_with_index do |field, index|
64
- entry[definition[index].to_sym] = field.strip
65
- end
66
- Row.new(entry)
93
+ def verify(type, val)
94
+ return val unless val.nil?
95
+
96
+ raise ParserError, "Unable to parse document: could not detect #{type} marker."
67
97
  end
68
98
 
69
99
  def generated_date
@@ -3,22 +3,72 @@
3
3
  module RightmoveBLM
4
4
  # A row in a BLM document.
5
5
  class Row
6
- attr_accessor :attributes
6
+ attr_reader :index
7
7
 
8
- def initialize(hash)
9
- @attributes = hash
8
+ def self.from_attributes(attributes, index:)
9
+ new(index: index, data: nil, separator: nil, definition: attributes.keys, attributes: attributes)
10
+ end
11
+
12
+ def initialize(index:, data:, separator:, definition:, attributes: nil)
13
+ @index = index
14
+ @data = data
15
+ @separator = separator
16
+ @definition = definition
17
+ @attributes = attributes
18
+ @fields = attributes.keys unless attributes.nil?
10
19
  end
11
20
 
12
21
  def to_h
13
- @attributes
22
+ attributes
23
+ end
24
+
25
+ def valid?
26
+ return false unless field_size_valid?
27
+
28
+ true
29
+ end
30
+
31
+ def errors
32
+ return [] if valid?
33
+
34
+ errors = []
35
+ errors << field_size_mismatch_message unless field_size_valid?
36
+ errors
37
+ end
38
+
39
+ def attributes
40
+ return nil unless valid?
41
+
42
+ @attributes ||= fields.each_with_index.to_h do |field, field_index|
43
+ [definition[field_index].to_sym, field.strip]
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :data, :separator, :definition
50
+
51
+ def field_size_mismatch_message
52
+ "Field size mismatch in row #{index}. Expected: #{definition.size} fields, found: #{fields.size}"
53
+ end
54
+
55
+ def fields
56
+ @fields ||= data.split(separator)
57
+ end
58
+
59
+ def field_size_valid?
60
+ definition.size >= fields.size
14
61
  end
15
62
 
16
63
  def method_missing(method, *_arguments)
17
- return @attributes[method] unless @attributes[method].nil?
64
+ return super if attributes.nil?
65
+ return attributes[method] unless attributes[method].nil?
66
+
67
+ super
18
68
  end
19
69
 
20
70
  def respond_to_missing?(method, _ = false)
21
- !@attributes[method].nil?
71
+ !attributes[method].nil?
22
72
  end
23
73
  end
24
74
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RightmoveBLM
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/rightmove_blm.rb CHANGED
@@ -8,4 +8,6 @@ require 'time'
8
8
  # Rightmove BLM (Bulk Load Mass) data format parsing tools.
9
9
  # https://www.rightmove.co.uk/ps/pdf/guides/RightmoveDatafeedFormatV3iOVS_1.6.pdf
10
10
  module RightmoveBLM
11
+ class Error < StandardError; end
12
+ class ParserError < Error; end
11
13
  end
@@ -5,24 +5,20 @@ require_relative 'lib/rightmove_blm/version'
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'rightmove_blm'
7
7
  spec.version = RightmoveBLM::VERSION
8
- spec.authors = ['Robert Farrell']
8
+ spec.authors = ['Bob Farrell']
9
9
  spec.email = 'git@bob.frl'
10
10
 
11
11
  spec.summary
12
12
  spec.description = 'Parse and generate Rightmove BLM files'
13
- spec.required_ruby_version = '~> 2.5'
13
+ spec.required_ruby_version = '>= 2.7'
14
14
 
15
15
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
16
16
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
17
  end
18
18
 
19
- spec.homepage = 'https://github.com/bobf/rightmove_blm'
19
+ spec.homepage = 'http://github.com/robertmay/blm'
20
20
  spec.licenses = ['MIT']
21
21
  spec.require_paths = ['lib']
22
- spec.rubygems_version = '1.3.7'
23
22
  spec.summary = 'A parser for the Rightmove .blm format'
24
- spec.test_files = [
25
- 'spec/blm_spec.rb',
26
- 'spec/spec_helper.rb'
27
- ]
23
+ spec.metadata['rubygems_mfa_required'] = 'true'
28
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rightmove_blm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - Robert Farrell
7
+ - Bob Farrell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-27 00:00:00.000000000 Z
11
+ date: 2023-05-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Parse and generate Rightmove BLM files
14
14
  email: git@bob.frl
@@ -27,37 +27,33 @@ files:
27
27
  - Makefile
28
28
  - README.md
29
29
  - Rakefile
30
- - VERSION
31
30
  - lib/rightmove_blm.rb
32
31
  - lib/rightmove_blm/document.rb
33
32
  - lib/rightmove_blm/row.rb
34
33
  - lib/rightmove_blm/version.rb
35
34
  - rightmove_blm.gemspec
36
- - spec/blm_spec.rb
37
- - spec/spec_helper.rb
38
- homepage: https://github.com/bobf/rightmove_blm
35
+ homepage: http://github.com/robertmay/blm
39
36
  licenses:
40
37
  - MIT
41
- metadata: {}
38
+ metadata:
39
+ rubygems_mfa_required: 'true'
42
40
  post_install_message:
43
41
  rdoc_options: []
44
42
  require_paths:
45
43
  - lib
46
44
  required_ruby_version: !ruby/object:Gem::Requirement
47
45
  requirements:
48
- - - "~>"
46
+ - - ">="
49
47
  - !ruby/object:Gem::Version
50
- version: '2.5'
48
+ version: '2.7'
51
49
  required_rubygems_version: !ruby/object:Gem::Requirement
52
50
  requirements:
53
51
  - - ">="
54
52
  - !ruby/object:Gem::Version
55
53
  version: '0'
56
54
  requirements: []
57
- rubygems_version: 3.2.3
55
+ rubygems_version: 3.1.6
58
56
  signing_key:
59
57
  specification_version: 4
60
58
  summary: A parser for the Rightmove .blm format
61
- test_files:
62
- - spec/blm_spec.rb
63
- - spec/spec_helper.rb
59
+ test_files: []
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.1
data/spec/blm_spec.rb DELETED
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe RightmoveBLM do
4
- context 'reading a .blm file' do
5
- let(:data) { fixture('example_data.blm') }
6
- let(:blm) { RightmoveBLM::Document.new(source: data.read) }
7
-
8
- it 'should parse settings from the header' do
9
- expect(blm.header).to be_a(Hash)
10
- expect(blm.header[:version]).to eq('3')
11
- expect(blm.header[:eof]).to_not be_nil
12
- expect(blm.header[:eor]).to_not be_nil
13
- end
14
-
15
- it 'should parse the column definition' do
16
- expect(blm.definition).to be_a(Array)
17
- expect(blm.definition.size).to be >= 1
18
- end
19
-
20
- it 'should parse the data into an array of hashes' do
21
- expect(blm.data).to be_a(Array)
22
- expect(blm.data.size).to be >= 1
23
- expect(blm.data).to respond_to(:each, :each_with_index)
24
- blm.data.each { |row| expect(row).to be_a(RightmoveBLM::Row) }
25
- end
26
-
27
- it 'should allow access to data values via methods' do
28
- blm.data.each { |row| expect(row.address_1).to_not be_nil }
29
- end
30
-
31
- it 'should allow access to the @attributes hash directly' do
32
- blm.data.each { |row| expect(row.attributes).to be_a(Hash) }
33
- end
34
- end
35
-
36
- context 'creating a .blm file' do
37
- subject(:document) { RightmoveBLM::Document.from_array_of_hashes(data) }
38
-
39
- let(:data) do
40
- [{ field1: 'row 1 field 1 data', field2: 'row 1 field 2 data', field3: 'row 1 field 3 data' },
41
- { field1: 'row 2 field 1 data', field2: 'row 2 field 2 data', field3: 'row 2 field 3 data' }]
42
- end
43
-
44
- its(:header) { is_expected.to include({ 'property count': '2' }) }
45
- its(:header) { is_expected.to include({ version: '3' }) }
46
- its(:definition) { is_expected.to eql %i[field1 field2 field3] }
47
-
48
- describe '#to_blm' do
49
- let(:loaded_document) { RightmoveBLM::Document.new(source: subject.to_blm) }
50
-
51
- it 'has a property count' do
52
- expect(loaded_document.header[:'property count']).to eql '2'
53
- end
54
-
55
- it 'has a version' do
56
- expect(loaded_document.header[:version]).to eql '3'
57
- end
58
-
59
- it 'has a "generated date" timestamp' do
60
- expect(loaded_document.header[:'generated date']).to start_with Time.now.utc.strftime('%d-%b-%Y').upcase
61
- end
62
-
63
- it 'has property data' do
64
- expect(loaded_document.data.map(&:to_h)).to eql data
65
- end
66
-
67
- it 'has a field definition' do
68
- expect(loaded_document.definition).to eql %w[field1 field2 field3]
69
- end
70
- end
71
- end
72
- end
data/spec/spec_helper.rb DELETED
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/setup'
4
- require 'devpack'
5
- require 'rightmove_blm'
6
-
7
- require 'rspec/file_fixtures'
8
- require 'rspec/its'
9
-
10
- RSpec.configure do |config|
11
- config.example_status_persistence_file_path = '.rspec_status'
12
- config.disable_monkey_patching!
13
- config.expect_with :rspec do |c|
14
- c.syntax = :expect
15
- end
16
- end