so_far_so_good 0.0.3 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 89ca68bf267662afde3f952d21d46565546f8278
4
- data.tar.gz: 76dc75b71d6764960d11e45c4a6f6d4b2b39312f
3
+ metadata.gz: ab77e766152edb727d4513fc59371d119f0339b3
4
+ data.tar.gz: fd27195a2227fa669f86d93593a5eeded9c8310c
5
5
  SHA512:
6
- metadata.gz: d6a7d2876d9c40b48d0f6b504bd52e184865634f6667c76e995c04175a1fbd512d69a44e67742e2c888ae893f660f019f529882fa510248493eff648005270a1
7
- data.tar.gz: dd43c8d95edb8a176f242bc596c4c0ca99533e588962f6efe81c5f7c25d9f1912f836ba328d30e122258d4922e7d4300907ff141bbdbf4bca043827cecb8e492
6
+ metadata.gz: 305cdec6f88a0555b7af3504414b48814e8927efc19b42e93746f553a69f9b9d181e5bfe748db258ed242b7c51bd5d758faf47fa236f07fbf28329d5acb3263f
7
+ data.tar.gz: 09d6f2de3a92e53f8f3e7079b89fa18793fd588d522738ead0e9969f48a8ebd5ef09216d28365df55845b7dcc66470d43914d541566ffb8348126fb8149d2475
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # So FAR so Good
2
2
 
