vdf4r 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Joshua Morris
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # VDF4R
2
+
3
+ Parse Valve Data Format files easily and quickly.
4
+
5
+
6
+ ## Context
7
+
8
+ Valve has its own data format for storing game information. This library lets
9
+ you parse these files into a plain-old Ruby hash easily and quickly.
10
+
11
+ After that, you can do with the data what you will.
12
+
13
+
14
+ ## Installation
15
+
16
+ As normal:
17
+
18
+ gem install vdf4r
19
+
20
+ Or in your Gemfile:
21
+
22
+ gem 'vdf4r', '~>0.1.0'
23
+
24
+
25
+ ## Usage
26
+
27
+ require 'vdf4r'
28
+ require 'pp'
29
+
30
+ File.open('vdf_file.txt') do |file|
31
+ parser = VDF4R::Parser.new(file)
32
+ pp parser.parse # pretty-printed
33
+ end
34
+
35
+
36
+ ## Caveats
37
+
38
+ This library has only really been used on a few Dota 2 VDF files. It's not
39
+ battle-tested yet, and there are probably some minor issues.
40
+
41
+ If you find something you'd like to discuss, you can find me on #dota2replay
42
+ on quakenet IRC.
43
+
44
+ At least one of Dota 2's own VDF files have grammar mistakes.
45
+ (i.e. npc_abilities.txt) If you get an "ungrammatical content" error while
46
+ parsing, you will need to fix the error. It will give you the offending line:
47
+
48
+ (RuntimeError)parser.rb:30:in `block in parse': ungrammatical content: ' / Damage.
49
+ '
50
+
51
+ Indeed, in the VDF file, there are "comment" lines lacking the proper '//'
52
+ prefix. When I changed the file to contain '// Damage.' it parsed correctly.
53
+
54
+ I'll think of a way to make the parser more permissive as time allows.
55
+
56
+
57
+ ## Hacking
58
+
59
+ Just clone the source from here. If issuing a pull request, make sure your
60
+ change is on a topic branch accompanied by new tests; all behaviors must pass.
61
+
62
+
63
+ ## License
64
+
65
+ VDF4R is offered under the MIT license. See [LICENSE](https://github.com/skadistats/vdf4r/blob/master/README.md)
66
+ for the license itself.
data/lib/vdf.tt ADDED
@@ -0,0 +1,80 @@
1
+ module VDF4R
2
+ grammar KeyValues
3
+ rule line
4
+ (enter_object / exit_object / comment / key_value / key / blank) endline?
5
+ {
6
+ def value
7
+ elements[0].value
8
+ end
9
+ }
10
+ end
11
+
12
+ rule blank
13
+ whitespace*
14
+ {
15
+ def value
16
+ :blank
17
+ end
18
+ }
19
+ end
20
+
21
+ rule key_value
22
+ whitespace* token whitespace* token whitespace* comment?
23
+ {
24
+ def value
25
+ [elements[1].value, elements[3].value]
26
+ end
27
+ }
28
+ end
29
+
30
+ rule key
31
+ whitespace* token whitespace* comment?
32
+ {
33
+ def value
34
+ [elements[1].value]
35
+ end
36
+ }
37
+ end
38
+
39
+ rule token
40
+ '"' [^"]* '"'
41
+ {
42
+ def value
43
+ elements[1..-2].collect { |e| e.text_value }.join
44
+ end
45
+ }
46
+ end
47
+
48
+ rule enter_object
49
+ whitespace* '{' whitespace* comment? {
50
+ def value
51
+ :enter_object
52
+ end
53
+ }
54
+ end
55
+
56
+ rule exit_object
57
+ whitespace* '}' whitespace* comment? {
58
+ def value
59
+ :exit_object
60
+ end
61
+ }
62
+ end
63
+
64
+ rule comment
65
+ whitespace* [/]? [/] [^\n]* {
66
+ def value
67
+ :comment
68
+ end
69
+ }
70
+ end
71
+
72
+ rule whitespace
73
+ [\t ]
74
+ end
75
+
76
+ rule endline
77
+ [\r]? [\n]
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,61 @@
1
+ require 'treetop'
2
+ require 'vdf4r/store'
3
+
4
+ MAX_RECURSION = 10
5
+ GRAMMAR_PATH = File.join(File.dirname(__FILE__), '..', 'vdf.tt')
6
+
7
+ Treetop.load GRAMMAR_PATH
8
+
9
+ module VDF4R
10
+ class Parser
11
+ def initialize(input)
12
+ case
13
+ when input.respond_to?(:each_line)
14
+ @input = input.each_line.to_a
15
+ when input.respond_to?(:lines)
16
+ @input = input.lines
17
+ else
18
+ raise ArgumentError.new('input must respond to #each_line or #lines')
19
+ end
20
+ end
21
+
22
+ def parse
23
+ parser = VDF4R::KeyValuesParser.new
24
+ store = Store.new
25
+ key = nil
26
+ path = []
27
+
28
+ @input.each do |line|
29
+ node = parser.parse(line)
30
+ raise "ungrammatical content: '#{line}'" if node.nil?
31
+
32
+ next if [:blank, :comment].include?(node.value)
33
+
34
+ if node.value.respond_to?(:to_ary)
35
+ case node.value.length
36
+ when 1
37
+ key = node.value.first
38
+ when 2
39
+ k, v = node.value
40
+ store.traverse(path)[k] = v
41
+ end
42
+ elsif node.value.kind_of?(Symbol)
43
+ case node.value
44
+ when :enter_object
45
+ raise 'no preceding key for object' unless key
46
+ raise 'too recursive' if path.length > MAX_RECURSION
47
+ path.push key
48
+ key = nil
49
+ when :exit_object
50
+ raise 'nesting unbalanced (excessive exit)' if path.empty?
51
+ path.pop
52
+ end
53
+ end
54
+ end
55
+
56
+ raise 'nesting unbalanced (insufficient exit)' unless path.empty?
57
+
58
+ store
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,11 @@
1
+ module VDF4R
2
+ class Store < Hash
3
+ def initialize
4
+ super { |h, k| h[k] = Store.new } # defaultdict(dict)
5
+ end
6
+
7
+ def traverse(path)
8
+ path.inject(self) { |current, path_component| current[path_component] }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module VDF4R
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,5 @@
1
+ "DOTAAbilities"
2
+ {
3
+ "Version"
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ "DOTAAbilities"
2
+ {
3
+ "Version"
4
+ {
5
+ }
@@ -0,0 +1,5 @@
1
+ "DOTAAbilities"
2
+ {
3
+ {
4
+ }
5
+ }
@@ -0,0 +1,36 @@
1
+ "DOTAAbilities"
2
+ {
3
+ "1"
4
+ {
5
+ "2"
6
+ {
7
+ "3"
8
+ {
9
+ "4"
10
+ {
11
+ "5"
12
+ {
13
+ "6"
14
+ {
15
+ "7"
16
+ {
17
+ "8"
18
+ {
19
+ "9"
20
+ {
21
+ "10"
22
+ {
23
+ "11"
24
+ {
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,4 @@
1
+ "DOTAAbilities"
2
+ {
3
+ The parser won't understand this line.
4
+ }
@@ -0,0 +1,169 @@
1
+ // Dota Heroes File
2
+ "DOTAAbilities"
3
+ {
4
+ "Version" "1"
5
+
6
+ //=================================================================================================================
7
+ // Blink dagger
8
+ //=================================================================================================================
9
+ "item_blink"
10
+ {
11
+ // General
12
+ //-------------------------------------------------------------------------------------------------------------
13
+ "ID" "1" // unique ID number for this item. Do not change this once established or it will invalidate collected stats.
14
+ "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_DIRECTIONAL | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES"
15
+
16
+ // Stats
17
+ //-------------------------------------------------------------------------------------------------------------
18
+ "AbilityCastRange" "0"
19
+ "AbilityCastPoint" "0.0"
20
+ "AbilityCooldown" "12.0"
21
+ "AbilityManaCost" "0"
22
+
23
+ // Item Info
24
+ //-------------------------------------------------------------------------------------------------------------
25
+ "ItemCost" "2150"
26
+ "ItemShopTags" "teleport"
27
+ "ItemQuality" "component"
28
+ "ItemAliases" "blink dagger"
29
+ "SideShop" "1"
30
+ "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS"
31
+
32
+ // Special
33
+ //-------------------------------------------------------------------------------------------------------------
34
+ "AbilitySpecial"
35
+ {
36
+ "01"
37
+ {
38
+ "var_type" "FIELD_INTEGER"
39
+ "blink_range" "1200"
40
+ }
41
+ "02"
42
+ {
43
+ "var_type" "FIELD_INTEGER"
44
+ "blink_damage_cooldown" "3"
45
+ }
46
+ "03"
47
+ {
48
+ "var_type" "FIELD_INTEGER"
49
+ "blink_range_clamp" "960"
50
+ }
51
+ }
52
+ }
53
+
54
+ //=================================================================================================================
55
+ // Blades of Attack
56
+ //=================================================================================================================
57
+ "item_blades_of_attack"
58
+ {
59
+ // General
60
+ //-------------------------------------------------------------------------------------------------------------
61
+ "ID" "2" // unique ID number for this item. Do not change this once established or it will invalidate collected stats.
62
+ "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE"
63
+
64
+ // Item Info
65
+ //-------------------------------------------------------------------------------------------------------------
66
+ "ItemCost" "450"
67
+ "ItemShopTags" "damage;tutorial"
68
+ "ItemQuality" "component"
69
+ "ItemAliases" "blades of attack"
70
+ "SideShop" "1"
71
+
72
+ // Special
73
+ //-------------------------------------------------------------------------------------------------------------
74
+ "AbilitySpecial"
75
+ {
76
+ "01"
77
+ {
78
+ "var_type" "FIELD_INTEGER"
79
+ "bonus_damage" "9"
80
+ }
81
+ }
82
+ }
83
+
84
+ //=================================================================================================================
85
+ // Broadsword
86
+ //=================================================================================================================
87
+ "item_broadsword"
88
+ {
89
+ // General
90
+ //-------------------------------------------------------------------------------------------------------------
91
+ "ID" "3" // unique ID number for this item. Do not change this once established or it will invalidate collected stats.
92
+ "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE"
93
+
94
+ // Item Info
95
+ //-------------------------------------------------------------------------------------------------------------
96
+ "ItemCost" "1200"
97
+ "ItemShopTags" "damage"
98
+ "ItemQuality" "component"
99
+ "ItemAliases" "broadsword"
100
+
101
+ // Special
102
+ //-------------------------------------------------------------------------------------------------------------
103
+ "AbilitySpecial"
104
+ {
105
+ "01"
106
+ {
107
+ "var_type" "FIELD_INTEGER"
108
+ "bonus_damage" "18"
109
+ }
110
+ }
111
+ }
112
+
113
+ //=================================================================================================================
114
+ // Black King Bar
115
+ //=================================================================================================================
116
+ "item_black_king_bar"
117
+ {
118
+ // General
119
+ //-------------------------------------------------------------------------------------------------------------
120
+ "ID" "116" // unique ID number for this item. Do not change this once established or it will invalidate collected stats.
121
+ "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET"
122
+
123
+ // Stats
124
+ //-------------------------------------------------------------------------------------------------------------
125
+ "AbilityCooldown" "80 75 70 65 60 55 50"
126
+
127
+ // Item Info
128
+ //-------------------------------------------------------------------------------------------------------------
129
+ "ItemCost" "3975"
130
+ "ItemShopTags" "str;damage;hard_to_tag"
131
+ "ItemQuality" "epic"
132
+ "ItemAliases" "bkb;black king bar"
133
+ "ItemSellable" "0"
134
+ "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS"
135
+
136
+ // Special
137
+ //-------------------------------------------------------------------------------------------------------------
138
+ "AbilitySpecial"
139
+ {
140
+ "01"
141
+ {
142
+ "var_type" "FIELD_INTEGER"
143
+ "bonus_strength" "10"
144
+ }
145
+ "02"
146
+ {
147
+ "var_type" "FIELD_INTEGER"
148
+ "bonus_damage" "24"
149
+ }
150
+ "03"
151
+ {
152
+ "var_type" "FIELD_FLOAT"
153
+ "duration" "10.0 9.0 8.0 7.0 6.0 5.0 4.0"
154
+ }
155
+ "04"
156
+ {
157
+ "var_type" "FIELD_INTEGER"
158
+ "max_level" "6"
159
+ }
160
+ "05"
161
+ {
162
+ "var_type" "FIELD_INTEGER"
163
+ "model_scale" "30" // Percentage over model scale
164
+ }
165
+ }
166
+ }
167
+
168
+ // next free ID: 242
169
+ }
@@ -0,0 +1,12 @@
1
+ module FixtureHelpers
2
+ def with_fixture(name, &block)
3
+ abspath = File.join(File.dirname(__FILE__), 'fixtures', *name.split('/'))
4
+ File.open("#{abspath}.txt") do |fixture|
5
+ yield fixture
6
+ end
7
+ end
8
+ end
9
+
10
+ RSpec.configure do |c|
11
+ c.include FixtureHelpers
12
+ end
@@ -0,0 +1,130 @@
1
+ require 'spec_helper'
2
+
3
+ require 'vdf4r'
4
+ require 'stringio'
5
+
6
+ module VDF4R
7
+ describe Parser do
8
+ subject { VDF4R::Parser }
9
+
10
+ describe 'instantiation' do
11
+ shared_examples_for "doesn't raise" do
12
+ it 'does not raise' do
13
+ expect {
14
+ subject.new(input)
15
+ }.not_to raise_error
16
+ end
17
+ end
18
+
19
+ context 'with string' do
20
+ let(:input) { 'foo\nbar\n'}
21
+ it_behaves_like "doesn't raise"
22
+ end
23
+
24
+ context 'with io' do
25
+ let(:input) { StringIO.new('input') }
26
+ it_behaves_like "doesn't raise"
27
+ end
28
+
29
+ context 'quacks right' do
30
+ let(:input) do
31
+ quacker = Class.new do
32
+ def lines
33
+ ['line 1', 'line 2']
34
+ end
35
+ end
36
+ quacker.new
37
+ end
38
+
39
+ it_behaves_like "doesn't raise"
40
+ end
41
+
42
+ context 'with inappropriate input' do
43
+ let(:input) { 1234 }
44
+
45
+ it 'raises' do
46
+ expect {
47
+ subject.new(input)
48
+ }.to raise_error
49
+ end
50
+ end
51
+ end
52
+
53
+ describe 'items.txt fixture translation output' do
54
+ let(:result) do
55
+ with_fixture('items') do |fixture|
56
+ subject.new(fixture).parse
57
+ end
58
+ end
59
+
60
+ it 'quacks like hash' do
61
+ expect(result).to respond_to(:keys)
62
+ end
63
+
64
+ it 'has the correct root' do
65
+ expect(result.keys.length).to eq(1)
66
+ expect(result.keys).to include('DOTAAbilities')
67
+ end
68
+
69
+ it 'has a sampling of correct top-level child entries' do
70
+ abilities = result['DOTAAbilities']
71
+ expect(abilities).to include('item_blink')
72
+ expect(abilities).to include('item_blades_of_attack')
73
+ expect(abilities).to include('item_broadsword')
74
+ expect(abilities).to include('item_black_king_bar')
75
+ end
76
+ end
77
+
78
+ describe 'bad input' do
79
+ context 'unbalanced nesting (insufficient exit)' do
80
+ it 'raises TranslationError' do
81
+ expect {
82
+ with_fixture('bad/insufficient_exit') do |fixture|
83
+ subject.new(fixture).parse
84
+ end
85
+ }.to raise_error /insufficient exit/
86
+ end
87
+ end
88
+
89
+ context 'unbalanced nesting (excessive exit)' do
90
+ it 'raises TranslationError' do
91
+ expect {
92
+ with_fixture('bad/excessive_exit') do |fixture|
93
+ subject.new(fixture).parse
94
+ end
95
+ }.to raise_error /excessive exit/
96
+ end
97
+ end
98
+
99
+ context 'ungrammatical content' do
100
+ it 'raises TranslationError' do
101
+ expect {
102
+ with_fixture('bad/ungrammatical_content') do |fixture|
103
+ subject.new(fixture).parse
104
+ end
105
+ }.to raise_error /ungrammatical content/
106
+ end
107
+ end
108
+
109
+ context 'too recursive' do
110
+ it 'raises TranslationError' do
111
+ expect {
112
+ with_fixture('bad/too_recursive') do |fixture|
113
+ subject.new(fixture).parse
114
+ end
115
+ }.to raise_error /too recursive/
116
+ end
117
+ end
118
+
119
+ context 'no preceding key' do
120
+ it 'raises TranslationError' do
121
+ expect {
122
+ with_fixture('bad/no_preceding_key') do |fixture|
123
+ subject.new(fixture).parse
124
+ end
125
+ }.to raise_error /no preceding key/
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'vdf4r/store'
3
+
4
+ module VDF4R
5
+ describe Store do
6
+ it 'defaults to a nested store for unknown keys' do
7
+ subject['foo'].should be_kind_of(Store)
8
+ end
9
+
10
+ it 'preserves original on second access of unknown key' do
11
+ store = subject['foo']['bar']
12
+ subject['foo']['bar']['ohai'] = 1
13
+ subject['foo']['bar'].should === store
14
+ end
15
+
16
+ describe '#traverse' do
17
+ let(:path) { ['foo', 'bar'] }
18
+
19
+ it 'follows path recursively to arbitrary depths' do
20
+ subject['foo']['bar'] = 'ohai'
21
+ subject.traverse(path).should eq('ohai')
22
+ end
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vdf4r
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joshua Morris
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: treetop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.14.1
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.14.1
46
+ description: ! "# VDF4R\n\nParse Valve Data Format files easily and quickly.\n\n\n##
47
+ Context\n\nValve has its own data format for storing game information. This library
48
+ lets\nyou parse these files into a plain-old Ruby hash easily and quickly.\n\nAfter
49
+ that, you can do with the data what you will.\n\n\n## Installation\n\nAs normal:\n\n
50
+ \ gem install vdf4r\n\nOr in your Gemfile:\n\n gem 'vdf4r', '~>0.1.0'\n\n\n##
51
+ Usage\n\n require 'vdf4r'\n require 'pp'\n\n File.open('vdf_file.txt')
52
+ do |file|\n parser = VDF4R::Parser.new(file)\n pp parser.parse # pretty-printed\n
53
+ \ end\n\n\n## Caveats\n\nThis library has only really been used on a few Dota
54
+ 2 VDF files. It's not\nbattle-tested yet, and there are probably some minor issues.\n\nIf
55
+ you find something you'd like to discuss, you can find me on #dota2replay\non quakenet
56
+ IRC.\n\nAt least one of Dota 2's own VDF files have grammar mistakes.\n(i.e. npc_abilities.txt)
57
+ If you get an \"ungrammatical content\" error while\nparsing, you will need to fix
58
+ the error. It will give you the offending line:\n\n (RuntimeError)parser.rb:30:in
59
+ `block in parse': ungrammatical content: ' / Damage.\n '\n\nIndeed, in
60
+ the VDF file, there are \"comment\" lines lacking the proper '//'\nprefix. When
61
+ I changed the file to contain '// Damage.' it parsed correctly.\n\nI'll think of
62
+ a way to make the parser more permissive as time allows.\n\n\n## Hacking\n\nJust
63
+ clone the source from here. If issuing a pull request, make sure your\nchange is
64
+ on a topic branch accompanied by new tests; all behaviors must pass.\n\n\n## License\n\nVDF4R
65
+ is offered under the MIT license. See [LICENSE](https://github.com/skadistats/vdf4r/blob/master/README.md)\nfor
66
+ the license itself.\n"
67
+ email: onethirtyfive@skadistats.com
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - lib/vdf.tt
73
+ - lib/vdf4r/parser.rb
74
+ - lib/vdf4r/store.rb
75
+ - lib/vdf4r/version.rb
76
+ - spec/fixtures/bad/excessive_exit.txt
77
+ - spec/fixtures/bad/insufficient_exit.txt
78
+ - spec/fixtures/bad/no_preceding_key.txt
79
+ - spec/fixtures/bad/too_recursive.txt
80
+ - spec/fixtures/bad/ungrammatical_content.txt
81
+ - spec/fixtures/items.txt
82
+ - spec/spec_helper.rb
83
+ - spec/vdf4r/parser_spec.rb
84
+ - spec/vdf4r/store_spec.rb
85
+ - README.md
86
+ - LICENSE
87
+ homepage: https://github.com/skadistats/vdf4r
88
+ licenses:
89
+ - MIT
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.23.2
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Valve Data Format (VDF) file parser
112
+ test_files: []