scon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/lib/scon/constants.rb +23 -0
- data/lib/scon/generator.rb +162 -0
- data/lib/scon/parser.rb +133 -0
- data/lib/scon/version.rb +3 -0
- data/lib/scon.rb +47 -0
- data/scon.gemspec +33 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 61a64a294cb55566d8184fca83089010a4277ff5
|
4
|
+
data.tar.gz: 23577a76d5a336c369e21953a7e6052b30b787bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f514339fbe3c8403dfa79dbe41f0ea7afdf379951bcea33ce5b9d3677ea1d1064f0172c0dbacb49b5cfb460fa49104c6909fd00c31bf5da956f85ae95d836753
|
7
|
+
data.tar.gz: 88e8161cce48e6ba7abda2ef6e8f7f0ebbc4c810694edd41216e3f08f598e2cc7c32d01d94e951594298a3ac3867dfceeb2cdf2b64f5e14a6d86e2be77dd9dc5
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SCON
|
2
|
+
module Constants
|
3
|
+
DATA = {
|
4
|
+
:byte => 0xA0,
|
5
|
+
:short => 0xA1,
|
6
|
+
:integer => 0xA2,
|
7
|
+
:long => 0xA3,
|
8
|
+
:float => 0xA4,
|
9
|
+
:double => 0xA5,
|
10
|
+
|
11
|
+
:nil => 0xC0,
|
12
|
+
:true => 0xC1,
|
13
|
+
:false => 0xC2,
|
14
|
+
|
15
|
+
:string => 0xD0,
|
16
|
+
|
17
|
+
:obj_start => 0xFA,
|
18
|
+
:obj_end => 0xFB,
|
19
|
+
:arr_start => 0xFC,
|
20
|
+
:arr_end => 0xFD
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module SCON
|
2
|
+
class Generator
|
3
|
+
|
4
|
+
def generate! hashorarray
|
5
|
+
if hashorarray.is_a? Hash
|
6
|
+
return generate_type! hashorarray, :hash
|
7
|
+
elsif hashorarray.is_a? Array
|
8
|
+
return generate_type! hashorarray, :array
|
9
|
+
else
|
10
|
+
throw TypeError.new("Only Hash or Array types can be generated!")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def generate_type! hash, type
|
16
|
+
@unique_keys = {}
|
17
|
+
@header_bytes = []
|
18
|
+
@body_bytes = []
|
19
|
+
@body_bytes << 0xF0 if type == :hash
|
20
|
+
|
21
|
+
if type == :hash
|
22
|
+
generate_keys hash
|
23
|
+
elsif type == :array
|
24
|
+
generate_keys_array hash
|
25
|
+
end
|
26
|
+
select_keys
|
27
|
+
encode_keys
|
28
|
+
|
29
|
+
encode_body hash
|
30
|
+
|
31
|
+
@body_bytes.flatten.pack("c*")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Hash Methods
|
35
|
+
def generate_keys hash
|
36
|
+
hash.each do |key, value|
|
37
|
+
keys = key.to_s
|
38
|
+
@unique_keys[keys] ||= 0
|
39
|
+
@unique_keys[keys] += 1
|
40
|
+
|
41
|
+
if value.is_a? Hash
|
42
|
+
generate_keys value
|
43
|
+
elsif value.is_a? Array
|
44
|
+
generate_keys_array value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_keys_array array
|
50
|
+
array.each do |v|
|
51
|
+
generate_keys v if v.is_a? Hash
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def select_keys
|
56
|
+
selected = @unique_keys.select { |k, v| v >= 2 }
|
57
|
+
@unique_keys = selected.map { |k, v| k }
|
58
|
+
end
|
59
|
+
|
60
|
+
def encode_keys
|
61
|
+
if @unique_keys.length > 0
|
62
|
+
@header_bytes << 0xF1
|
63
|
+
@unique_keys.each do |key|
|
64
|
+
@header_bytes << Conversions.string_bytes(key)
|
65
|
+
end
|
66
|
+
@body_bytes.unshift(@header_bytes)
|
67
|
+
@header_bytes = nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#General Methods
|
72
|
+
def encode_body object
|
73
|
+
if object.is_a? Hash
|
74
|
+
object.each do |k, v|
|
75
|
+
encode_value k, v, :hash
|
76
|
+
end
|
77
|
+
elsif object.is_a? Array
|
78
|
+
counter = 0
|
79
|
+
object.each_with_index do |v, i|
|
80
|
+
encode_value i, v, :array, counter
|
81
|
+
counter += 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def encode_value key, value, parent_type, array_counter=0
|
87
|
+
instruct, serial = 0, nil
|
88
|
+
if value.is_a? Hash
|
89
|
+
@body_bytes << Constants::DATA[:obj_start]
|
90
|
+
encode_key key, parent_type, array_counter
|
91
|
+
value.each do |k, v|
|
92
|
+
encode_value k, v, :hash
|
93
|
+
end
|
94
|
+
@body_bytes << Constants::DATA[:obj_end]
|
95
|
+
return
|
96
|
+
elsif value.is_a? Array
|
97
|
+
@body_bytes << Constants::DATA[:arr_start]
|
98
|
+
encode_key key, parent_type, array_counter
|
99
|
+
counter = 0
|
100
|
+
value.each_with_index do |v, i|
|
101
|
+
encode_value i, v, :array, counter
|
102
|
+
counter += 1
|
103
|
+
end
|
104
|
+
@body_bytes << Constants::DATA[:arr_end]
|
105
|
+
return
|
106
|
+
elsif value.is_a? Fixnum
|
107
|
+
instruct, serial = auto_number(value)
|
108
|
+
elsif value.is_a? Float
|
109
|
+
instruct = Constants::DATA[:float]
|
110
|
+
serial = Conversions.float_bytes(value)
|
111
|
+
elsif value.is_a? String
|
112
|
+
bytes = Conversions.string_bytes(value)
|
113
|
+
instruct = bytes[0]
|
114
|
+
serial = bytes[1]
|
115
|
+
elsif value.is_a?(TrueClass)
|
116
|
+
instruct = Constants::DATA[:true]
|
117
|
+
elsif value.is_a?(FalseClass)
|
118
|
+
instruct = Constants::DATA[:false]
|
119
|
+
elsif value.nil?
|
120
|
+
instruct = Constants::DATA[:nil]
|
121
|
+
end
|
122
|
+
|
123
|
+
@body_bytes << instruct
|
124
|
+
encode_key key, parent_type, array_counter
|
125
|
+
@body_bytes << serial unless serial.nil?
|
126
|
+
end
|
127
|
+
|
128
|
+
def encode_key key, parent_type, array_counter
|
129
|
+
if parent_type == :hash
|
130
|
+
keys = key.to_s
|
131
|
+
encode_key_hash keys
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def encode_key_hash keys
|
136
|
+
if @unique_keys.include? keys
|
137
|
+
index = @unique_keys.index(keys)
|
138
|
+
@body_bytes << auto_number(index)
|
139
|
+
else
|
140
|
+
@body_bytes << Conversions.string_bytes(keys)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def auto_number value
|
145
|
+
returnval = []
|
146
|
+
if value <= 0x99 && value >= 0 # Byte, no following data bytes
|
147
|
+
returnval[0] = value
|
148
|
+
elsif value <= ((2**16)/2-1) && value >= -(((2**16)/2-1)) # Short, following bytes
|
149
|
+
returnval[0] = Constants::DATA[:short]
|
150
|
+
returnval[1] = Conversions.short_bytes(value)
|
151
|
+
elsif value <= ((2**32)/2-1) && value >= -(((2**32)/2-1)) # Integer, following bytes
|
152
|
+
returnval[0] = Constants::DATA[:integer]
|
153
|
+
returnval[1] = Conversions.int_bytes(value)
|
154
|
+
else # Long, following bytes
|
155
|
+
returnval[0] = Constants::DATA[:long]
|
156
|
+
returnval[1] = Conversions.long_bytes(value)
|
157
|
+
end
|
158
|
+
returnval
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
data/lib/scon/parser.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
module SCON
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
def parse! data
|
5
|
+
@data = data.bytes
|
6
|
+
if @data[0] == 0xF1 # Has Key Lookup
|
7
|
+
@keyassoc = []
|
8
|
+
@data.shift
|
9
|
+
while (temp = parse_inline_string(@data)) != false
|
10
|
+
@keyassoc << temp
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
if @data[0] == 0xF0 # Is a Hash
|
15
|
+
@data.shift
|
16
|
+
root = {}
|
17
|
+
hash_parse_entries root
|
18
|
+
return root
|
19
|
+
else # Is an Array
|
20
|
+
root = []
|
21
|
+
array_parse_entries root
|
22
|
+
return root
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def parse_inline_string data
|
29
|
+
if (data[0] > 0xD0) && (data[0] < (0xD0 + 32))
|
30
|
+
count = data.shift
|
31
|
+
return data.shift(count - 0xD0).pack("c*")
|
32
|
+
elsif data[0] == 0xD0
|
33
|
+
data.shift
|
34
|
+
str = []
|
35
|
+
while (tmp = data.shift) != 0x03
|
36
|
+
str << tmp
|
37
|
+
end
|
38
|
+
return str.pack("c*")
|
39
|
+
else
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_string instruct
|
45
|
+
if (instruct > 0xD0) && (instruct < (0xD0 + 32))
|
46
|
+
return @data.shift(instruct - 0xD0).pack("c*")
|
47
|
+
elsif instruct == 0xD0
|
48
|
+
str = []
|
49
|
+
while (tmp = @data.shift) != 0x03
|
50
|
+
str << tmp
|
51
|
+
end
|
52
|
+
return str.pack("c*")
|
53
|
+
else
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_value instruct
|
59
|
+
if instruct <= 0x99 && instruct >= 0 # Byte, no following data bytes
|
60
|
+
return instruct
|
61
|
+
elsif instruct == Constants::DATA[:byte]
|
62
|
+
return @data.shift[0]
|
63
|
+
elsif instruct == Constants::DATA[:short]
|
64
|
+
return @data.shift(2).pack("c*").unpack("s<")[0]
|
65
|
+
elsif instruct == Constants::DATA[:integer]
|
66
|
+
return @data.shift(4).pack("c*").unpack("i<")[0]
|
67
|
+
elsif instruct == Constants::DATA[:long]
|
68
|
+
return @data.shift(8).pack("c*").unpack("l<")[0]
|
69
|
+
elsif instruct == Constants::DATA[:float]
|
70
|
+
return @data.shift(4).pack("c*").unpack("e")[0]
|
71
|
+
elsif instruct == Constants::DATA[:double]
|
72
|
+
return @data.shift(8).pack("c*").unpack("E")[0]
|
73
|
+
elsif instruct == Constants::DATA[:nil]
|
74
|
+
return nil
|
75
|
+
elsif instruct == Constants::DATA[:true]
|
76
|
+
return true
|
77
|
+
elsif instruct == Constants::DATA[:false]
|
78
|
+
return false
|
79
|
+
elsif (tmp = parse_string(instruct)) != false
|
80
|
+
return tmp
|
81
|
+
elsif instruct == Constants::DATA[:obj_start]
|
82
|
+
root = {}
|
83
|
+
hash_parse_entries root
|
84
|
+
return root
|
85
|
+
elsif instruct == Constants::DATA[:arr_start]
|
86
|
+
root = []
|
87
|
+
array_parse_entries root
|
88
|
+
return root
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def hash_parse_entries parent
|
93
|
+
closed = false
|
94
|
+
while !closed
|
95
|
+
data_type = @data.shift
|
96
|
+
|
97
|
+
if data_type == Constants::DATA[:obj_end]
|
98
|
+
closed = true
|
99
|
+
next
|
100
|
+
end
|
101
|
+
|
102
|
+
index_meta = @data.shift
|
103
|
+
index_val = parse_value index_meta
|
104
|
+
data_value = parse_value data_type
|
105
|
+
|
106
|
+
if index_val.is_a? Fixnum
|
107
|
+
parent[@keyassoc[index_val]] = data_value
|
108
|
+
elsif index_val.is_a? String
|
109
|
+
parent[index_val] = data_value
|
110
|
+
end
|
111
|
+
|
112
|
+
closed = true if @data.length == 0
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def array_parse_entries parent
|
117
|
+
closed = false
|
118
|
+
while !closed
|
119
|
+
data_type = @data.shift
|
120
|
+
|
121
|
+
if data_type == Constants::DATA[:arr_end]
|
122
|
+
closed = true
|
123
|
+
next
|
124
|
+
end
|
125
|
+
data_value = parse_value data_type
|
126
|
+
parent << data_value
|
127
|
+
|
128
|
+
closed = true if @data.length == 0
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
data/lib/scon/version.rb
ADDED
data/lib/scon.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "scon/version"
|
2
|
+
require "scon/constants"
|
3
|
+
require "scon/generator"
|
4
|
+
require "scon/parser"
|
5
|
+
|
6
|
+
module SCON
|
7
|
+
|
8
|
+
def SCON.generate! hashorarray
|
9
|
+
SCON::Generator.new.generate! hashorarray
|
10
|
+
end
|
11
|
+
|
12
|
+
def SCON.parse! data
|
13
|
+
SCON::Parser.new.parse! data
|
14
|
+
end
|
15
|
+
|
16
|
+
class Conversions
|
17
|
+
|
18
|
+
def self.int_bytes int
|
19
|
+
[int].pack("i<").bytes
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.short_bytes short
|
23
|
+
[short].pack("s<").bytes
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.long_bytes long
|
27
|
+
[long].pack("l<").bytes
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.string_bytes str
|
31
|
+
bytes = str.bytes
|
32
|
+
instr = 0xD0
|
33
|
+
if bytes.length < 32
|
34
|
+
instr += bytes.length
|
35
|
+
else
|
36
|
+
bytes << 0x03
|
37
|
+
end
|
38
|
+
[instr, bytes]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.float_bytes float
|
42
|
+
[float].pack("e").bytes
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/scon.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'scon/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "scon"
|
8
|
+
spec.version = SCON::VERSION
|
9
|
+
spec.authors = ["JacisNonsense"]
|
10
|
+
spec.email = ["jaci.brunning@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Simple and Compressed Object Notation}
|
13
|
+
spec.description = %q{Simple and Compressed Object Notation (SCON) serializes arrays
|
14
|
+
and objects into a binary format, all the while using as little space as possible by
|
15
|
+
reusing duplicate keys with a binary reference and clever type definitions.}
|
16
|
+
spec.homepage = "http://www.github.com/JacisNonsense/scon"
|
17
|
+
|
18
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
19
|
+
# delete this section to allow pushing this gem to any host.
|
20
|
+
# if spec.respond_to?(:metadata)
|
21
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
22
|
+
# else
|
23
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
24
|
+
# end
|
25
|
+
|
26
|
+
spec.bindir = "bin"
|
27
|
+
spec.files = Dir.glob("lib/**/*") + ['Rakefile', 'scon.gemspec', 'Gemfile', 'Rakefile']
|
28
|
+
spec.executables = []
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler"
|
32
|
+
spec.add_development_dependency "rake"
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JacisNonsense
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: |-
|
42
|
+
Simple and Compressed Object Notation (SCON) serializes arrays
|
43
|
+
and objects into a binary format, all the while using as little space as possible by
|
44
|
+
reusing duplicate keys with a binary reference and clever type definitions.
|
45
|
+
email:
|
46
|
+
- jaci.brunning@gmail.com
|
47
|
+
executables: []
|
48
|
+
extensions: []
|
49
|
+
extra_rdoc_files: []
|
50
|
+
files:
|
51
|
+
- Gemfile
|
52
|
+
- Rakefile
|
53
|
+
- lib/scon.rb
|
54
|
+
- lib/scon/constants.rb
|
55
|
+
- lib/scon/generator.rb
|
56
|
+
- lib/scon/parser.rb
|
57
|
+
- lib/scon/version.rb
|
58
|
+
- scon.gemspec
|
59
|
+
homepage: http://www.github.com/JacisNonsense/scon
|
60
|
+
licenses: []
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.4.5
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Simple and Compressed Object Notation
|
82
|
+
test_files: []
|