fbo 0.0.1

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +27 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +39 -0
  8. data/Rakefile +1 -0
  9. data/fbo.gemspec +30 -0
  10. data/lib/fbo.rb +25 -0
  11. data/lib/fbo/file.rb +14 -0
  12. data/lib/fbo/notice.rb +9 -0
  13. data/lib/fbo/notices.rb +6 -0
  14. data/lib/fbo/notices/amendment.rb +13 -0
  15. data/lib/fbo/notices/award.rb +14 -0
  16. data/lib/fbo/notices/combined_solicitation.rb +14 -0
  17. data/lib/fbo/notices/modification.rb +14 -0
  18. data/lib/fbo/notices/presolicitation.rb +14 -0
  19. data/lib/fbo/notices/sources_sought.rb +13 -0
  20. data/lib/fbo/notices/unknown.rb +7 -0
  21. data/lib/fbo/parser.rb +79 -0
  22. data/lib/fbo/parser/amendment_handler.rb +58 -0
  23. data/lib/fbo/parser/award_handler.rb +62 -0
  24. data/lib/fbo/parser/combined_solicitation_handler.rb +57 -0
  25. data/lib/fbo/parser/modification_handler.rb +66 -0
  26. data/lib/fbo/parser/notice_handler.rb +27 -0
  27. data/lib/fbo/parser/parser_helper.rb +275 -0
  28. data/lib/fbo/parser/presolicitation_handler.rb +57 -0
  29. data/lib/fbo/parser/sources_sought_handler.rb +57 -0
  30. data/lib/fbo/parser/unknown_handler.rb +20 -0
  31. data/lib/fbo/remote_file.rb +39 -0
  32. data/lib/fbo/version.rb +3 -0
  33. data/spec/fbo/file_spec.rb +20 -0
  34. data/spec/fbo/parser/amendment_handler_spec.rb +46 -0
  35. data/spec/fbo/parser/award_handler_spec.rb +51 -0
  36. data/spec/fbo/parser/combined_solicitation_handler_spec.rb +47 -0
  37. data/spec/fbo/parser/modification_handler_spec.rb +47 -0
  38. data/spec/fbo/parser/presolicitation_handler_spec.rb +46 -0
  39. data/spec/fbo/parser/sources_sought_handler_spec.rb +46 -0
  40. data/spec/fbo/parser/unknown_handler_spec.rb +19 -0
  41. data/spec/fbo/parser_spec.rb +85 -0
  42. data/spec/fbo/remote_file_spec.rb +55 -0
  43. data/spec/fixtures/FBOFeed20130331 +5668 -0
  44. data/spec/fixtures/FBOFeed20130404 +45653 -0
  45. data/spec/fixtures/FBOFeed20130406 +10152 -0
  46. data/spec/fixtures/FBOFeed20130407 +6610 -0
  47. data/spec/fixtures/notices/amdcss +26 -0
  48. data/spec/fixtures/notices/award +31 -0
  49. data/spec/fixtures/notices/combine +29 -0
  50. data/spec/fixtures/notices/mod +28 -0
  51. data/spec/fixtures/notices/presol +25 -0
  52. data/spec/fixtures/notices/snote +26 -0
  53. data/spec/fixtures/notices/srcsgt +27 -0
  54. data/spec/spec_helper.rb +23 -0
  55. metadata +154 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 341885ee905b5466fe116ddfbef59dc1ceb9fc1b
