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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +106 -0
  7. data/Rakefile +12 -0
  8. data/escape_java_properties.gemspec +27 -0
  9. data/lib/escape_java_properties.rb +46 -0
  10. data/lib/escape_java_properties/encoding.rb +56 -0
  11. data/lib/escape_java_properties/encoding/separators.rb +52 -0
  12. data/lib/escape_java_properties/encoding/special_chars.rb +49 -0
  13. data/lib/escape_java_properties/encoding/unicode.rb +61 -0
  14. data/lib/escape_java_properties/generating.rb +1 -0
  15. data/lib/escape_java_properties/generating/generator.rb +56 -0
  16. data/lib/escape_java_properties/parsing.rb +2 -0
  17. data/lib/escape_java_properties/parsing/normalizer.rb +77 -0
  18. data/lib/escape_java_properties/parsing/parser.rb +64 -0
  19. data/lib/escape_java_properties/properties.rb +64 -0
  20. data/lib/escape_java_properties/version.rb +3 -0
  21. data/spec/escape_java_properties/encoding/separators_spec.rb +19 -0
  22. data/spec/escape_java_properties/encoding/special_chars_spec.rb +23 -0
  23. data/spec/escape_java_properties/encoding/unicode_spec.rb +29 -0
  24. data/spec/escape_java_properties/encoding_spec.rb +72 -0
  25. data/spec/escape_java_properties/generating/generator_spec.rb +47 -0
  26. data/spec/escape_java_properties/parsing/normalizer_spec.rb +13 -0
  27. data/spec/escape_java_properties/parsing/parser_spec.rb +36 -0
  28. data/spec/escape_java_properties/version_spec.rb +7 -0
  29. data/spec/escape_java_properties_spec.rb +85 -0
  30. data/spec/fixtures/test.properties +41 -0
  31. data/spec/fixtures/test_normalized.properties +14 -0
  32. data/spec/fixtures/test_out.properties +11 -0
  33. data/spec/fixtures/test_out_skip_separators.properties +11 -0
  34. data/spec/fixtures/test_out_skip_special_chars.properties +14 -0
  35. data/spec/fixtures/test_out_skip_unicode.properties +11 -0
  36. data/spec/helper.rb +19 -0
  37. 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,2 @@
1
+ require 'escape_java_properties/parsing/normalizer'
2
+ require 'escape_java_properties/parsing/parser'
@@ -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,3 @@
1
+ module EscapeJavaProperties
2
+ VERSION = "0.0.3"
3
+ 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