proper_properties 0.0.1

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 +104 -0
  7. data/Rakefile +12 -0
  8. data/lib/proper_properties/encoding/separators.rb +52 -0
  9. data/lib/proper_properties/encoding/special_chars.rb +48 -0
  10. data/lib/proper_properties/encoding/unicode.rb +61 -0
  11. data/lib/proper_properties/encoding.rb +56 -0
  12. data/lib/proper_properties/generating/generator.rb +56 -0
  13. data/lib/proper_properties/generating.rb +1 -0
  14. data/lib/proper_properties/parsing/normalizer.rb +77 -0
  15. data/lib/proper_properties/parsing/parser.rb +64 -0
  16. data/lib/proper_properties/parsing.rb +2 -0
  17. data/lib/proper_properties/properties.rb +6 -0
  18. data/lib/proper_properties/version.rb +3 -0
  19. data/lib/proper_properties.rb +46 -0
  20. data/proper_properties.gemspec +25 -0
  21. data/spec/fixtures/test.properties +41 -0
  22. data/spec/fixtures/test_normalized.properties +14 -0
  23. data/spec/fixtures/test_out.properties +11 -0
  24. data/spec/fixtures/test_out_skip_separators.properties +11 -0
  25. data/spec/fixtures/test_out_skip_special_chars.properties +14 -0
  26. data/spec/fixtures/test_out_skip_unicode.properties +11 -0
  27. data/spec/helper.rb +20 -0
  28. data/spec/proper_properties/encoding/separators_spec.rb +19 -0
  29. data/spec/proper_properties/encoding/special_chars_spec.rb +23 -0
  30. data/spec/proper_properties/encoding/unicode_spec.rb +29 -0
  31. data/spec/proper_properties/encoding_spec.rb +72 -0
  32. data/spec/proper_properties/generating/generator_spec.rb +47 -0
  33. data/spec/proper_properties/parsing/normalizer_spec.rb +13 -0
  34. data/spec/proper_properties/parsing/parser_spec.rb +36 -0
  35. data/spec/proper_properties/version_spec.rb +7 -0
  36. data/spec/proper_properties_spec.rb +43 -0
  37. metadata +152 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b0e73ae3fb455d335998b9ced5a388d03e923384