3
- A Ruby Gem to parse and manipulate the Federal Acquisition Regulation
3
+ A Ruby Gem to parse and manipulate the Federal Acquisition Regulation (FAR) and Defense Federal Acquisition Regulation Supplement (DFARs)
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/so_far_so_good.svg)](http://badge.fury.io/rb/so_far_so_good) [![Build Status](https://travis-ci.org/benbalter/so_far_so_good.svg)](https://travis-ci.org/benbalter/so_far_so_good)
6
6
 
7
7
  ## What it does
8
8
 
9
- So FAR so Good is a Ruby Gem to interact with the Federal Acquisition Regulation. Right now, it only supports section 52.20x, the FAR standard contracting clause templates.
9
+ So FAR so Good is a Ruby Gem to interact with the Federal Acquisition Regulation (FAR) and Defense Federal Acquisition Regulation Supplement (DFARs). Right now, it supports section 52.20x and 252.20x, the FAR and DFARs standard contracting clause templates.
10
10
 
11
11
  For any contract clause, it will provide:
12
12
 
@@ -16,6 +16,7 @@ For any contract clause, it will provide:
16
16
  * The formal citation
17
17
  * The body (full clause text)
18
18
  * The extract (what's inserted into the contract)
19
+ * A link to the full text online
19
20
 
20
21
  It will give you access to this information in object-oriented Ruby, as JSON, or as a markdown table for use elsewhere.
21
22
 
@@ -24,69 +25,77 @@ It will give you access to this information in object-oriented Ruby, as JSON, or
24
25
  ### Basic usage
25
26
 
26
27
  ```ruby
27
- # Get all clauses
28
- clauses = SoFarSoGood.clauses
29
- => [#<SoFarSoGood::Clause @number="52.200" @subject="Scope of subpart." @reserved="false",
30
- #<SoFarSoGood::Clause @number="52.202-1" @subject="Definitions." @reserved="false",
31
- #<SoFarSoGood::Clause @number="52.203-1" @subject="[Reserved]" @reserved="true",
32
- #<SoFarSoGood::Clause @number="52.203-2" @subject="Certificate of Independent Price Determination." @reserved="false",
33
- #<SoFarSoGood::Clause @number="52.203-3" @subject="Gratuities." @reserved="false", ... ]
34
-
35
- # Get a list of clause numbers
36
- SoFarSoGood::Clauses.numbers
28
+ subchapters = SoFarSoGood.subchapters
29
+ => [#<SoFarSoGood::Subchapter year=2013 title=48 volume=2 chapter=1 name="FAR">,
30
+ #<SoFarSoGood::Subchapter year=2013 title=48 volume=3 chapter=2 name="DFARs">]
31
+
32
+ # Get the FAR's subparts
33
+ subparts = SoFarSoGood.far.subparts
34
+ => [#<SoFarSoGood::Subpart year=2013 title=48 volume=2 chapter=1 number="52.200" subject="Scope of subpart." document="FAR" reserved=false>,
35
+ #<SoFarSoGood::Subpart year=2013 title=48 volume=2 chapter=1 number="52.202-1" subject="Definitions." document="FAR" reserved=false>,
36
+ #<SoFarSoGood::Subpart year=2013 title=48 volume=2 chapter=1 number="52.203-1" subject="[Reserved]" document="FAR" reserved=true>,
37
+ #<SoFarSoGood::Subpart year=2013 title=48 volume=2 chapter=1 number="52.203-2" subject="Certificate of Independent Price Determination." document="FAR" reserved=false>,
38
+ #<SoFarSoGood::Subpart year=2013 title=48 volume=2 chapter=1 number="52.203-3" subject="Gratuities." document="FAR" reserved=false>,
39
+ #<SoFarSoGood::Subpart year=2013 title=48 volume=2 chapter=1 number="52.203-4" subject="[Reserved]" document="FAR" reserved=true>...]
40
+
41
+ # Get a list of subpart numbers
42
+ SoFarSoGood.far.numbers
37
43
  => ["52.200", "52.202-1", "52.203-1", "52.203-2", "52.203-3", "52.203-4", "52.203-5", ... ]
38
44
 
39
- # Get a list of clause numbers, excluding reserved clauses
40
- SoFarSoGood::Clauses.numbers(:exclude_reserved => true)
45
+ # Get a list of subpart numbers, excluding reserved clauses
46
+ SoFarSoGood.far.numbers(:reserved => false)
41
47
  => ["52.200", "52.202-1", "52.203-1", "52.203-2", "52.203-3", "52.203-4", "52.203-5", ... ]
42
48
 
43
- # Get a list of clause subjects
44
- SoFarSoGood::Clauses.subjects
49
+ # Get a list of subpart subjects
50
+ SoFarSoGood.far.subjects
45
51
  => ["Scope of subpart.", "Definitions.", "[Reserved]", "Certificate of Independent Price Determination.", ... ]
46
52
  ```
47
53
 
48
54
  ### Working with individual clauses
49
55
 
50
56
  ```ruby
51
- # Get a specific clause
52
- clause = SoFarSoGood::Clauses["52.202-1"]
53
- => #<SoFarSoGood::Clause @number="52.202-1" @subject="Definitions." @reserved="false"
57
+ # Get a specific subpart
58
+ subpart = SoFarSoGood["52.202-1"]
59
+ => #<SoFarSoGood::Subpart year=2013 title=48 volume=2 chapter=1 number="52.202-1" subject="Definitions." document="FAR" reserved=false>
54
60
 
55
- clause.number
61
+ subpart.number
56
62
  => "52.202-1"
57
63
 
58
- clause.subject
64
+ subpart.subject
59
65
  => "Definitions"
60
66
 
61
- clause.reserved?
67
+ subpart.reserved?
62
68
  => false
63
69
 
64
- clause.citation
70
+ subpart.citation
65
71
  => "[69 FR 34228, June 18, 2004]"
66
72
 
67
73
  # The full clause body
68
- clause.body
74
+ subpart.body
69
75
  => "As prescribed in 2.201, insert the following clause:..."
70
76
 
71
77
  # The actual clause text to be inserted in the contract
72
- clause.extract
78
+ subpart.extract
73
79
  => "Definitions (JUL 2004)(a) When a solicitation provision or contract clause uses a word..."
80
+
81
+ subpart.link
82
+ => "http://www.law.cornell.edu/cfr/text/48/52.202-1"
74
83
  ```
75
84
 
76
- ### Using clause data elsewhere
85
+ ### Using subpart data elsewhere
77
86
 
78
87
  #### As JSON
79
88
 
80
89
  ```ruby
81
90
 
82
- SoFarSoGood::Clauses.list.to_json
91
+ SoFarSoGood.far.to_json
83
92
  => "[{\"number\":\"52.200\",\"subject\":\"Scope of subpart.\",\"reserverd\":false,\"citation\":..."
84
93
  ```
85
94
 
86
95
  #### As a markdown table
87
96
 
88
97
  ```ruby
89
- puts SoFarSoGood::Clauses.to_md
98
+ puts SoFarSoGood.far.to_md
90
99
  |-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
91
100
  | Clause | Description |
92
101
  |-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -97,11 +106,11 @@ puts SoFarSoGood::Clauses.to_md
97
106
  | 52.203-5 | Covenant Against Contingent Fees. |
98
107
  ...
99
108
 
100
- # Table without reserved clauses
101
- puts SoFarSoGood::Clauses.to_md(:exclude_reserved => true)
109
+ # Table without reserved subparts
110
+ puts SoFarSoGood.dfars.to_md(:reserved => false)
102
111
 
103
112
  # Table with links to text
104
- puts SoFarSoGood::Clauses.to_md(:links => true)
113
+ puts SoFarSoGood.far.to_md(:links => true)
105
114
  ...
106
115
  | [52.222-34](http://www.law.cornell.edu/cfr/text/48/52.222-34) | Project Labor Agreement. |
107
116
  | [52.222-35](http://www.law.cornell.edu/cfr/text/48/52.222-35) | Equal Opportunity for Veterans. |
@@ -112,15 +121,26 @@ puts SoFarSoGood::Clauses.to_md(:links => true)
112
121
  ...
113
122
  ```
114
123
 
115
- #### Individual clauses as markdown
124
+ #### As a CSV
125
+ ```ruby
126
+ puts SoFarSoGood.far.to_csv(:reserved => false)
127
+ Clause,Description
128
+ 52.200,Scope of subpart.
129
+ 52.202-1,Definitions.
130
+ 52.203-2,Certificate of Independent Price Determination.
131
+ 52.203-3,Gratuities.
132
+ 52.203-5,Covenant Against Contingent Fees.
133
+ ```
134
+
135
+ #### Individual subparts as markdown
116
136
 
117
137
  ```ruby
118
138
  # The body
119
- puts SoFarSoGood::Clauses["52.202-1"].body(:format => :markdown)
139
+ puts SoFarSoGood["52.202-1"].body(:format => :markdown)
120
140
  => As prescribed in 2.201, insert the following clause:
121
141
 
122
142
  # The extract
123
- puts SoFarSoGood::Clauses["52.202-1"].extract(:format => :markdown)
143
+ puts SoFarSoGood["52.202-1"].extract(:format => :markdown)
124
144
  => ### Definitions (JUL 2004)
125
145
 
126
146
  (a) When a solicitation provision or contract clause uses a word or term that is defined in the Federal Acquisition Regulation (FAR), the word or term has the same meaning as the definition in FAR 2.101 in effect at the time the solicitation was issued, unless— ...
@@ -0,0 +1,88 @@
1
+ module SoFarSoGood
2
+ class Subchapter
3
+
4
+ attr_reader :year
5
+ attr_reader :title
6
+ attr_reader :volume
7
+ attr_reader :chapter
8
+ attr_reader :name
9
+
10
+ def initialize(hash)
11
+ @volume = hash[:volume]
12
+ @chapter = hash[:chapter]
13
+ @name = hash[:name]
14
+ @year = SoFarSoGood::YEAR
15
+ @title = SoFarSoGood::TITLE
16
+ end
17
+
18
+ def numbers(options = {})
19
+ @numbers ||= subparts(options).map { |s| s.number }
20
+ end
21
+
22
+ def subjects(options = {})
23
+ @subjects ||= subparts(options).map { |s| s.subject }
24
+ end
25
+
26
+ def subparts(options = {})
27
+ @subparts ||= sections.map { |node| SoFarSoGood::Subpart.new(to_hash.merge(:node => node)) }
28
+ @subparts.select { |subpart| options.all? { |k,v| subpart.send(k) == v } }
29
+ end
30
+
31
+ def to_md(options = {})
32
+ links = options.delete(:links) == true
33
+ rows = subparts(options).map { |c| [ links ? "[#{c.number}](#{c.link})" : c.number , c.subject] }
34
+ Terminal::Table.new(:rows => rows, :style => { :border_i => "|" }, :headings => HEADINGS).to_s
35
+ end
36
+
37
+ def to_json(options = {})
38
+ subparts(options).to_json(options)
39
+ end
40
+
41
+ def to_csv(options = {})
42
+ CSV.generate do |csv|
43
+ csv << HEADINGS
44
+ subparts(options).each do |c|
45
+ csv << [c.number , c.subject]
46
+ end
47
+ end
48
+ end
49
+
50
+ def to_hash
51
+ {
52
+ :year => year,
53
+ :title => title,
54
+ :volume => volume,
55
+ :chapter => chapter,
56
+ :name => name
57
+ }
58
+ end
59
+
60
+ def [](number)
61
+ subparts.find { |s| s.number == number }
62
+ end
63
+
64
+ def inspect
65
+ "#<SoFarSoGood::Subchapter year=#{year} title=#{title} volume=#{volume} chapter=#{chapter} name=\"#{name}\">"
66
+ end
67
+
68
+ private
69
+
70
+ def filename
71
+ "CFR-#{YEAR}-title#{TITLE}-vol#{volume}-chap#{chapter}-subchapH.xml"
72
+ end
73
+
74
+ def source_path
75
+ @source_path ||= File.expand_path filename, SoFarSoGood.vendor_directory
76
+ end
77
+
78
+ def doc
79
+ @doc ||= Nokogiri::XML(File.open(source_path)) do |config|
80
+ config.noblanks.nonet
81
+ end
82
+ end
83
+
84
+ def sections
85
+ @subpart ||= doc.css("PART SUBPART")[name == "FAR" ? 4 : 3].children.css("SECTION")
86
+ end
87
+ end
88
+ end
@@ -1,10 +1,20 @@
1
1
  module SoFarSoGood
2
- class Clause
2
+ class Subpart
3
3
 
4
4
  attr_reader :node
5
+ attr_reader :year
6
+ attr_reader :title
7
+ attr_reader :volume
8
+ attr_reader :chapter
9
+ attr_reader :document
5
10
 
6
- def initialize(node)
7
- @node = node
11
+ def initialize(hash)
12
+ @year = hash[:year]
13
+ @title = hash[:title]
14
+ @volume = hash[:volume]
15
+ @chapter = hash[:chapter]
16
+ @document = hash[:name]
17
+ @node = hash[:node]
8
18
  normalize!
9
19
  end
10
20
 
@@ -51,13 +61,18 @@ module SoFarSoGood
51
61
 
52
62
  def to_hash(options={})
53
63
  {
64
+ :year => year,
65
+ :title => title,
66
+ :volume => volume,
67
+ :chapter => chapter,
54
68
  :number => number,
55
69
  :subject => subject,
56
70
  :reserverd => reserved,
57
71
  :citation => citation,
58
72
  :extract => extract(options),
59
73
  :body => body(options),
60
- :link => link
74
+ :link => link,
75
+ :document => document,
61
76
  }
62
77
  end
63
78
 
@@ -66,7 +81,7 @@ module SoFarSoGood
66
81
  end
67
82
 
68
83
  def inspect
69
- "#<SoFarSoGood::Clause @number=\"#{@number}\" @subject=\"#{@subject}\" @reserved=\"#{@reserved}\""
84
+ "#<SoFarSoGood::Subpart year=#{year} title=#{title} volume=#{volume} chapter=#{chapter} number=\"#{number}\" subject=\"#{subject}\" document=\"#{document}\" reserved=#{reserved}>"
70
85
  end
71
86
 
72
87
  private
@@ -1,3 +1,3 @@
1
1
  module SoFarSoGood
2
- VERSION = "0.0.3"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -1,15 +1,37 @@
1
1
  require "nokogiri"
2
2
  require "terminal-table"
3
- require "json"
4
3
  require "reverse_markdown"
4
+ require "json"
5
+ require 'csv'
5
6
  require_relative "so_far_so_good/version"
6
- require_relative "so_far_so_good/clauses"
7
- require_relative "so_far_so_good/clause"
7
+ require_relative "so_far_so_good/subchapter"
8
+ require_relative "so_far_so_good/subpart"
8
9
 
9
10
  module SoFarSoGood
11
+
12
+ HEADINGS = ["Clause", "Description"]
13
+ YEAR = 2013
14
+ TITLE = 48
15
+
10
16
  class << self
11
- def clauses(options = {})
12
- SoFarSoGood::Clauses.clauses(options)
17
+ def far
18
+ @far ||= SoFarSoGood::Subchapter.new(:name => "FAR", :volume => 2, :chapter => 1)
19
+ end
20
+
21
+ def dfars
22
+ @dfars ||= SoFarSoGood::Subchapter.new(:name => "DFARs", :volume => 3, :chapter => 2)
23
+ end
24
+
25
+ def subchapters
26
+ [far, dfars]
27
+ end
28
+
29
+ def subparts(options = {})
30
+ subchapters.map { |d| d.subparts(options) }.flatten
31
+ end
32
+
33
+ def [](subpart)
34
+ subparts.find { |s| s.number == subpart }
13
35
  end
14
36
 
15
37
  def vendor_directory
data/script/release CHANGED
@@ -1,3 +1,38 @@
1
1
  #!/bin/sh
2
+ # Tag and push a release.
2
3
 
3
- bundle exec rake release
4
+ set -e
5
+
6
+ # Make sure we're in the project root.
7
+
8
+ cd $(dirname "$0")/..
9
+
10
+ # Build a new gem archive.
11
+
12
+ rm -rf so_far_so_good-*.gem
13
+ gem build -q so_far_so_good.gemspec
14
+
15
+ # Make sure we're on the master branch.
16
+
17
+ (git branch | grep -q '* master') || {
18
+ echo "Only release from the master branch."
19
+ exit 1
20
+ }
21
+
22
+ # Figure out what version we're releasing.
23
+
24
+ tag=v`ls so_far_so_good-*.gem | sed 's/^so_far_so_good-\(.*\)\.gem$/\1/'`
25
+
26
+ # Make sure we haven't released this version before.
27
+
28
+ git fetch -t origin
29
+
30
+ (git tag -l | grep -q "$tag") && {
31
+ echo "Whoops, there's already a '${tag}' tag."
32
+ exit 1
33
+ }
34
+
35
+ # Tag it and bag it.
36
+
37
+ gem push so_far_so_good-*.gem && git tag "$tag" &&
38
+ git push origin master && git push origin "$tag"
data/script/vendor CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/bin/sh
2
2
 
3
3
  rm -Rf ./vendor/*
4
- wget http://www.gpo.gov/fdsys/pkg/CFR-2010-title48-vol2/xml/CFR-2010-title48-vol2-chap1-subchapH.xml -P vendor
4
+ wget http://www.gpo.gov/fdsys/pkg/CFR-2013-title48-vol2/xml/CFR-2013-title48-vol2-chap1-subchapH.xml -P vendor
5
+ wget http://www.gpo.gov/fdsys/pkg/CFR-2013-title48-vol3/xml/CFR-2013-title48-vol3-chap2-subchapH.xml -P vendor
@@ -0,0 +1,59 @@
1
+ require 'helper'
2
+
3
+ class TestSoFarSoGoodSubchapter < Minitest::Test
4
+
5
+ def setup
6
+ @subchapter = SoFarSoGood.far
7
+ end
8
+
9
+ should "know the subchapter file" do
10
+ assert File.exists? @subchapter.send :source_path
11
+ end
12
+
13
+ should "parse the XML" do
14
+ assert Nokogiri::XML::Document, @subchapter.send(:doc).class
15
+ end
16
+
17
+ should "parse subpart numbers" do
18
+ assert_equal 626, @subchapter.numbers.count
19
+ assert_equal "52.200", @subchapter.numbers.first
20
+ end
21
+
22
+ should "parse subpart descriptions" do
23
+ assert_equal 626, @subchapter.subjects.count
24
+ assert_equal "Scope of subpart.", @subchapter.subjects.first
25
+ end
26
+
27
+ should "put out valid JSON" do
28
+ assert !!JSON.parse(@subchapter.subparts.to_json)
29
+ end
30
+
31
+ should "return a particular subpart" do
32
+ assert_equal "52.202-1", @subchapter["52.202-1"].number
33
+ end
34
+
35
+ should "return all subparts" do
36
+ assert_equal 626, @subchapter.subparts.count
37
+ end
38
+
39
+ should "filter reserved subparts" do
40
+ assert_equal 576, @subchapter.subparts(:reserved => false).count
41
+ end
42
+
43
+ should "build the markdown table" do
44
+ assert_includes @subchapter.to_md, "-|\n| 52.200 | Scope of subpart."
45
+ end
46
+
47
+ should "exclude reserved in markdown table when asked" do
48
+ refute_includes @subchapter.to_md(:reserved => false), "[Reserved]"
49
+ end
50
+
51
+ should "include links in markdown table when asked" do
52
+ assert_includes @subchapter.to_md(:links => true), "[52.252-6](http://www.law.cornell.edu/cfr/text/48/52.252-6)"
53
+ end
54
+
55
+ should "output valid CSV" do
56
+ csv = CSV.parse @subchapter.to_csv
57
+ assert_equal ["52.200", "Scope of subpart."], csv[1]
58
+ end
59
+ end
@@ -0,0 +1,59 @@
1
+ require 'helper'
2
+
3
+ class TestSoFarSoGoodSubpart < Minitest::Test
4
+
5
+ def setup
6
+ @subchapter = SoFarSoGood.far
7
+ @nodes = @subchapter.send(:sections)
8
+ @subpart = SoFarSoGood::Subpart.new(:node => @nodes[6])
9
+ end
10
+
11
+ should "Store the node" do
12
+ assert Nokogiri::XML::Element, @subpart.node.class
13
+ end
14
+
15
+ should "expose the subpart number" do
16
+ assert_equal "52.203-5", @subpart.number
17
+ end
18
+
19
+ should "expose the subpart subject" do
20
+ assert_equal "Covenant Against Contingent Fees.", @subpart.subject
21
+ end
22
+
23
+ should "know the cite" do
24
+ assert_includes @subpart.citation, "48 FR 42478"
25
+ end
26
+
27
+ should "know the subpart body" do
28
+ assert_includes @subpart.body, "As prescribed in 3.404,"
29
+ end
30
+
31
+ should "not include the extract in the subpart body" do
32
+ refute_includes @subpart.body, "The Contractor warrants that no person or agency"
33
+ end
34
+
35
+ should "know the subpart extract" do
36
+ assert_includes @subpart.extract, "The Contractor warrants that no person or agency"
37
+ end
38
+
39
+ should "know if its reserved" do
40
+ refute @subchapter["52.203-5"].reserved?
41
+ assert @subchapter["52.203-1"].reserved?
42
+ end
43
+
44
+ should "convert the body to markdown" do
45
+ assert_includes @subpart.body(:format => :markdown), "As prescribed in 3.404, insert the following clause:\n\n"
46
+ end
47
+
48
+ should "convert the extract to markdown" do
49
+ assert_includes @subpart.extract(:format => :markdown), "### Covenant Against Contingent Fees (APR 1984)\n\n(a)"
50
+ end
51
+
52
+ should "build the link" do
53
+ assert_equal "http://www.law.cornell.edu/cfr/text/48/52.203-5", @subpart.link
54
+ end
55
+
56
+ should "put out valid json" do
57
+ assert JSON.parse @subchapter["52.203-5"].to_json
58
+ end
59
+ end
@@ -2,15 +2,28 @@ require 'helper'
2
2
 
3
3
  class TestSoFarSoGood < Minitest::Test
4
4
  should "figure out the vendor directory" do
5
- assert File.exists?(SoFarSoGood.vendor_directory)
5
+ assert File.exists?(SoFarSoGood.send(:vendor_directory))
6
6
  end
7
7
 
8
- should "return the clause hash" do
9
- assert_equal Array, SoFarSoGood.clauses.class
10
- assert_equal 616, SoFarSoGood.clauses.count
8
+ should "know the FAR" do
9
+ assert_equal SoFarSoGood::Subchapter, SoFarSoGood.far.class
10
+ assert_equal "FAR", SoFarSoGood.far.name
11
11
  end
12
12
 
13
- should "accept clause options" do
14
- assert_equal 567, SoFarSoGood.clauses(:exclude_reserved => true).count
13
+ should "know the DFARs" do
14
+ assert_equal SoFarSoGood::Subchapter, SoFarSoGood.dfars.class
15
+ assert_equal "DFARs", SoFarSoGood.dfars.name
16
+ end
17
+
18
+ should "return subchapters" do
19
+ assert_equal 2, SoFarSoGood.subchapters.count
20
+ end
21
+
22
+ should "return subparts" do
23
+ assert_equal 981, SoFarSoGood.subparts.count
24
+ end
25
+
26
+ should "accept subpart options" do
27
+ assert_equal 907, SoFarSoGood.subparts(:reserved => false).count
15
28
  end
16
29
  end