escape_java_properties 0.0.3
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +106 -0
- data/Rakefile +12 -0
- data/escape_java_properties.gemspec +27 -0
- data/lib/escape_java_properties.rb +46 -0
- data/lib/escape_java_properties/encoding.rb +56 -0
- data/lib/escape_java_properties/encoding/separators.rb +52 -0
- data/lib/escape_java_properties/encoding/special_chars.rb +49 -0
- data/lib/escape_java_properties/encoding/unicode.rb +61 -0
- data/lib/escape_java_properties/generating.rb +1 -0
- data/lib/escape_java_properties/generating/generator.rb +56 -0
- data/lib/escape_java_properties/parsing.rb +2 -0
- data/lib/escape_java_properties/parsing/normalizer.rb +77 -0
- data/lib/escape_java_properties/parsing/parser.rb +64 -0
- data/lib/escape_java_properties/properties.rb +64 -0
- data/lib/escape_java_properties/version.rb +3 -0
- data/spec/escape_java_properties/encoding/separators_spec.rb +19 -0
- data/spec/escape_java_properties/encoding/special_chars_spec.rb +23 -0
- data/spec/escape_java_properties/encoding/unicode_spec.rb +29 -0
- data/spec/escape_java_properties/encoding_spec.rb +72 -0
- data/spec/escape_java_properties/generating/generator_spec.rb +47 -0
- data/spec/escape_java_properties/parsing/normalizer_spec.rb +13 -0
- data/spec/escape_java_properties/parsing/parser_spec.rb +36 -0
- data/spec/escape_java_properties/version_spec.rb +7 -0
- data/spec/escape_java_properties_spec.rb +85 -0
- data/spec/fixtures/test.properties +41 -0
- data/spec/fixtures/test_normalized.properties +14 -0
- data/spec/fixtures/test_out.properties +11 -0
- data/spec/fixtures/test_out_skip_separators.properties +11 -0
- data/spec/fixtures/test_out_skip_special_chars.properties +14 -0
- data/spec/fixtures/test_out_skip_unicode.properties +11 -0
- data/spec/helper.rb +19 -0
- metadata +151 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'escape_java_properties/generating/generator'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module EscapeJavaProperties
|
2
|
+
module Generating
|
3
|
+
# This module allows generating the content of a properties file
|
4
|
+
# base on a {Properties} object (or any other hash like structure)
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# Generator.generate({:item => "something ה"}) => "item=something \u05d4"
|
8
|
+
#
|
9
|
+
module Generator
|
10
|
+
# Character used for key-value separation
|
11
|
+
# @return [String]
|
12
|
+
KEY_VALUE_SEPARATOR = '='
|
13
|
+
|
14
|
+
# Default options
|
15
|
+
# @return [Hash]
|
16
|
+
DEFAULT_OPTIONS = {
|
17
|
+
:skip_encode_unicode => false,
|
18
|
+
:skip_encode_separators => false,
|
19
|
+
:skip_encode_special_chars => false
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
# Generates a properties file content based on a hash
|
23
|
+
# @param properties [Properties] or simple hash
|
24
|
+
# @param options [Hash]
|
25
|
+
# @option options skip_encode_unicode [Boolean] Skip unicode encoding
|
26
|
+
# @option options skip_encode_separators [Boolean] Skip seperators encoding
|
27
|
+
# @option options skip_encode_special_chars [Boolean] Skip special char encoding
|
28
|
+
# @return [String]
|
29
|
+
def self.generate(properties, options = {})
|
30
|
+
options = DEFAULT_OPTIONS.merge(options)
|
31
|
+
lines = []
|
32
|
+
properties.each do |key, value|
|
33
|
+
lines << build_line(key, value, options)
|
34
|
+
end
|
35
|
+
lines.join("\n")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.build_line(key, value, options)
|
41
|
+
encoded_key = Encoding.encode!(key.to_s.dup, *encoding_skips(false, options))
|
42
|
+
encoded_value = Encoding.encode!(value.to_s.dup, *encoding_skips(true, options))
|
43
|
+
|
44
|
+
encoded_key + KEY_VALUE_SEPARATOR + encoded_value
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.encoding_skips(is_value, options)
|
48
|
+
skips = []
|
49
|
+
skips << Encoding::SKIP_SEPARATORS if is_value || options[:skip_encode_separators]
|
50
|
+
skips << Encoding::SKIP_UNICODE if options[:skip_encode_unicode]
|
51
|
+
skips << Encoding::SKIP_SPECIAL_CHARS if options[:skip_encode_special_chars]
|
52
|
+
skips
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module EscapeJavaProperties
|
2
|
+
module Parsing
|
3
|
+
# Module to normalize the content of a properties file
|
4
|
+
#
|
5
|
+
# @example Normalizes:
|
6
|
+
# # Comment 1
|
7
|
+
# ! Comment 2
|
8
|
+
# item0
|
9
|
+
# item1 = item1
|
10
|
+
# item2 : item2
|
11
|
+
# item3=line 1 \
|
12
|
+
# line 2
|
13
|
+
#
|
14
|
+
# @example Into:
|
15
|
+
#
|
16
|
+
# item0
|
17
|
+
# item1=item1
|
18
|
+
# item2=item2
|
19
|
+
# item3=line 1 line 2
|
20
|
+
#
|
21
|
+
module Normalizer
|
22
|
+
|
23
|
+
# Describes a single normalization rule by replacing content
|
24
|
+
class Rule
|
25
|
+
# Initializes a new rules base on a matching regexp
|
26
|
+
# and a replacement as substitution
|
27
|
+
# @param matcher [Regexp]
|
28
|
+
# @param replacement [String]
|
29
|
+
def initialize(matcher, replacement = '')
|
30
|
+
@matcher = matcher
|
31
|
+
@replacement = replacement
|
32
|
+
end
|
33
|
+
|
34
|
+
# Apply the substitution to the text in place
|
35
|
+
# @param text [string]
|
36
|
+
# @return [String]
|
37
|
+
def apply!(text)
|
38
|
+
text.gsub!(@matcher, @replacement)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Collection of ordered rules
|
43
|
+
RULES = []
|
44
|
+
|
45
|
+
# Removes leading whitespace
|
46
|
+
RULES << Rule.new(/^\s+/)
|
47
|
+
|
48
|
+
# Strings ending with \ are concatenated (only if the number is odd)
|
49
|
+
RULES << Rule.new(/^(.*[^\\]((\\){2})*)\\$\s+/, '\1')
|
50
|
+
|
51
|
+
# Removes comments (after the application of the logic line concatenation)
|
52
|
+
RULES << Rule.new(/^\s*[!\#].*$/)
|
53
|
+
|
54
|
+
# Removes empty lines
|
55
|
+
RULES << Rule.new(/^$(\r\n?|\n)/)
|
56
|
+
|
57
|
+
# Unescape double backslashes
|
58
|
+
RULES << Rule.new(/\\\\/, '\\')
|
59
|
+
|
60
|
+
# Remove whitespace around delimiters and replace with =
|
61
|
+
RULES << Rule.new(/^((?:(?:\\[=: \t])|[^=: \t])+)[ \t]*[=: \t][ \t]*/, '\1=')
|
62
|
+
|
63
|
+
RULES.freeze
|
64
|
+
|
65
|
+
# Normalizes the content of a properties file content by applying the RULES
|
66
|
+
# @param text [String]
|
67
|
+
# @return [String]
|
68
|
+
def self.normalize!(text)
|
69
|
+
RULES.each do |rule|
|
70
|
+
rule.apply!(text)
|
71
|
+
end
|
72
|
+
text
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module EscapeJavaProperties
|
3
|
+
module Parsing
|
4
|
+
# This module allows parsing of a properties file content string
|
5
|
+
# into a {Properties} object
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Parser.parse("item=something \u05d4") => {:item => "something ה"}
|
9
|
+
#
|
10
|
+
module Parser
|
11
|
+
|
12
|
+
# Symbol which separates key from value after normalization
|
13
|
+
# @return [String]
|
14
|
+
KEY_VALUE_MARKER = '='
|
15
|
+
|
16
|
+
# Symbol which escapes a KEY_VALUE_MARKER in the key name
|
17
|
+
# @return [String]
|
18
|
+
KEY_ESCAPE = '\\'
|
19
|
+
|
20
|
+
# Marker for a line which only consists of an key w/o value
|
21
|
+
# @return [Regexp]
|
22
|
+
KEY_ONLY_MARKER = /^(\S+)$/
|
23
|
+
|
24
|
+
# Parses a string into a {Properties} object
|
25
|
+
# @param text [String]
|
26
|
+
# @return [Properties]
|
27
|
+
def self.parse(text)
|
28
|
+
properties = Properties.new
|
29
|
+
Normalizer.normalize!(text)
|
30
|
+
text.each_line do |line|
|
31
|
+
key, value = extract_key_and_value(line.chomp)
|
32
|
+
append_to_properties(properties, key, value)
|
33
|
+
end
|
34
|
+
properties
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def self.extract_key_and_value(line)
|
40
|
+
# A line must be handled char by char to handled escaped '=' chars in the key name
|
41
|
+
key = StringIO.new
|
42
|
+
value = StringIO.new
|
43
|
+
key_complete = false
|
44
|
+
last_token = ''
|
45
|
+
line.each_char do |char|
|
46
|
+
if !key_complete && char == KEY_VALUE_MARKER && last_token != KEY_ESCAPE
|
47
|
+
key_complete = true
|
48
|
+
else
|
49
|
+
(key_complete ? value : key) << char
|
50
|
+
end
|
51
|
+
last_token = char
|
52
|
+
end
|
53
|
+
[key.string, value.string]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.append_to_properties(properties, key, value)
|
57
|
+
unless key.nil? && value.nil?
|
58
|
+
properties[Encoding.decode!(key).to_sym] = Encoding.decode!(value, Encoding::SKIP_SEPARATORS)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module EscapeJavaProperties
|
2
|
+
# Simple representation of a properties file content
|
3
|
+
# by an extension of a Hash object a la ActiveSupport::HashWithIndifferentAccess, with underlying symbol keys
|
4
|
+
class Properties < Hash
|
5
|
+
|
6
|
+
# Assigns a new value to the hash:
|
7
|
+
#
|
8
|
+
# hash = EscapeJavaProperties::Properties.new
|
9
|
+
# hash[:key] = 'value'
|
10
|
+
#
|
11
|
+
# This value can be later fetched using either +:key+ or +'key'+.
|
12
|
+
def []=(key, value)
|
13
|
+
super(convert_key(key), value)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks the hash for a key matching the argument passed in:
|
17
|
+
#
|
18
|
+
# hash = EscapeJavaProperties::Properties.new
|
19
|
+
# hash['key'] = 'value'
|
20
|
+
# hash.key?(:key) # => true
|
21
|
+
# hash.key?('key') # => true
|
22
|
+
def key?(key)
|
23
|
+
super(convert_key(key))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
|
27
|
+
# either a string or a symbol:
|
28
|
+
#
|
29
|
+
# counters = EscapeJavaProperties::Properties.new
|
30
|
+
# counters[:foo] = 1
|
31
|
+
#
|
32
|
+
# counters.fetch('foo') # => 1
|
33
|
+
# counters.fetch(:bar, 0) # => 0
|
34
|
+
# counters.fetch(:bar) {|key| 0} # => 0
|
35
|
+
# counters.fetch(:zoo) # => KeyError: key not found: "zoo"
|
36
|
+
def fetch(key, *extras)
|
37
|
+
super(convert_key(key), *extras)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an array of the values at the specified indices:
|
41
|
+
#
|
42
|
+
# hash = EscapeJavaProperties::Properties.new
|
43
|
+
# hash[:a] = 'x'
|
44
|
+
# hash[:b] = 'y'
|
45
|
+
# hash.values_at('a', 'b') # => ["x", "y"]
|
46
|
+
def values_at(*indices)
|
47
|
+
indices.collect {|key| self[convert_key(key)]}
|
48
|
+
end
|
49
|
+
|
50
|
+
# Removes the specified key from the hash.
|
51
|
+
def delete(key)
|
52
|
+
super(convert_key(key))
|
53
|
+
end
|
54
|
+
|
55
|
+
def [](key)
|
56
|
+
self.fetch(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
def convert_key(key)
|
61
|
+
key.kind_of?(String) ? key.to_sym : key
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe EscapeJavaProperties::Encoding::Separators do
|
4
|
+
subject{ EscapeJavaProperties::Encoding::Separators }
|
5
|
+
let(:raw) { 'this is some = text : with special \\=separators' }
|
6
|
+
let(:raw_normalizd) { 'this is some = text : with special =separators' }
|
7
|
+
let(:encoded){ 'this\\ is\\ some\\ \\=\\ text\\ \\:\\ with\\ special\\ \\=separators' }
|
8
|
+
|
9
|
+
it "encodes separators" do
|
10
|
+
processed = subject.encode!(raw.dup)
|
11
|
+
processed.must_equal encoded
|
12
|
+
end
|
13
|
+
|
14
|
+
it "decodes separators" do
|
15
|
+
processed = subject.decode!(encoded.dup)
|
16
|
+
processed.must_equal raw_normalizd
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe EscapeJavaProperties::Encoding::SpecialChars do
|
4
|
+
subject{ EscapeJavaProperties::Encoding::SpecialChars }
|
5
|
+
let(:raw) { "this is some \n text \t with special\r chars\f" }
|
6
|
+
let(:encoded){ 'this is some \n text \t with special\r chars\f' }
|
7
|
+
|
8
|
+
it "encodes special chars" do
|
9
|
+
processed = subject.encode!(raw.dup)
|
10
|
+
processed.must_equal encoded
|
11
|
+
end
|
12
|
+
|
13
|
+
it "deencodes special chars" do
|
14
|
+
processed = subject.decode!(encoded.dup)
|
15
|
+
processed.must_equal raw
|
16
|
+
end
|
17
|
+
|
18
|
+
it "decodes and encodes to the same" do
|
19
|
+
encoded = subject.encode!(raw.dup)
|
20
|
+
deconded = subject.decode!(encoded)
|
21
|
+
deconded.must_equal raw
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
describe EscapeJavaProperties::Encoding::Unicode do
|
5
|
+
subject{ EscapeJavaProperties::Encoding::Unicode }
|
6
|
+
|
7
|
+
let(:encoded) { 'this is some \u0024 text \U05D4 with unicode' }
|
8
|
+
let(:encoded_normalized) { 'this is some $ text \u05d4 with unicode' }
|
9
|
+
let(:decoded) { 'this is some $ text ה with unicode' }
|
10
|
+
|
11
|
+
it "decodes unicode chars" do
|
12
|
+
subject.decode!(encoded.dup).must_equal decoded
|
13
|
+
end
|
14
|
+
|
15
|
+
it "encodes unicode chars" do
|
16
|
+
subject.encode!(decoded.dup).must_equal encoded_normalized
|
17
|
+
end
|
18
|
+
|
19
|
+
it "encodes unicode chars but has 2-based hex size" do
|
20
|
+
subject.encode!("ה").must_equal '\u05d4'
|
21
|
+
subject.encode!("ᘓ").must_equal '\u1613'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "decodes and encodes" do
|
25
|
+
encoded = subject.encode!(decoded.dup)
|
26
|
+
deconded = subject.decode!(encoded.dup)
|
27
|
+
deconded.must_equal decoded
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
describe EscapeJavaProperties::Encoding do
|
5
|
+
subject{ EscapeJavaProperties::Encoding }
|
6
|
+
|
7
|
+
describe "full" do
|
8
|
+
let(:decoded){ "this is some \n text = with ה" }
|
9
|
+
let(:encoded){ 'this\ is\ some\ \n\ text\ \=\ with\ \u05d4' }
|
10
|
+
|
11
|
+
it "encodes correctly" do
|
12
|
+
input = decoded.dup
|
13
|
+
processed = subject.encode!(input)
|
14
|
+
processed.must_equal encoded
|
15
|
+
end
|
16
|
+
|
17
|
+
it "decodes correctly" do
|
18
|
+
input = encoded.dup
|
19
|
+
processed = subject.decode!(input)
|
20
|
+
processed.must_equal decoded
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "skip_separators" do
|
25
|
+
let(:decoded){ "this is some \n text = with ה" }
|
26
|
+
let(:encoded){ 'this is some \n text = with \u05d4' }
|
27
|
+
|
28
|
+
it "encodes correctly" do
|
29
|
+
input = decoded.dup
|
30
|
+
processed = subject.encode!(input, subject::SKIP_SEPARATORS)
|
31
|
+
processed.must_equal encoded
|
32
|
+
end
|
33
|
+
|
34
|
+
it "decodes correctly" do
|
35
|
+
input = encoded.dup
|
36
|
+
processed = subject.decode!(input, subject::SKIP_SEPARATORS)
|
37
|
+
processed.must_equal decoded
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "skip_unicode" do
|
42
|
+
it "encodes correctly" do
|
43
|
+
input = "this is some \n text = with ה"
|
44
|
+
processed = subject.encode!(input, subject::SKIP_UNICODE)
|
45
|
+
expected = 'this\ is\ some\ \n\ text\ \=\ with\ ה'
|
46
|
+
processed.must_equal expected
|
47
|
+
end
|
48
|
+
|
49
|
+
it "decodes correctly" do
|
50
|
+
input = 'this\ is\ some\ \n\ text\ \=\ with\ \u05d4'
|
51
|
+
processed = subject.decode!(input, subject::SKIP_UNICODE)
|
52
|
+
expected = "this is some \n text = with " + '\u05d4'
|
53
|
+
processed.must_equal expected
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "skip_special_chars" do
|
58
|
+
it "encodes correctly" do
|
59
|
+
input = "this is some \n text = with ה"
|
60
|
+
processed = subject.encode!(input, subject::SKIP_SPECIAL_CHARS)
|
61
|
+
expected = 'this\ is\ some\ ' + "\n" + '\ text\ \=\ with\ \u05d4'
|
62
|
+
processed.must_equal expected
|
63
|
+
end
|
64
|
+
|
65
|
+
it "decodes correctly" do
|
66
|
+
input = 'this\ is\ some\ \n\ text\ \=\ with\ \u05d4'
|
67
|
+
processed = subject.decode!(input, subject::SKIP_SPECIAL_CHARS)
|
68
|
+
expected = 'this is some \n text = with ה'
|
69
|
+
processed.must_equal expected
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
describe EscapeJavaProperties::Generating::Generator do
|
5
|
+
subject { EscapeJavaProperties::Generating::Generator }
|
6
|
+
|
7
|
+
let(:as_hash) do
|
8
|
+
{
|
9
|
+
:item0 => "",
|
10
|
+
:item1 => "item1".freeze,
|
11
|
+
:item2 => "item2",
|
12
|
+
:item3 => "item3",
|
13
|
+
:"it em4".freeze => "item4",
|
14
|
+
:"it=em5" => "item5",
|
15
|
+
:"it:em6" => "item6",
|
16
|
+
:item7 => "line 1 line 2 line 3",
|
17
|
+
:item8 => "line 1 line 2 line 3",
|
18
|
+
:item9 => "line 1 line 2 line 3",
|
19
|
+
:item10 => "test\n\ttestה test\n\ttest test\n\ttest = test"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
it "generates properties file content" do
|
24
|
+
expected = fixture("test_out.properties")
|
25
|
+
content = subject.generate(as_hash)
|
26
|
+
content.must_equal expected
|
27
|
+
end
|
28
|
+
|
29
|
+
it "generates properties file content but skips unicode encoding" do
|
30
|
+
expected = fixture("test_out_skip_unicode.properties")
|
31
|
+
content = subject.generate(as_hash, :skip_encode_unicode => true)
|
32
|
+
content.must_equal expected
|
33
|
+
end
|
34
|
+
|
35
|
+
it "generates properties file content but skips separators encoding" do
|
36
|
+
expected = fixture("test_out_skip_separators.properties")
|
37
|
+
content = subject.generate(as_hash, :skip_encode_separators => true)
|
38
|
+
content.must_equal expected
|
39
|
+
end
|
40
|
+
|
41
|
+
it "generates properties file content but skips special chars encoding" do
|
42
|
+
expected = fixture("test_out_skip_special_chars.properties")
|
43
|
+
content = subject.generate(as_hash, :skip_encode_special_chars => true)
|
44
|
+
content.must_equal expected
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|