4
+ data.tar.gz: 174890d5265606617411593f037279ef2bbc9c81
5
+ SHA512:
6
+ metadata.gz: b402ae7a76d56183cf457ce71e97ce8f5f68dae4fd64afecb130abdfcf7aa493214a2bc65d56138f48991bd8193643bd2826953c1a22cf7d11134a7b3cc9213e
7
+ data.tar.gz: 711cf90f60b7ee205cfe3f921155b3ae3b0fdfb8545b3e886d8a5e717ab2cb62d48a81105ec2afe6ad176ade4e9ce300f6de8b2bbc3b156cd05568b071e7c778
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .ruby-version
2
+ .ruby-gemset
3
+ Gemfile.lock
4
+ coverage
5
+ pkg
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in proper_properties.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2014 Jonas Thiel (Applies to code available as https://github.com/jnbt/java-properties 1f2c4b008d69d0eae1084b1adfdea814e2b29899)
2
+ Copyright (c) 2014 tnarik (Modifications in https://github.com/tnarik/proper_properties)
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # ProperProperties
2
+
3
+ [![Build Status](http://img.shields.io/travis/tnarik/proper_properties.svg)](https://travis-ci.org/tnarik/proper_properties)
4
+ [![Code Climate](http://img.shields.io/codeclimate/github/tnarik/proper_properties.svg)](https://codeclimate.com/github/tnarik/proper_properties)
5
+ [![Coveralls](http://img.shields.io/coveralls/tnarik/proper_properties.svg)](https://coveralls.io/r/tnarik/proper_properties)
6
+ [![RubyGems](http://img.shields.io/gem/v/proper_properties.svg)](http://rubygems.org/gems/proper_properties)
7
+ [![Gemnasium](http://img.shields.io/gemnasium/tnarik/proper_properties.svg)](https://gemnasium.com/tnarik/proper_properties)
8
+
9
+ A ruby library to read and write [Java properties files](http://en.wikipedia.org/wiki/.properties), which format is better describe [in the Java documentation](http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader\)).
10
+
11
+ ## Installation
12
+
13
+ Install via Rubygems or Gemfile
14
+
15
+ ```zsh
16
+ $ gem install proper_properties
17
+ ```
18
+
19
+ ## Loading files
20
+
21
+ You can load a valid Java properties file from the file system using a path:
22
+
23
+ ```ruby
24
+ properties = ProperProperties.load("path/to/my.properties")
25
+ properties[:foo] # => "bar"
26
+ ```
27
+
28
+ If have already the content of the properties file at hand than parse the content as:
29
+
30
+ ```ruby
31
+ properties = ProperProperties.load("foo=bar")
32
+ properties[:foo] # => "bar"
33
+ ```
34
+
35
+ ## Writing files
36
+
37
+ You can write any Hash-like structure as a properties file:
38
+
39
+ ```ruby
40
+ hash = {:foo => "bar"}
41
+ ProperProperties.write(hash, "path/to/my.properties")
42
+ ```
43
+
44
+ Or if you want to omit the file you can receive the content directly:
45
+
46
+ ```ruby
47
+ hash = {:foo => "bar"}
48
+ ProperProperties.generate(hash) # => "foo=bar"
49
+ ```
50
+
51
+ ## Encodings and special chars
52
+
53
+ Although ISO 8859-1 is assumed for Java properties files, they normally use UTF-8 encoding. This tool tries to convert them:
54
+
55
+ ```
56
+ "ה" <=> "\u05d4"
57
+ ```
58
+
59
+ The tool also escaped every '=', ' ' and ':' in the name part of a property line:
60
+
61
+ ```ruby
62
+ ProperProperties.generate({"i : like=strange" => "bar"})
63
+ # => "i\ \:\ like\=strange=bar"
64
+ ```
65
+
66
+ ## Multi line and line breaks
67
+
68
+ In Java properties files there is distinction between natural lines and logical lines (than may span several lines by escaping the line terminator characters).
69
+
70
+ Assume the following input:
71
+
72
+ ```ini
73
+ my=This is a multi \
74
+ line content with only \n one line break
75
+ ```
76
+
77
+ The parses would read:
78
+
79
+ ```ruby
80
+ {:my => "This is a multi line content which only \n one line break"}
81
+ ```
82
+
83
+ In the opposite direction line breaks will be correctly escaped but the generator will never use multi line values.
84
+
85
+ ## Contributing
86
+
87
+ 1. [Fork it!](https://github.com/tnarik/proper_properties/fork)
88
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
89
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
90
+ 4. Push to the branch (`git push origin my-new-feature`)
91
+ 5. Create new Pull Request
92
+
93
+ ## Author
94
+
95
+ - Tnarik Innael (@tnarik)
96
+ - Jonas Thiel (@jonasthiel) : [original project](https://github.com/jnbt/java-properties) from commit [1f2c4b008d69d0eae1084b1adfdea814e2b29899]
97
+
98
+ ## References
99
+
100
+ For a proper description about the properties file format have a look at the [Java Plattform documentation](http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader\)).
101
+
102
+ ## License
103
+
104
+ This gem is released under the MIT License. See the LICENSE.txt file for further details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+
5
+ desc 'Run tests'
6
+ task :default => :test
7
+
8
+ Rake::TestTask.new(:test) do |test|
9
+ test.test_files = FileList['spec/**/*_spec.rb']
10
+ test.libs << 'spec'
11
+ test.verbose = true
12
+ end
@@ -0,0 +1,52 @@
1
+ module ProperProperties
2
+ module Encoding
3
+ # Module to escape separators as : or =
4
+ # @see ProperProperties::Encoding
5
+ module Separators
6
+
7
+ # Marker for all separators
8
+ # @return [Regexp]
9
+ ENCODE_SEPARATOR_MARKER = /[ :=]/
10
+
11
+ # Marker for already escaped separators
12
+ # @return [Regexp]
13
+ ESCAPING_MARKER = /\\/
14
+
15
+ # Char to use for escaping
16
+ # @return [String]
17
+ ESCAPE = "\\"
18
+
19
+ # Marker for all escaped separators
20
+ # @return [Regexp]
21
+ DECODE_SEPARATOR_MARKER = /\\([ :=])/
22
+
23
+ # Escapes all not already escaped separators
24
+ # @param text [text]
25
+ # @return [String] The escaped text for chaining
26
+ def self.encode!(text)
27
+ buffer = StringIO.new
28
+ last_token = ''
29
+ text.each_char do |char|
30
+ if char =~ ENCODE_SEPARATOR_MARKER && last_token !~ ESCAPING_MARKER
31
+ buffer << ESCAPE
32
+ end
33
+ buffer << char
34
+ last_token = char
35
+ end
36
+ text.replace buffer.string
37
+ text
38
+ end
39
+
40
+ # Removes escapes from escaped separators
41
+ # @param text [text]
42
+ # @return [String] The unescaped text for chaining
43
+ def self.decode!(text)
44
+ text.gsub!(DECODE_SEPARATOR_MARKER) do
45
+ $1
46
+ end
47
+ text
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ module ProperProperties
2
+ module Encoding
3
+ # Module to escape and unescape special chars
4
+ # @see ProperProperties::Encoding
5
+ module SpecialChars
6
+
7
+ # Lookup table for escaping special chars
8
+ # @return [Hash]
9
+ ESCAPING = {
10
+ "\t" => '\\t',
11
+ "\r" => '\\r',
12
+ "\n" => '\\n',
13
+ "\f" => '\\f'
14
+ }.freeze
15
+
16
+ # Lookup table to remove escaping from special chars
17
+ # @return [Hash]
18
+ DESCAPING = ESCAPING.invert.freeze
19
+
20
+ # Marks a segment which has is an encoding special char
21
+ # @return [Regexp]
22
+ DESCAPING_MARKER = /\\./
23
+
24
+ # Encodes the content a text by escaping all special chars
25
+ # @param text [String]
26
+ # @return [String] The escaped text for chaining
27
+ def self.encode!(text)
28
+ buffer = StringIO.new
29
+ text.each_char do |char|
30
+ buffer << ESCAPING.fetch(char, char)
31
+ end
32
+ text.replace buffer.string
33
+ text
34
+ end
35
+
36
+ # Decodes the content a text by removing all escaping from special chars
37
+ # @param text [String]
38
+ # @return [String] The unescaped text for chaining
39
+ def self.decode!(text)
40
+ text.gsub!(DESCAPING_MARKER) do |match|
41
+ DESCAPING.fetch(match, match)
42
+ end
43
+ text
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,61 @@
1
+ module ProperProperties
2
+ module Encoding
3
+ # Module to encode and decode unicode chars
4
+ # @see ProperProperties::Encoding
5
+ module Unicode
6
+
7
+ # Marker for encoded unicode chars
8
+ # @return [Regexp]
9
+ UNICODE_MARKER = /\\[uU]([0-9a-fA-F]{4,5}|10[0-9a-fA-F]{4})/
10
+
11
+ # Escape char for unicode chars
12
+ # @return [String]
13
+ UNICODE_ESCAPE = "\\u"
14
+
15
+ # Decodes all unicode chars from escape sequences in place
16
+ # @param text [String]
17
+ # @return [String] The encoded text for chaining
18
+ def self.decode!(text)
19
+ text.gsub!(UNICODE_MARKER) do
20
+ unicode($1.hex)
21
+ end
22
+ text
23
+ end
24
+
25
+ # Decodes all unicode chars into escape sequences in place
26
+ # @param text [String]
27
+ # @return [String] The decoded text for chaining
28
+ def self.encode!(text)
29
+ buffer = StringIO.new
30
+ text.each_char do |char|
31
+ if char.ascii_only?
32
+ buffer << char
33
+ else
34
+ buffer << UNICODE_ESCAPE
35
+ buffer << hex(char.codepoints.first)
36
+ end
37
+ end
38
+ text.replace buffer.string
39
+ text
40
+ end
41
+
42
+ private
43
+
44
+ def self.unicode(code)
45
+ [code].pack("U")
46
+ end
47
+
48
+ def self.hex(codepoint)
49
+ hex = codepoint.to_s(16)
50
+ size = hex.size
51
+ # padding the hex value for uneven digest
52
+ if (size % 2) == 1
53
+ "0#{hex}"
54
+ else
55
+ hex
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,56 @@
1
+ require 'proper_properties/encoding/special_chars'
2
+ require 'proper_properties/encoding/separators'
3
+ require 'proper_properties/encoding/unicode'
4
+
5
+ module ProperProperties
6
+ # Module to encode and decode
7
+ #
8
+ # Usage:
9
+ # encoded = Encoding.encode!("Some text to be encoded")
10
+ # decoded = Encoding.decode!("Some text to be decoded")
11
+ #
12
+ # You can disable separate encoding (and decoding) steps,
13
+ # by passing in additional flags:
14
+ #
15
+ # * SKIP_SEPARATORS: Do not code the separators (space,:,=)
16
+ # * SKIP_UNICODE: Do not code unicode chars
17
+ # * SKIP_SPECIAL_CHARS: Do not code newlines, tab stops, ...
18
+ #
19
+ module Encoding
20
+
21
+ # Flag for skipping separators encodings / decoding
22
+ # @return [Symbol]
23
+ SKIP_SEPARATORS=:skip_separators
24
+
25
+ # Flag for skipping separators encodings / decoding
26
+ # @return [Symbol]
27
+ SKIP_UNICODE=:skip_unicode
28
+
29
+ # Flag for skipping separators encodings / decoding
30
+ # @return [Symbol]
31
+ SKIP_SPECIAL_CHARS=:skip_special_chars
32
+
33
+ # Encode a given text in place
34
+ # @param text [String]
35
+ # @param *flags [Array] Optional flags to skip encoding steps
36
+ # @return [String] The encoded text for chaining
37
+ def self.encode!(text, *flags)
38
+ SpecialChars.encode!(text) unless flags.include?(SKIP_SPECIAL_CHARS)
39
+ Separators.encode!(text) unless flags.include?(SKIP_SEPARATORS)
40
+ Unicode.encode!(text) unless flags.include?(SKIP_UNICODE)
41
+ text
42
+ end
43
+
44
+ # Decodes a given text in place
45
+ # @param text [String]
46
+ # @param *flags [Array] Optional flags to skip decoding steps
47
+ # @return [String] The decoded text for chaining
48
+ def self.decode!(text, *flags)
49
+ Unicode.decode!(text) unless flags.include?(SKIP_UNICODE)
50
+ Separators.decode!(text) unless flags.include?(SKIP_SEPARATORS)
51
+ SpecialChars.decode!(text) unless flags.include?(SKIP_SPECIAL_CHARS)
52
+ text
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,56 @@
1
+ module ProperProperties
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 @@
1
+ require 'proper_properties/generating/generator'
@@ -0,0 +1,77 @@
1
+ module ProperProperties
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 ProperProperties
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,2 @@
1
+ require 'proper_properties/parsing/normalizer'
2
+ require 'proper_properties/parsing/parser'
@@ -0,0 +1,6 @@
1
+ module ProperProperties
2
+ # Simple representation of a properties file content
3
+ # by a simple ruby hash object
4
+ class Properties < Hash
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module ProperProperties
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,46 @@
1
+ require 'proper_properties/version'
2
+ require 'proper_properties/properties'
3
+ require 'proper_properties/encoding'
4
+ require 'proper_properties/parsing'
5
+ require 'proper_properties/generating'
6
+
7
+ # A module to read and write Proper properties files
8
+ module ProperProperties
9
+
10
+ # Parses the content of a Proper properties file
11
+ # @see Parsing::Parser
12
+ # @param text [String]
13
+ # @return [Properties]
14
+ def self.parse(text)
15
+ Parsing::Parser.parse(text)
16
+ end
17
+
18
+ # Generates the content of a Proper properties file
19
+ # @see Generating::Generator
20
+ # @param hash [Hash]
21
+ # @param options [Hash] options for the generator
22
+ # @return [String]
23
+ def self.generate(hash, options = {})
24
+ Generating::Generator.generate(hash, options)
25
+ end
26
+
27
+ # Loads and parses a Proper properties file
28
+ # @see Parsing::Parser
29
+ # @param path [String]
30
+ # @param encoding [String]
31
+ # @param allow_invalid_byte_sequence [Boolean]
32
+ # @return [Properties]
33
+ def self.load(path, encoding = 'UTF-8', allow_invalid_byte_sequence = true)
34
+ parse(File.read(path).encode(encoding, 'binary', allow_invalid_byte_sequence ? {invalid: :replace, undef: :replace} : {} ))
35
+ end
36
+
37
+ # Generates a Proper properties file
38
+ # @see Generating::Generator
39
+ # @param hash [Hash]
40
+ # @param path [String]
41
+ # @param options [Hash] options for the generator
42
+ def self.write(hash, path, options = {})
43
+ File.write(path, generate(hash, options))
44
+ end
45
+
46
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'proper_properties/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "proper_properties"
8
+ spec.version = ProperProperties::VERSION
9
+ spec.authors = ["Tnarik Innael"]
10
+ spec.email = ["tnarik@lecafeautomatique.co.uk"]
11
+ spec.summary = %q{Loader and writer for Java properties files}
12
+ spec.description = %q{Library for loading and writing of Java properties files that matches java.util.Properties behaviour}
13
+ spec.homepage = "https://github.com/tnarik/proper_properties"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ # development dependencies
21
+ spec.add_development_dependency 'rake'
22
+ spec.add_development_dependency 'coveralls'
23
+ spec.add_development_dependency 'simplecov'
24
+ spec.add_development_dependency 'minitest'
25
+ end
@@ -0,0 +1,41 @@
1
+ # Comment 1
2
+ ! Comment 2
3
+ item0
4
+ item1 = item1
5
+ item2 : item2
6
+ item3 item3
7
+
8
+ #Comment 3
9
+ ! Comment 4
10
+
11
+ it\ em4=item4
12
+ it\=em5:item5
13
+ item6 item6
14
+
15
+ !Comment 5
16
+ # Comment 6
17
+
18
+ item7 = line 1 \
19
+ line 2 \
20
+ line 3
21
+
22
+ item8 : line 1 \
23
+ #Not a Comment \
24
+ line 2 \
25
+ line 3
26
+
27
+ item9 line 1 \
28
+ line 2 \
29
+ line 3
30
+
31
+ item10=test\n\ttest\u0050 \
32
+ test\n\ttest \
33
+ test\n\ttest = test
34
+
35
+ item11 : line 1 \\\
36
+ line 2 \\\\\
37
+ line 3 \
38
+ line 4
39
+
40
+ item12 : line 1\\
41
+ item13 : line 1
@@ -0,0 +1,14 @@
1
+ item0
2
+ item1=item1
3
+ item2=item2
4
+ item3=item3
5
+ it\ em4=item4
6
+ it\=em5=item5
7
+ item6=item6
8
+ item7=line 1 line 2 line 3
9
+ item8=line 1 #Not a Comment line 2 line 3
10
+ item9=line 1 line 2 line 3
11
+ item10=test\n\ttest\u0050 test\n\ttest test\n\ttest = test
12
+ item11=line 1 \line 2 \\line 3 line 4
13
+ item12=line 1\
14
+ item13=line 1
@@ -0,0 +1,11 @@
1
+ item0=
2
+ item1=item1
3
+ item2=item2
4
+ item3=item3
5
+ it\ em4=item4
6
+ it\=em5=item5
7
+ it\:em6=item6
8
+ item7=line 1 line 2 line 3
9
+ item8=line 1 line 2 line 3
10
+ item9=line 1 line 2 line 3
11
+ item10=test\n\ttest\u05d4 test\n\ttest test\n\ttest = test
@@ -0,0 +1,11 @@
1
+ item0=
2
+ item1=item1
3
+ item2=item2
4
+ item3=item3
5
+ it em4=item4
6
+ it=em5=item5
7
+ it:em6=item6
8
+ item7=line 1 line 2 line 3
9
+ item8=line 1 line 2 line 3
10
+ item9=line 1 line 2 line 3
11
+ item10=test\n\ttest\u05d4 test\n\ttest test\n\ttest = test
@@ -0,0 +1,14 @@
1
+ item0=
2
+ item1=item1
3
+ item2=item2
4
+ item3=item3
5
+ it\ em4=item4
6
+ it\=em5=item5
7
+ it\:em6=item6
8
+ item7=line 1 line 2 line 3
9
+ item8=line 1 line 2 line 3
10
+ item9=line 1 line 2 line 3
11
+ item10=test
12
+ test\u05d4 test
13
+ test test
14
+ test = test
@@ -0,0 +1,11 @@
1
+ item0=
2
+ item1=item1
3
+ item2=item2
4
+ item3=item3
5
+ it\ em4=item4
6
+ it\=em5=item5
7
+ it\:em6=item6
8
+ item7=line 1 line 2 line 3
9
+ item8=line 1 line 2 line 3
10
+ item9=line 1 line 2 line 3
11
+ item10=test\n\ttestה test\n\ttest test\n\ttest = test
data/spec/helper.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ Coveralls::SimpleCov::Formatter,
6
+ SimpleCov::Formatter::HTMLFormatter
7
+ ]
8
+
9
+ SimpleCov.start
10
+
11
+ require 'minitest/autorun'
12
+ require 'proper_properties'
13
+
14
+ class Minitest::Spec
15
+
16
+ def fixture(*segments)
17
+ File.read(File.join(File.dirname(__FILE__), "fixtures", *segments))
18
+ end
19
+
20
+ end
@@ -0,0 +1,19 @@
1
+ require 'helper'
2
+
3
+ describe ProperProperties::Encoding::Separators do
4
+ subject{ ProperProperties::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 ProperProperties::Encoding::SpecialChars do
4
+ subject{ ProperProperties::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 ProperProperties::Encoding::Unicode do
5
+ subject{ ProperProperties::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 ProperProperties::Encoding do
5
+ subject{ ProperProperties::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 ProperProperties::Generating::Generator do
5
+ subject { ProperProperties::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
@@ -0,0 +1,13 @@
1
+ require 'helper'
2
+
3
+ describe ProperProperties::Parsing::Normalizer do
4
+ subject { ProperProperties::Parsing::Normalizer }
5
+
6
+ it "normalizes by applying all rules" do
7
+ content = fixture("test.properties")
8
+ normalized = fixture("test_normalized.properties")
9
+ subject.normalize! content
10
+ content.must_equal normalized
11
+ end
12
+
13
+ end
@@ -0,0 +1,36 @@
1
+ require 'helper'
2
+
3
+ describe ProperProperties::Parsing::Parser do
4
+ subject { ProperProperties::Parsing::Parser }
5
+
6
+ let(:as_hash) do
7
+ {
8
+ :item0 => "",
9
+ :item1 => "item1 ",
10
+ :item2 => "item2 ",
11
+ :item3 => "item3",
12
+ :"it em4" => "item4",
13
+ :"it=em5" => "item5",
14
+ :item6 => "item6",
15
+ :item7 => "line 1 line 2 line 3",
16
+ :item8 => "line 1 #Not a Comment line 2 line 3",
17
+ :item9 => "line 1 line 2 line 3",
18
+ :item10 => "test\n\ttestP test\n\ttest test\n\ttest = test",
19
+ :item11 => "line 1 \\line 2 \\\\line 3 line 4",
20
+ :item12 => "line 1\\",
21
+ :item13 => "line 1"
22
+ }
23
+ end
24
+
25
+ it "parses correctly a properties file content" do
26
+ content = fixture("test.properties")
27
+ properties = subject.parse(content)
28
+
29
+ # don't compare the hashes directly, as this hard to debug
30
+ properties.keys.must_equal as_hash.keys
31
+ properties.each do |key, value|
32
+ value.must_equal as_hash[key]
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ describe ProperProperties do
4
+ it "should have a version" do
5
+ ProperProperties::VERSION.wont_be_nil
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ require 'helper'
2
+ require 'tempfile'
3
+
4
+ describe ProperProperties do
5
+ subject{ ProperProperties }
6
+
7
+ it "parses from string" do
8
+ subject.parse("item1=item1").must_equal({:item1 => "item1"})
9
+ end
10
+
11
+ it "generates from hash" do
12
+ subject.generate({:item1 => "item1"}).must_equal("item1=item1")
13
+ end
14
+
15
+ it "loads from file" do
16
+ with_temp_file do |file|
17
+ file << "item1=item1"
18
+ file.flush
19
+
20
+ subject.load(file.path).must_equal({:item1 => "item1"})
21
+ end
22
+ end
23
+
24
+ it "writes to file" do
25
+ with_temp_file do |file|
26
+ subject.write({:item1 => "item1"}, file.path)
27
+
28
+ file.rewind
29
+ file.read.must_equal "item1=item1"
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def with_temp_file(&block)
36
+ file = Tempfile.new("proper_properties")
37
+ block.call(file)
38
+ ensure
39
+ file.close
40
+ file.unlink
41
+ end
42
+
43
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proper_properties
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tnarik Innael
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
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: coveralls
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
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Library for loading and writing of Java properties files that matches
70
+ java.util.Properties behaviour
71
+ email:
72
+ - tnarik@lecafeautomatique.co.uk
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".travis.yml"
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - lib/proper_properties.rb
84
+ - lib/proper_properties/encoding.rb
85
+ - lib/proper_properties/encoding/separators.rb
86
+ - lib/proper_properties/encoding/special_chars.rb
87
+ - lib/proper_properties/encoding/unicode.rb
88
+ - lib/proper_properties/generating.rb
89
+ - lib/proper_properties/generating/generator.rb
90
+ - lib/proper_properties/parsing.rb
91
+ - lib/proper_properties/parsing/normalizer.rb
92
+ - lib/proper_properties/parsing/parser.rb
93
+ - lib/proper_properties/properties.rb
94
+ - lib/proper_properties/version.rb
95
+ - proper_properties.gemspec
96
+ - spec/fixtures/test.properties
97
+ - spec/fixtures/test_normalized.properties
98
+ - spec/fixtures/test_out.properties
99
+ - spec/fixtures/test_out_skip_separators.properties
100
+ - spec/fixtures/test_out_skip_special_chars.properties
101
+ - spec/fixtures/test_out_skip_unicode.properties
102
+ - spec/helper.rb
103
+ - spec/proper_properties/encoding/separators_spec.rb
104
+ - spec/proper_properties/encoding/special_chars_spec.rb
105
+ - spec/proper_properties/encoding/unicode_spec.rb
106
+ - spec/proper_properties/encoding_spec.rb
107
+ - spec/proper_properties/generating/generator_spec.rb
108
+ - spec/proper_properties/parsing/normalizer_spec.rb
109
+ - spec/proper_properties/parsing/parser_spec.rb
110
+ - spec/proper_properties/version_spec.rb
111
+ - spec/proper_properties_spec.rb
112
+ homepage: https://github.com/tnarik/proper_properties
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 2.2.2
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: Loader and writer for Java properties files
136
+ test_files:
137
+ - spec/fixtures/test.properties
138
+ - spec/fixtures/test_normalized.properties
139
+ - spec/fixtures/test_out.properties
140
+ - spec/fixtures/test_out_skip_separators.properties
141
+ - spec/fixtures/test_out_skip_special_chars.properties
142
+ - spec/fixtures/test_out_skip_unicode.properties
143
+ - spec/helper.rb
144
+ - spec/proper_properties/encoding/separators_spec.rb
145
+ - spec/proper_properties/encoding/special_chars_spec.rb
146
+ - spec/proper_properties/encoding/unicode_spec.rb
147
+ - spec/proper_properties/encoding_spec.rb
148
+ - spec/proper_properties/generating/generator_spec.rb
149
+ - spec/proper_properties/parsing/normalizer_spec.rb
150
+ - spec/proper_properties/parsing/parser_spec.rb
151
+ - spec/proper_properties/version_spec.rb
152
+ - spec/proper_properties_spec.rb