dot-properties 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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