prison_parser 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.yardopts +6 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +39 -0
- data/Rakefile +2 -0
- data/lib/prison_parser.rb +9 -0
- data/lib/prison_parser/models.rb +7 -0
- data/lib/prison_parser/models/base.rb +11 -0
- data/lib/prison_parser/models/finance.rb +7 -0
- data/lib/prison_parser/node.rb +99 -0
- data/lib/prison_parser/prison.rb +30 -0
- data/lib/prison_parser/utils.rb +8 -0
- data/lib/prison_parser/utils/parser.rb +118 -0
- data/lib/prison_parser/utils/writer.rb +96 -0
- data/lib/prison_parser/version.rb +3 -0
- data/prison_parser.gemspec +31 -0
- data/spec/fixtures/deathrow.prison +8355 -0
- data/spec/fixtures/full.prison +53599 -0
- data/spec/prison_parser/node_spec.rb +11 -0
- data/spec/prison_parser/prison_spec.rb +9 -0
- data/spec/prison_parser/utils/parser_spec.rb +27 -0
- data/spec/prison_parser/utils/writer_spec.rb +37 -0
- data/spec/spec_helper.rb +20 -0
- metadata +174 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 75a9c80e97dedb4608da793cacfdc8fa6905d4f2
|
4
|
+
data.tar.gz: 9346ac58194348db220677e267918375cb78be48
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0438293e09a82e458a9a2ae476e2858dff53a449d69caf728f9c455db1845d3f0cc4b0ffe440b7cfb0d9414df7c658d275bccbf583550ec6280b0113d98c6de
|
7
|
+
data.tar.gz: ab4562d8aa3f1439c2bf6f67eec72d14a28ee6f5e79334dd1d19a42f2df39dc12516f9cd39cf955aafa5309d5ee1ed38fb614c6bec5c8831b8446152e3ae88a2
|
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Mitch Dempsey
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Prison Architect Parser
|
2
|
+
|
3
|
+
This gem allows you to parse, access, and manipulate a save file from Prison Architect.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'prison_parser'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install prison_parser
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'prison_parser'
|
23
|
+
prison = PrisonParser::Prison.open("myprison.prison")
|
24
|
+
|
25
|
+
puts prison.Finance.Balance # 1318560.0
|
26
|
+
|
27
|
+
prison.save("newprison.prison")
|
28
|
+
|
29
|
+
```
|
30
|
+
|
31
|
+
## Roadmap
|
32
|
+
|
33
|
+
* Ability to convert a prison to a blank, but planned prison.
|
34
|
+
|
35
|
+
## Thanks
|
36
|
+
Special thanks to: [Matvei Stefarov](https://github.com/fragmer) for his work on [PASaveEditor](https://github.com/fragmer/PASaveEditor), which served as the basis for the parsing implementation. To [Jason Dew](https://github.com/jasondew) for letting me use the name of his original library, [prison_parser](https://github.com/jasondew/prison_parser)
|
37
|
+
|
38
|
+
## License
|
39
|
+
Please see the [LICENSE](https://github.com/webdestroya/prison_parser/LICENSE) for more information.
|
data/Rakefile
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
module PrisonParser
|
2
|
+
class Node
|
3
|
+
attr_reader :nodes
|
4
|
+
attr_reader :properties
|
5
|
+
attr_reader :label
|
6
|
+
|
7
|
+
attr_accessor :allow_inline
|
8
|
+
|
9
|
+
def initialize(label=nil)
|
10
|
+
@label = label
|
11
|
+
@nodes = Hash.new
|
12
|
+
@properties = Hash.new
|
13
|
+
@allow_inline = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(name, *args, &block)
|
17
|
+
# Look in Nodes
|
18
|
+
# Look in properties
|
19
|
+
# if ends with ?, cast property to boolean
|
20
|
+
# else SUPER
|
21
|
+
|
22
|
+
if nodes.has_key?(name.to_s)
|
23
|
+
nodes[name.to_s]
|
24
|
+
elsif properties.has_key?(name.to_s)
|
25
|
+
properties[name.to_s]
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def allow_inline?
|
32
|
+
@allow_inline
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
str = StringIO.new("")
|
37
|
+
str.write "<#{label} "
|
38
|
+
str.write properties.entries.map { |e|"#{e[0]}=\"#{e[1]}\"" }.join(", ")
|
39
|
+
str.write ">"
|
40
|
+
str.string
|
41
|
+
end
|
42
|
+
|
43
|
+
# ReadKey, PushProperty
|
44
|
+
def add_property(key, value)
|
45
|
+
return if key == "BEGIN" || key == "END"
|
46
|
+
if properties[key]
|
47
|
+
if properties[key].is_a?(Array)
|
48
|
+
@properties[key] << value
|
49
|
+
else
|
50
|
+
@properties[key] = [properties[key], value]
|
51
|
+
end
|
52
|
+
else
|
53
|
+
@properties[key] = value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_node(node_label)
|
58
|
+
node = Node.new(node_label)
|
59
|
+
if nodes.has_key?(node_label)
|
60
|
+
if nodes[node_label].is_a?(Array)
|
61
|
+
@nodes[node_label] << node
|
62
|
+
else
|
63
|
+
@nodes[node_label] = [nodes[node_label], node]
|
64
|
+
end
|
65
|
+
else
|
66
|
+
@nodes[node_label] = node
|
67
|
+
end
|
68
|
+
node
|
69
|
+
end
|
70
|
+
|
71
|
+
def finished_reading_node(node)
|
72
|
+
# do nothing
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# WRITING
|
77
|
+
|
78
|
+
def write_nodes(writer)
|
79
|
+
nodes.values.each do |node|
|
80
|
+
if node.is_a?(Array)
|
81
|
+
node.map { |n| writer.write_node(n) }
|
82
|
+
else
|
83
|
+
writer.write_node(node)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def write_properties(writer)
|
89
|
+
properties.each_pair do |k,value|
|
90
|
+
if value.is_a?(Array)
|
91
|
+
value.map { |v| writer.write_property(k, v) }
|
92
|
+
else
|
93
|
+
writer.write_property(k, value)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module PrisonParser
|
2
|
+
class Prison < Node
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
super("Prison")
|
6
|
+
end
|
7
|
+
|
8
|
+
# Open a prison from the file specified
|
9
|
+
def self.open(filename)
|
10
|
+
PrisonParser::Utils::Parser.new.load(filename)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Save the prison to the specified filename
|
14
|
+
def save(filename)
|
15
|
+
file = File.open(filename, "w+")
|
16
|
+
PrisonParser::Utils::Writer.new(file).write_prison(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_node(node_label)
|
20
|
+
if PrisonParser::Models.const_defined?(node_label)
|
21
|
+
node = PrisonParser::Models.const_get(node_label).new
|
22
|
+
@nodes[node.label] = node
|
23
|
+
node
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module PrisonParser
|
2
|
+
module Utils
|
3
|
+
class Parser
|
4
|
+
|
5
|
+
attr_reader :tokens
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@tokens = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(filepath)
|
12
|
+
line_num = 0
|
13
|
+
nodes = []
|
14
|
+
@tokens = []
|
15
|
+
currentNode = Prison.new
|
16
|
+
|
17
|
+
file = File.open(filepath, "r")
|
18
|
+
|
19
|
+
while !file.eof? do
|
20
|
+
line = file.readline.strip
|
21
|
+
line_num += 1
|
22
|
+
tokenize(line)
|
23
|
+
|
24
|
+
next if tokens.size == 0
|
25
|
+
|
26
|
+
# start a new node
|
27
|
+
if "BEGIN" == tokens[0]
|
28
|
+
nodes.push(currentNode)
|
29
|
+
|
30
|
+
label = tokens[1]
|
31
|
+
currentNode = currentNode.create_node(label)
|
32
|
+
|
33
|
+
if tokens.size > 2
|
34
|
+
# inline node
|
35
|
+
if tokens.size % 2 != 1
|
36
|
+
raise "Unexpected number of tokens in an inline node definition on line #{line_num}"
|
37
|
+
end
|
38
|
+
|
39
|
+
unless "END" == tokens[tokens.size - 1]
|
40
|
+
raise "Unexpected end of inline node definition on line #{line_num}"
|
41
|
+
end
|
42
|
+
|
43
|
+
tokens.each_slice(2) do |parts|
|
44
|
+
# the first one is bogus :/
|
45
|
+
currentNode.add_property(parts[0], parts[1])
|
46
|
+
end
|
47
|
+
|
48
|
+
upperNode = nodes.pop
|
49
|
+
upperNode.finished_reading_node(currentNode)
|
50
|
+
currentNode = upperNode
|
51
|
+
else
|
52
|
+
currentNode.allow_inline = false
|
53
|
+
end
|
54
|
+
elsif "END" == tokens[0]
|
55
|
+
# end of multi-line section
|
56
|
+
upperNode = nodes.pop
|
57
|
+
upperNode.finished_reading_node(currentNode)
|
58
|
+
currentNode = upperNode
|
59
|
+
else
|
60
|
+
# inside a multi-line section
|
61
|
+
currentNode.add_property(tokens[0], tokens[1])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
if nodes.size != 0
|
66
|
+
raise "Unexpected end of file!"
|
67
|
+
end
|
68
|
+
|
69
|
+
currentNode
|
70
|
+
end
|
71
|
+
|
72
|
+
# Splits a line into a series of tokens
|
73
|
+
def tokenize(line)
|
74
|
+
return if line.size == 0
|
75
|
+
|
76
|
+
@tokens.clear
|
77
|
+
|
78
|
+
token_start = 0
|
79
|
+
i = 0
|
80
|
+
chars = line.chars
|
81
|
+
|
82
|
+
while i < line.size do
|
83
|
+
c = chars[i]
|
84
|
+
if c == ' '
|
85
|
+
# eat the spaces!
|
86
|
+
if token_start != i
|
87
|
+
@tokens << line[token_start, i - token_start]
|
88
|
+
end
|
89
|
+
token_start = i + 1
|
90
|
+
elsif c == '"'
|
91
|
+
# skip ahead to the next quote
|
92
|
+
end_quotes = line.index('"', i + 1)
|
93
|
+
|
94
|
+
end_quotes = -1 if end_quotes.nil?
|
95
|
+
|
96
|
+
@tokens << line[i + 1, end_quotes - i - 1]
|
97
|
+
i = end_quotes
|
98
|
+
token_start = i + 1
|
99
|
+
else
|
100
|
+
# skip ahead to the next space
|
101
|
+
next_space = line.index(' ', i)
|
102
|
+
next_space = -1 if next_space.nil?
|
103
|
+
i = next_space - 1
|
104
|
+
break if (i < 0)
|
105
|
+
end
|
106
|
+
i += 1
|
107
|
+
end
|
108
|
+
|
109
|
+
if token_start < line.size
|
110
|
+
# append the remainder of the string, after we ran out of spaces
|
111
|
+
@tokens << line[token_start, line.size - token_start]
|
112
|
+
end
|
113
|
+
|
114
|
+
return @tokens
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module PrisonParser
|
2
|
+
module Utils
|
3
|
+
class Writer
|
4
|
+
INDENT_STR = " "
|
5
|
+
|
6
|
+
attr_reader :stream
|
7
|
+
|
8
|
+
|
9
|
+
def initialize(stream=nil)
|
10
|
+
@stream = stream || StringIO.new("")
|
11
|
+
@inline = false
|
12
|
+
@inline_stack = []
|
13
|
+
@indent = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def write_prison(prison)
|
17
|
+
write "\n"
|
18
|
+
write_node_data(prison)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_node(node)
|
23
|
+
return nil if node.nil?
|
24
|
+
@inline_stack.push(@inline)
|
25
|
+
@inline = node_inline?(node)
|
26
|
+
|
27
|
+
write_indent
|
28
|
+
@indent += 1
|
29
|
+
write "BEGIN "
|
30
|
+
write_value node.label
|
31
|
+
|
32
|
+
if @inline
|
33
|
+
write " "
|
34
|
+
else
|
35
|
+
write "\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
write_node_data(node)
|
39
|
+
|
40
|
+
@indent -= 1
|
41
|
+
|
42
|
+
write_indent unless @inline
|
43
|
+
write "END\n"
|
44
|
+
|
45
|
+
@inline = @inline_stack.pop
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_indent(indent = nil)
|
51
|
+
indent ||= @indent
|
52
|
+
return if indent <= 0
|
53
|
+
write INDENT_STR*indent
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_property(key, value)
|
57
|
+
return if value.nil?
|
58
|
+
|
59
|
+
write_indent unless @inline
|
60
|
+
|
61
|
+
write_value key
|
62
|
+
write " "
|
63
|
+
write_value value
|
64
|
+
|
65
|
+
write (@inline ? " " : "\n")
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_node_data(node)
|
70
|
+
node.write_properties(self)
|
71
|
+
node.write_nodes(self)
|
72
|
+
end
|
73
|
+
|
74
|
+
def write_value(value)
|
75
|
+
if quote?(value)
|
76
|
+
write "\"#{value}\""
|
77
|
+
else
|
78
|
+
write value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def node_inline?(node)
|
83
|
+
node.nodes.size == 0 && node.properties.size < 13 && node.allow_inline?
|
84
|
+
end
|
85
|
+
|
86
|
+
def quote?(value)
|
87
|
+
!value.index(' ').nil?
|
88
|
+
end
|
89
|
+
|
90
|
+
def write(data)
|
91
|
+
stream.write data
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|