dot-properties 0.1.0

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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dot_properties.gemspec
4
+ gemspec
@@ -0,0 +1,14 @@
1
+ ##########################################################################
2
+ # Copyright 2013 Michael B. Klein
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
@@ -0,0 +1,66 @@
1
+ # DotProperties [![Build Status](https://secure.travis-ci.org/mbklein/dot-properties.png)](http://travis-ci.org/mbklein/dot-properties)
2
+
3
+ Reads and writes [Java .properties files](http://en.wikipedia.org/wiki/.properties) like a champ.
4
+
5
+ * Intuitive, Hash-like access. Anywhere it makes sense to act like a Hash, it acts like a Hash.
6
+ * Won't clobber comments and blank lines (unless you want to).
7
+ * Will preserve original delimiters for each value (unless you normalize them).
8
+ * Supports all the delimiters (whitespace, `=`, `:`).
9
+ * Supports both comment prefixes (`#`, `!`).
10
+ * Supports expansion of inline `${property}` references.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'dot_properties'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install dot_properties
25
+
26
+ ## Usage
27
+
28
+ require 'dot_properties'
29
+
30
+ # Load a .properties file
31
+ props = DotProperties.new('sample.properties')
32
+
33
+ # Get a value
34
+ props['foo']
35
+
36
+ # Set a value
37
+ props['foo'] = 'bar'
38
+
39
+ # Convert key/value pairs to a hash
40
+ props.to_h
41
+
42
+ # Or just let it act like a hash
43
+ props.each_pair { |key,value| puts "#{key} :: #{value}" }
44
+
45
+ # Remove all comments/blanks/both
46
+ props.strip_comments!
47
+ props.strip_blanks!
48
+ props.compact!
49
+
50
+ # Write a .properties file
51
+ File.open('output.properties','w') { |out| out.write(props.to_s) }
52
+
53
+ See the spec tests and fixture data for more examples.
54
+
55
+ ## Known Issues
56
+
57
+ * Multiline values will be converted to single line on output
58
+
59
+ ## History
60
+
61
+ - <b>0.1.0</b> - Initial release
62
+
63
+ ## Copyright
64
+
65
+ Copyright (c) 2013 Michael B. Klein. See LICENSE.txt for further details.
66
+
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rdoc/task'
3
+ require 'dot_properties/version'
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.pattern = FileList['./spec/**/*_spec.rb']
8
+ end
9
+
10
+ task :default => :spec
11
+
12
+ RDoc::Task.new do |rdoc|
13
+ version = DotProperties::VERSION
14
+
15
+ rdoc.rdoc_dir = 'rdoc'
16
+ rdoc.title = "dot-properties #{version}"
17
+ rdoc.rdoc_files.include('README*')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dot_properties/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dot-properties"
8
+ spec.version = DotProperties::VERSION
9
+ spec.authors = ["Michael B. Klein"]
10
+ spec.email = ["mbklein@gmail.com"]
11
+ spec.description = %q{Java-style .properties file manipulation with a light touch}
12
+ spec.summary = %q{Read/write .properties files, respecting comments and existing formatting as much as possible}
13
+ spec.homepage = "https://github.com/mbklein/dot-properties"
14
+ spec.license = "APACHE2"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "rdoc", ">= 2.4.2"
25
+ spec.add_development_dependency "simplecov"
26
+ end
@@ -0,0 +1 @@
1
+ require 'dot_properties'
@@ -0,0 +1,144 @@
1
+ require "dot_properties/version"
2
+
3
+ class DotProperties
4
+ extend Forwardable
5
+
6
+ def_delegators :to_h, :each, :each_key, :each_pair, :each_value, :empty?,
7
+ :fetch, :has_key?, :has_value?, :include?, :inspect, :invert,
8
+ :key, :key?, :keys, :length, :member?, :merge, :reject,
9
+ :select, :size, :value?, :values, :values_at
10
+
11
+
12
+ # @!attribute [rw] auto_expand
13
+ # @return [Boolean] Whether to expand resolvable variables within values on retrieval (default: +true+)
14
+ # @!attribute [rw] default_delimiter
15
+ # @return [String] The delimiter to use when adding new properties or when calling +normalize_delimiters!+ (default: '=')
16
+ attr_accessor :auto_expand, :default_delimiter
17
+
18
+ def initialize(lines=[])
19
+ @content = lines.collect { |item| tokenize(item) }
20
+ @auto_expand = true
21
+ @default_delimiter = '='
22
+ end
23
+
24
+ def self.load(file)
25
+ self.parse(File.read(file))
26
+ end
27
+
28
+ def self.parse(str)
29
+ self.new(str.split(/(?<!\\)\n/))
30
+ end
31
+
32
+ def get(key, expand=@auto_expand)
33
+ item = find_key(key)
34
+ value = (item && item[:value]) || nil
35
+ if value and expand
36
+ value = value.gsub(/\$\{(.+?)\}/) { |v| has_key?($1) ? get($1,true) : v }
37
+ end
38
+ return value
39
+ end
40
+
41
+ def set(key, value)
42
+ item = find_key(key)
43
+ if item
44
+ item[:value] = value
45
+ else
46
+ @content << { type: :value, key: key, delimiter: default_delimiter, value: value }
47
+ end
48
+ return value
49
+ end
50
+
51
+ def [](key)
52
+ get(key)
53
+ end
54
+
55
+ def []=(key,value)
56
+ set(key, value)
57
+ end
58
+
59
+ def <<(item)
60
+ @content << tokenize(item)
61
+ end
62
+
63
+ def delete(key)
64
+ value = get(key)
65
+ @content.reject! { |item| item[:type] == :value and item[:key] == key }
66
+ return value
67
+ end
68
+
69
+ def inspect
70
+ to_h.inspect
71
+ end
72
+
73
+ # Strip all comments and blank lines, leaving only values
74
+ def compact!
75
+ @content.reject! { |item| item[:type] != :value }
76
+ end
77
+
78
+ # Replace all delimiters with +default_delimiter+
79
+ def normalize_delimiters!
80
+ @content.each { |item| item[:delimiter] = default_delimiter if item[:type] == :value }
81
+ end
82
+
83
+ # Strip all blank lines, leaving only comments and values
84
+ def strip_blanks!
85
+ @content.reject! { |item| item[:type] == :blank }
86
+ end
87
+
88
+ # Strip all comments, leaving only blank lines and values
89
+ def strip_comments!
90
+ @content.reject! { |item| item[:type] == :comment }
91
+ end
92
+
93
+ # The assembled .properties file as an array of lines
94
+ def to_a
95
+ @content.collect { |item| assemble(item) }
96
+ end
97
+
98
+ # All properties as a hash
99
+ def to_h
100
+ Hash[@content.select { |item| item[:type] == :value }.collect { |item| item.values_at(:key,:value) }]
101
+ end
102
+
103
+ # The assembled .properties file as a string
104
+ def to_s
105
+ to_a.join("\n")
106
+ end
107
+
108
+ protected
109
+ def assemble(item)
110
+ if item[:type] == :value
111
+ if item[:value].nil? or item[:value].empty?
112
+ escape(item[:key])
113
+ else
114
+ "#{escape(item[:key])}#{item[:delimiter]}#{item[:value]}"
115
+ end
116
+ else
117
+ item[:value]
118
+ end
119
+ end
120
+
121
+ def tokenize(item)
122
+ if item =~ /^\s*[#!]/
123
+ { type: :comment, value: item }
124
+ elsif item =~ /^\s*$/
125
+ { type: :blank, value: item }
126
+ else
127
+ key, delimiter, value = item.split /(\s*(?<!\\)[\s:=]\s*)/, 2
128
+ { type: :value, key: unescape(key), delimiter: delimiter, value: value.to_s.gsub(/\\\n\s*/,'') }
129
+ end
130
+ end
131
+
132
+ def find_key(key)
133
+ @content.find { |item| item[:type] == :value and item[:key] == key }
134
+ end
135
+
136
+ def escape(key)
137
+ key.gsub(/[\s:=]/) { |m| "\\#{m}" }
138
+ end
139
+
140
+ def unescape(key)
141
+ key.gsub(/\\/,'')
142
+ end
143
+
144
+ end
@@ -0,0 +1,3 @@
1
+ class DotProperties
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,127 @@
1
+ if defined?(RUBY_ENGINE) and (RUBY_ENGINE == 'ruby') and (RUBY_VERSION >= '1.9')
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+ $:.push(File.join(File.dirname(__FILE__),'..','lib'))
6
+
7
+ require 'dot_properties'
8
+
9
+ describe DotProperties do
10
+ let(:propfile) { File.expand_path('../fixtures/sample.properties', __FILE__) }
11
+ subject { DotProperties.load(propfile) }
12
+
13
+ let(:properties) { 16 }
14
+ let(:comments) { 14 }
15
+ let(:blanks) { 8 }
16
+
17
+ it { should be_an_instance_of(DotProperties) }
18
+
19
+ describe "values" do
20
+ it "should have the right number of properties" do
21
+ expect(subject.keys.length).to eq(16)
22
+ end
23
+
24
+ it "should have the right values" do
25
+ expect(subject['foo.normal']).to eq('bar.normal.value')
26
+ expect(subject['bar.normal']).to eq('baz.normal.value')
27
+ expect(subject['foo.whitespace']).to eq('bar.whitespace.value')
28
+ expect(subject['foo.extraspace']).to eq('bar.extraspace.value')
29
+ expect(subject['bar.extraspace']).to eq('baz.extraspace.value')
30
+ expect(subject['foo bar']).to eq('bar baz')
31
+ expect(subject['bar:baz']).to eq('baz quux')
32
+ expect(subject['foo bar:baz= quux']).to eq('this is getting ridiculous')
33
+ expect(subject['lots of fruit']).to eq('apple, peach, kiwi, mango, banana, strawberry, raspberry')
34
+ expect(subject['some.veggies']).to eq('carrot, potato, broccoli')
35
+ expect(subject['foo.empty']).to be_empty
36
+ expect(subject['bar.empty']).to be_empty
37
+ expect(subject['baz.empty']).to be_empty
38
+ expect(subject['quux.empty']).to be_empty
39
+ end
40
+
41
+ it "#auto_expand" do
42
+ expect(subject['present']).to eq('This value contains two resolvable references, bar.normal.value and baz.extraspace.value')
43
+ expect(subject['missing']).to eq('This value contains one resolvable reference, bar.normal.value, and one unresolvable reference, ${quux.missing}')
44
+ end
45
+
46
+ it "#auto_expand=false" do
47
+ subject.auto_expand = false
48
+ expect(subject['present']).to eq('This value contains two resolvable references, ${foo.normal} and ${bar.extraspace}')
49
+ expect(subject['missing']).to eq('This value contains one resolvable reference, ${foo.normal}, and one unresolvable reference, ${quux.missing}')
50
+ end
51
+ end
52
+
53
+ describe "delimiters" do
54
+ it "should retain original delimiters and spacing" do
55
+ expect(subject.to_a.find { |l| l =~ /^foo\.extraspace/ }).to eq('foo.extraspace = bar.extraspace.value')
56
+ end
57
+
58
+ it "should retain delimiters even after #set" do
59
+ subject['bar.extraspace'] = 'new value'
60
+ expect(subject.to_a.find { |l| l =~ /^bar\.extraspace/ }).to eq('bar.extraspace : new value')
61
+ end
62
+
63
+ it "should #normalize delimiters!" do
64
+ subject.normalize_delimiters!
65
+ expect(subject.to_a.find { |l| l =~ /^foo\.extraspace/ }).to eq('foo.extraspace=bar.extraspace.value')
66
+ end
67
+
68
+ it "#default_delimiter" do
69
+ subject.default_delimiter = ' : '
70
+ subject['new key'] = 'new value'
71
+ expect(subject.to_a.last).to eq('new\\ key : new value')
72
+ end
73
+ end
74
+
75
+ describe "comments and blanks" do
76
+ it "should leave comments and blanks alone" do
77
+ expect(subject.to_a.length).to eq(properties + comments + blanks)
78
+ end
79
+
80
+ it "should strip comments" do
81
+ subject.strip_comments!
82
+ expect(subject.to_a.length).to eq(properties + blanks)
83
+ end
84
+
85
+ it "should strip blanks" do
86
+ subject.strip_blanks!
87
+ expect(subject.to_a.length).to eq(properties + comments)
88
+ end
89
+
90
+ it "should strip comments and blanks" do
91
+ subject.compact!
92
+ expect(subject.to_a.length).to eq(properties)
93
+ end
94
+ end
95
+
96
+ describe "serialization" do
97
+ it "#inspect" do
98
+ expect(subject.inspect).to match(/\{.+\}/)
99
+ end
100
+
101
+ it "should round-trip" do
102
+ hash = subject.to_h.dup
103
+ array = subject.to_a
104
+ duplicate = DotProperties.parse(subject.to_s)
105
+ expect(duplicate.to_h).to eq(hash)
106
+ expect(duplicate.to_a).to eq(array)
107
+ end
108
+ end
109
+
110
+ describe "additional methods" do
111
+ it "#<<" do
112
+ subject << ""
113
+ subject << "# This is a comment on some.new.property"
114
+ subject << "some.new.property = some.new.value"
115
+ expect(subject['some.new.property']).to eq('some.new.value')
116
+ expect(subject.to_h.length).to eq(properties + 1)
117
+ expect(subject.to_a.length).to eq(properties + comments + blanks + 3)
118
+ end
119
+
120
+ it "#delete" do
121
+ expect(subject.delete('foo bar')).to eq('bar baz')
122
+ expect(subject).not_to have_key('foo bar')
123
+ end
124
+ end
125
+
126
+
127
+ end
@@ -0,0 +1,42 @@
1
+ # Sample .properties file for dot-properties specs
2
+ # It has 16 properties, 14 comments, and 8 blank lines
3
+ # It starts with some comments
4
+
5
+ # And then has a blank line
6
+ ! followed by some other comments
7
+ # using various comment markers and
8
+ ! spacing
9
+
10
+ # Normal properties
11
+ foo.normal=bar.normal.value
12
+ bar.normal:baz.normal.value
13
+
14
+ # Whitespace-delimited properties
15
+ foo.whitespace bar.whitespace.value
16
+
17
+ # Whitespace surrounding delimiters
18
+ foo.extraspace = bar.extraspace.value
19
+ bar.extraspace : baz.extraspace.value
20
+
21
+ # Escaped characters in keys
22
+ foo\ bar=bar baz
23
+ bar\:baz : baz quux
24
+ foo\ bar\:baz\=\ quux this is getting ridiculous
25
+
26
+ # Multiline values
27
+ lots\ of\ fruit apple, peach, kiwi, \
28
+ mango, banana, strawberry, \
29
+ raspberry
30
+ some.veggies=carrot, \
31
+ potato, \
32
+ broccoli
33
+
34
+ # Empty values
35
+ foo.empty
36
+ bar.empty=
37
+ baz.empty:
38
+ quux.empty =
39
+
40
+ # References
41
+ present = This value contains two resolvable references, ${foo.normal} and ${bar.extraspace}
42
+ missing = This value contains one resolvable reference, ${foo.normal}, and one unresolvable reference, ${quux.missing}
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dot-properties
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael B. Klein
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rdoc
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 2.4.2
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 2.4.2
78
+ - !ruby/object:Gem::Dependency
79
+ name: simplecov
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Java-style .properties file manipulation with a light touch
95
+ email:
96
+ - mbklein@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - .travis.yml
103
+ - Gemfile
104
+ - LICENSE.txt
105
+ - README.md
106
+ - Rakefile
107
+ - dot-properties.gemspec
108
+ - lib/dot-properties.rb
109
+ - lib/dot_properties.rb
110
+ - lib/dot_properties/version.rb
111
+ - spec/dot_properties_spec.rb
112
+ - spec/fixtures/sample.properties
113
+ homepage: https://github.com/mbklein/dot-properties
114
+ licenses:
115
+ - APACHE2
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ segments:
127
+ - 0
128
+ hash: -1306251061274732022
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ segments:
136
+ - 0
137
+ hash: -1306251061274732022
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 1.8.23
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Read/write .properties files, respecting comments and existing formatting
144
+ as much as possible
145
+ test_files:
146
+ - spec/dot_properties_spec.rb
147
+ - spec/fixtures/sample.properties