4
+ data.tar.gz: 0f9954197c0c2cf290b860756a0e13bbf482f415
5
+ SHA512:
6
+ metadata.gz: df079c191ef00e41a23e3ce5b881131db4ef43ad786f39a4094a6b3b9e329b2d3f96a28406e608dc89cdb9947f13e5f8b5d5bf065787f6e802165a7be423f73d
7
+ data.tar.gz: 102f3ecbb75bb265890745ff7da8ae0a491977b1c0d9be81779d315bcef655ff5bef5c5adc651beb0cfed5dfeae50e316a1e538303895811a85b5e7422b1fd51
@@ -0,0 +1,37 @@
1
+ # Emacs temporary files
2
+ *~
3
+ \#*\#
4
+
5
+ # Bundler artifacts
6
+ .bundle
7
+ bundler_stubs/
8
+ vendor/
9
+
10
+ # Ruby / Rails operational files
11
+ .rvmrc
12
+ .ruby-version
13
+
14
+ # Anything temporary
15
+ *.old
16
+ *.swp
17
+ *.swo
18
+ tmp
19
+
20
+ # Reporting directory
21
+ /reports/
22
+
23
+ # Rubygems artifacts
24
+ *.gem
25
+
26
+ # Test artifacts
27
+ coverage
28
+ spec/reports
29
+ test/tmp
30
+ test/version_tmp
31
+
32
+ # Documentation
33
+ .yardoc
34
+ _yardoc
35
+ doc
36
+ rdoc
37
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format doc
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fbo.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec'
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ FBO (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.2)
10
+ rake (10.0.4)
11
+ rspec (2.13.0)
12
+ rspec-core (~> 2.13.0)
13
+ rspec-expectations (~> 2.13.0)
14
+ rspec-mocks (~> 2.13.0)
15
+ rspec-core (2.13.1)
16
+ rspec-expectations (2.13.0)
17
+ diff-lcs (>= 1.1.3, < 2.0)
18
+ rspec-mocks (2.13.1)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ FBO!
25
+ bundler (~> 1.3)
26
+ rake
27
+ rspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Chris Kottom
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,39 @@
1
+ # FBO
2
+
3
+ Fetch a dated dump file from [FedBizOpps](https://www.fbo.gov) and parse its
4
+ contents into structures suitable for use by other programs.
5
+
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'fbo'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install fbo
20
+
21
+ ## Usage
22
+
23
+ The gem provides a number of
24
+
25
+
26
+
27
+ ## Contributing
28
+
29
+ Code contributions gratefully accepted.
30
+
31
+ 1. Fork it
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create new Pull Request
36
+
37
+ Logging any issues you might notice at the
38
+ [Issues tracker](https://github.com/chriskottom/fbo/issues)
39
+ on GitHub is also a great way to help out.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fbo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "fbo"
8
+ spec.version = FBO::VERSION
9
+ spec.authors = ["Chris Kottom"]
10
+ spec.email = ["chris@chriskottom.com"]
11
+ spec.summary = "Parse and process FBO bulk listings"
12
+ spec.description = <<-EOF
13
+ The FBO gem manages the process of downloading and parsing file-based notice
14
+ information from the Federal Business Opportunities (https://www.fbo.gov/)
15
+ application / database. The FBO feed files include new and updated
16
+ opportunities, information about awarded contracts, and other details
17
+ concerning the offer and disposition of federal government contracts and
18
+ tenders.
19
+ EOF
20
+ spec.homepage = ""
21
+ spec.license = "MIT"
22
+
23
+ spec.files = `git ls-files`.split($/)
24
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
25
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.3"
29
+ spec.add_development_dependency "rake"
30
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "fbo/version"
2
+
3
+ require_relative "fbo/file"
4
+ require_relative "fbo/remote_file"
5
+ require_relative "fbo/notice"
6
+ require_relative "fbo/notices/amendment"
7
+ require_relative "fbo/notices/award"
8
+ require_relative "fbo/notices/combined_solicitation"
9
+ require_relative "fbo/notices/modification"
10
+ require_relative "fbo/notices/presolicitation"
11
+ require_relative "fbo/notices/sources_sought"
12
+ require_relative "fbo/notices/unknown"
13
+ require_relative "fbo/parser"
14
+ require_relative "fbo/parser/notice_handler"
15
+ require_relative "fbo/parser/parser_helper"
16
+ require_relative "fbo/parser/amendment_handler"
17
+ require_relative "fbo/parser/award_handler"
18
+ require_relative "fbo/parser/combined_solicitation_handler"
19
+ require_relative "fbo/parser/modification_handler"
20
+ require_relative "fbo/parser/presolicitation_handler"
21
+ require_relative "fbo/parser/sources_sought_handler"
22
+ require_relative "fbo/parser/unknown_handler"
23
+
24
+ module FBO
25
+ end
@@ -0,0 +1,14 @@
1
+ require 'forwardable'
2
+
3
+ module FBO
4
+ class File
5
+ extend Forwardable
6
+
7
+ attr_reader :file
8
+ def_delegators :@file, :open, :readline, :read
9
+
10
+ def initialize(filename)
11
+ @file = ::File.new(filename)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ module FBO
2
+ module Notice
3
+ def initialize(params = {})
4
+ params.each do |key, value|
5
+ self[key] = value if members.include?(key)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module FBO
2
+ module Notices
3
+ NOTICE_TYPES = %i{ PRESOL COMBINE AMDCSS MOD AWARD JA ITB FAIROPP } +
4
+ %i{ SRCSGT FSTD SNOTE SSALE EPSUPLOAD DELETE ARCHIVE UNARCHIVE }
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module FBO
2
+ module Notices
3
+ class Amendment < Struct.new(:date, :year, :zip, :class_code, :naics_code,
4
+ :agency, :office, :location, :office_address,
5
+ :subject, :solicitation_number, :notice_type,
6
+ :response_date, :archive_date, :contact_info,
7
+ :description, :link_url, :link_description,
8
+ :email_address, :email_description, :setaside,
9
+ :pop_address, :pop_zip, :pop_country)
10
+ include FBO::Notice
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module FBO
2
+ module Notices
3
+ class Award < Struct.new(:date, :year, :zip, :class_code, :naics_code,
4
+ :agency, :office, :location, :office_address,
5
+ :subject, :solicitation_number, :notice_type,
6
+ :description, :contact_info, :award_number,
7
+ :award_amount, :line_number, :award_date,
8
+ :archive_date, :awardee, :awardee_duns, :link_url,
9
+ :link_description, :email_address, :email_description,
10
+ :setaside, :correction)
11
+ include FBO::Notice
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module FBO
2
+ module Notices
3
+ class CombinedSolicitation < Struct.new(:date, :year, :zip, :class_code,
4
+ :naics_code, :agency, :office, :location,
5
+ :office_address, :subject, :solicitation_number,
6
+ :response_date, :archive_date,
7
+ :contact_info, :description, :link_url,
8
+ :link_description, :email_address,
9
+ :email_description, :setaside, :pop_address,
10
+ :pop_zip, :pop_country)
11
+ include FBO::Notice
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module FBO
2
+ module Notices
3
+ class Modification < Struct.new(:date, :year, :zip, :class_code,
4
+ :naics_code, :agency, :office, :location,
5
+ :office_address, :subject, :solicitation_number,
6
+ :notice_type, :response_date, :archive_date,
7
+ :contact_info, :description, :link_url,
8
+ :link_description, :email_address,
9
+ :email_description, :setaside, :pop_address,
10
+ :pop_zip, :pop_country)
11
+ include FBO::Notice
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module FBO
2
+ module Notices
3
+ class Presolicitation < Struct.new(:date, :year, :zip, :class_code,
4
+ :naics_code, :agency, :office, :location,
5
+ :office_address, :subject, :solicitation_number,
6
+ :response_date, :archive_date,
7
+ :contact_info, :description, :link_url,
8
+ :link_description, :email_address,
9
+ :email_description, :setaside, :pop_address,
10
+ :pop_zip, :pop_country)
11
+ include FBO::Notice
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module FBO
2
+ module Notices
3
+ class SourcesSought < Struct.new(:date, :year, :zip, :class_code, :naics_code,
4
+ :agency, :office, :location, :office_address,
5
+ :subject, :solicitation_number,
6
+ :response_date, :archive_date, :contact_info,
7
+ :description, :link_url, :link_description,
8
+ :email_address, :email_description, :setaside,
9
+ :pop_address, :pop_zip, :pop_country)
10
+ include FBO::Notice
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module FBO
2
+ module Notices
3
+ class Unknown < Struct.new(:type, :text)
4
+ include FBO::Notice
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,79 @@
1
+ require "strscan"
2
+
3
+ module FBO
4
+ class Parser
5
+ TAG_PATTERN = /<\w+>/m
6
+ PATTERN = /<([A-Z]+)>.*<\/\1>/m
7
+
8
+ def initialize(file, tag_pattern: TAG_PATTERN)
9
+ @file = file
10
+ @tag_pattern = tag_pattern
11
+ end
12
+
13
+ def notices
14
+ if @notices.nil?
15
+ parse_notices(split_file_contents)
16
+ end
17
+ @notices
18
+ end
19
+
20
+ private
21
+
22
+ def split_file_contents
23
+ contents = @file.read
24
+ contents = cleanup_contents(contents)
25
+ scanner = StringScanner.new(contents)
26
+ text_notices = []
27
+
28
+ while !scanner.eos?
29
+ initial_tag = scanner.check_until(@tag_pattern)
30
+ if initial_tag
31
+ initial_tag.strip!
32
+ else
33
+ break
34
+ end
35
+
36
+ text_notice = scanner.scan_until(/#{ closing_tag_for(initial_tag) }\s*/)
37
+ if text_notice
38
+ text_notices << text_notice.strip
39
+ else
40
+ break
41
+ end
42
+ end
43
+ text_notices
44
+ end
45
+
46
+ def parse_notices(text_notices)
47
+ @notices = []
48
+ text_notices.each do |text|
49
+ if FBO::Parser::PresolicitationHandler.is_presolicitation?(text)
50
+ @notices << FBO::Parser::PresolicitationHandler.parse(text)
51
+ elsif FBO::Parser::CombinedSolicitationHandler.is_combined_solicitation?(text)
52
+ @notices << FBO::Parser::CombinedSolicitationHandler.parse(text)
53
+ elsif FBO::Parser::SourcesSoughtHandler.is_sources_sought?(text)
54
+ @notices << FBO::Parser::SourcesSoughtHandler.parse(text)
55
+ elsif FBO::Parser::AmendmentHandler.is_amendment?(text)
56
+ @notices << FBO::Parser::AmendmentHandler.parse(text)
57
+ elsif FBO::Parser::ModificationHandler.is_modification?(text)
58
+ @notices << FBO::Parser::ModificationHandler.parse(text)
59
+ elsif FBO::Parser::AwardHandler.is_award?(text)
60
+ @notices << FBO::Parser::AwardHandler.parse(text)
61
+ else
62
+ @notices << FBO::Parser::UnknownHandler.parse(text)
63
+ end
64
+ end
65
+ end
66
+
67
+ def cleanup_contents(contents)
68
+ contents
69
+ .encode('UTF-16le', :invalid => :replace, :replace => '')
70
+ .encode('UTF-8')
71
+ .gsub(/\r\n/, "\n")
72
+ .gsub(/^M/, "")
73
+ end
74
+
75
+ def closing_tag_for(tag)
76
+ tag.sub(/</, "</")
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,58 @@
1
+ module FBO::Parser::AmendmentHandler
2
+ TAGS = %w( AMDCSS DATE YEAR CBAC PASSWORD ZIP CLASSCOD NAICS OFFADD NTYPE) +
3
+ %w( AGENCY OFFICE LOCATION SUBJECT SOLNBR RESPDATE ARCHDATE CONTACT DESC ) +
4
+ %w( LINK URL EMAIL ADDRESS SETASIDE POPADDRESS POPZIP POPCOUNTRY \/AMDCSS )
5
+
6
+ ANY_AMDCSS_TAG = TAGS.join("|")
7
+ AMENDMENT_PATTERN = /^<AMDCSS>/
8
+
9
+
10
+ def is_amendment?(text)
11
+ text =~ AMENDMENT_PATTERN
12
+ end
13
+
14
+ def parse(text)
15
+ params = {
16
+ date: date(text),
17
+ year: year(text),
18
+ zip: zip(text),
19
+ class_code: classification_code(text),
20
+ naics_code: naics_code(text),
21
+ agency: agency(text),
22
+ office: office(text),
23
+ location: location(text),
24
+ office_address: office_address(text),
25
+ subject: subject(text),
26
+ solicitation_number: solicitation_number(text),
27
+ notice_type: notice_type(text),
28
+ response_date: response_date(text),
29
+ archive_date: archive_date(text),
30
+ contact_info: contact(text),
31
+ description: description(text),
32
+ link_url: link_url(text),
33
+ link_description: link_description(text),
34
+ email_address: email_address(text),
35
+ email_description: email_description(text),
36
+ setaside: set_aside(text),
37
+ pop_address: pop_address(text),
38
+ pop_zip: pop_zip_code(text),
39
+ pop_country: pop_country(text)
40
+ }
41
+ FBO::Notices::Amendment.new(params)
42
+ end
43
+
44
+
45
+ protected
46
+
47
+ # Returns a concatenated list of all tags for the notice
48
+ #
49
+ # @return [String]
50
+ def any_notice_tag
51
+ ANY_AMDCSS_TAG
52
+ end
53
+
54
+
55
+ extend FBO::Parser::NoticeHandler
56
+ extend FBO::Parser::ParserHelper
57
+ extend self
58
+ end