apfel 0.0.2

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.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ apfel (0.0.2)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.0.8)
10
+ diff-lcs (1.1.3)
11
+ method_source (0.8.1)
12
+ pry (0.9.10)
13
+ coderay (~> 1.0.5)
14
+ method_source (~> 0.8)
15
+ slop (~> 3.3.1)
16
+ rake (0.9.2.2)
17
+ rspec (2.12.0)
18
+ rspec-core (~> 2.12.0)
19
+ rspec-expectations (~> 2.12.0)
20
+ rspec-mocks (~> 2.12.0)
21
+ rspec-core (2.12.0)
22
+ rspec-expectations (2.12.0)
23
+ diff-lcs (~> 1.1.3)
24
+ rspec-mocks (2.12.0)
25
+ slop (3.3.3)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ apfel!
32
+ pry
33
+ rake
34
+ rspec
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Apfel
2
+
3
+ ## Introduction
4
+
5
+ Apfel is simple parser for .strings (DotStrings) files written in Ruby. DotStrings files are used by Apple platforms for localization. Apfel reads DotStrings files, parses them for key-value pairs and comments.
6
+
7
+ Once in the form of a hash, the content of the DotStrings file can easily be
8
+ rebuilt as JSON, XML and RESX (with the help of Builder https://github.com/jimweirich/builder) and more!
9
+
10
+ ## Use
11
+
12
+ To start using Apfel first require the gem
13
+ ```Ruby
14
+ require 'apfel'
15
+ ```
16
+
17
+ Next, pass Apfel the .strings file you want to parse:
18
+ ```Ruby
19
+ parsed_file = Apfel.parse('path/to/file')
20
+ ```
21
+
22
+ Once the file has been parsed, you can do many things with it:
23
+ ```Ruby
24
+ # Turn it into a ruby hash (includes comments)
25
+ parsed_file.to_hash
26
+
27
+ # Turn it into json (includes comments)
28
+ parsed_file.to_json
29
+
30
+ # With either #to_hash or #to_json you can specify
31
+ # whether you want the comments included
32
+ parsed_file.to_hash(with_comments: false)
33
+
34
+ # Get all the keys as an array
35
+ parsed_file.keys
36
+
37
+ # Get all the values as an array
38
+ parsed_file.values
39
+
40
+ # Return an array of key-value hashes
41
+ parsed_file.key_values
42
+
43
+ # Return an array of key-comment hashes
44
+ parsed_file.comments
45
+
46
+ # Return an array of all the comments without their keys
47
+ parsed_file.comments(with_keys: false)
48
+ ```
49
+ ## Contributing
50
+
51
+ 1. Fork it
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Commit tests (they should pass when rake is run)
55
+ 5. Push to the branch (`git push origin my-new-feature`)
56
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ require 'rspec/core/rake_task'
10
+ RSpec::Core::RakeTask.new do |t|
11
+ t.rspec_opts = ["--color", '--order rand']
12
+ end
13
+
14
+ desc "Run all tests and documentation checks"
15
+ task :qa => [:spec]
16
+
17
+ task :default => :qa
data/apfel.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ apfel_folder = File.join(File.dirname(__FILE__), 'lib', 'apfel')
3
+ $:.unshift apfel_folder
4
+ require "version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "apfel"
8
+ s.version = Apfel::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Ryan Levick"]
11
+ s.email = ["ryan.levick@gmail.com"]
12
+ s.homepage = "https://github.com/rlevick/apfel"
13
+ s.summary = %q{Simple parser for DotStrings Files}
14
+ s.description = %q{Parse valid .strings files for easy conversion to other formats}
15
+
16
+ s.add_development_dependency "rspec"
17
+ s.add_development_dependency "rake"
18
+ s.add_development_dependency "pry"
19
+
20
+ s.rubyforge_project = "apfel"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,54 @@
1
+ module Apfel
2
+ require 'line'
3
+ require 'kv_pair'
4
+ class DotStringsParser
5
+ KEY = "KEY"
6
+ COMMENT = "COMMENT"
7
+
8
+ def initialize(read_file, parsed_file = ParsedDotStrings.new)
9
+ @read_file = read_file
10
+ @parsed_file = parsed_file
11
+ end
12
+
13
+ def parse_file
14
+ state = KEY
15
+ current_comment = nil
16
+ comments_for_keys = {}
17
+ @read_file.each do |content_line|
18
+ current_line = Line.new(content_line)
19
+ next if current_line.empty_line? && current_line.in_comment == false
20
+
21
+ #State machine to parse the comments
22
+ case state
23
+ when KEY
24
+ if current_line.whole_comment?
25
+ unless current_line.whole_comment.strip == 'No comment provided by engineer.'
26
+ current_comment = current_line.whole_comment
27
+ end
28
+ elsif current_line.key_value_pair? && current_comment
29
+ comments_for_keys[current_line.key] = current_comment
30
+ current_comment = nil
31
+ elsif current_line.open_comment?
32
+ current_comment = current_line.open_comment + "\n"
33
+ state = COMMENT
34
+ end
35
+ when COMMENT
36
+ if current_line.close_comment?
37
+ current_comment += current_line.close_comment
38
+ state = KEY
39
+ else
40
+ current_line.in_comment = true
41
+ current_comment = current_comment + current_line.content + "\n"
42
+ end
43
+ end
44
+
45
+ unless current_line.is_comment?
46
+ @parsed_file.kv_pairs << KVPair.new(current_line, comments_for_keys[current_line.key])
47
+ end
48
+ end
49
+
50
+ raise "Invalid .string file: Unterminated comment" unless state == KEY
51
+ @parsed_file
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ module Apfel
2
+ class KVPair
3
+ attr_reader :line, :raw_comment
4
+
5
+ def initialize(line, comment)
6
+ @line = line
7
+ @raw_comment = comment
8
+ end
9
+
10
+ def key
11
+ line.key.strip unless line.key.nil?
12
+ end
13
+
14
+ def value
15
+ line.value.strip unless line.key.nil?
16
+ end
17
+
18
+ def comment
19
+ if raw_comment.nil?
20
+ @raw_comment = ""
21
+ else
22
+ raw_comment.gsub!(/(\/\*)|(\*\/)/,"")
23
+ raw_comment.gsub!("\n", " ")
24
+ raw_comment.gsub!(/\s+/, " ")
25
+ raw_comment.strip
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/apfel/line.rb ADDED
@@ -0,0 +1,72 @@
1
+ module Apfel
2
+ class Line
3
+ attr_reader :content
4
+ attr_accessor :in_comment
5
+
6
+ def initialize(line)
7
+ @content = line
8
+ @in_comment = false
9
+ raise "Line does not end in ;" unless valid?
10
+ end
11
+
12
+ def empty_line?
13
+ /^\s*$/.match(content) || false
14
+ end
15
+
16
+ def whole_comment
17
+ /((^\/\*(.+)\*\/)|(^\/\/(.+)))/.match(content).to_s
18
+ end
19
+
20
+ def whole_comment?
21
+ !(whole_comment.empty?)
22
+ end
23
+
24
+ def open_comment
25
+ /^\/\*(.+)$/.match(content).to_s
26
+ end
27
+
28
+ def open_comment?
29
+ !(open_comment.empty?)
30
+ end
31
+
32
+ def close_comment
33
+ /^(.+)\*\/\s*$/.match(content).to_s
34
+ end
35
+
36
+ def close_comment?
37
+ !(close_comment.empty?)
38
+ end
39
+
40
+ def key_value_pair?
41
+ !!(/^\s*"([^"]+)"\s*=/.match(content))
42
+ end
43
+
44
+ def cleaned_content
45
+ content.gsub(/;\s*$/, "")
46
+ end
47
+
48
+ def valid?
49
+ if key_value_pair?
50
+ !!(/;[\s]*$/.match(content))
51
+ else
52
+ true
53
+ end
54
+ end
55
+
56
+ def key
57
+ if key_value_pair?
58
+ cleaned_content.partition(/"\s*=\s*"/)[0].gsub!(/(^"|"$)/, "")
59
+ end
60
+ end
61
+
62
+ def value
63
+ if key_value_pair?
64
+ cleaned_content.partition(/"\s*=\s*"/)[2].gsub!(/(^"|"$)/, "")
65
+ end
66
+ end
67
+
68
+ def is_comment?
69
+ whole_comment? || open_comment? || close_comment? || in_comment
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,65 @@
1
+ module Apfel
2
+ require 'json'
3
+ class ParsedDotStrings
4
+ attr_accessor :kv_pairs
5
+
6
+ def initialize
7
+ @kv_pairs = []
8
+ end
9
+
10
+ def key_values
11
+ kv_pairs.map do |pair|
12
+ {pair.key => pair.value}
13
+ end
14
+ end
15
+
16
+ def keys
17
+ kv_pairs.map do |pair|
18
+ pair.key
19
+ end
20
+ end
21
+
22
+ def values
23
+ kv_pairs.map do |pair|
24
+ pair.value
25
+ end
26
+ end
27
+
28
+ def comments(args={})
29
+ with_keys = args[:with_keys].nil? ? true : args[:with_keys]
30
+ cleaned_pairs = kv_pairs.map do |pair|
31
+ pair
32
+ end
33
+ with_keys ? build_comment_hash(cleaned_pairs) : cleaned_pairs.map(&:comment)
34
+ end
35
+
36
+ def to_hash(args={})
37
+ with_comments = args[:with_comments].nil? ? true : args[:with_comments]
38
+
39
+ build_hash { |hash, pair|
40
+ hash_value = with_comments ? { pair.value => pair.comment } : pair.value
41
+ hash[pair.key] = hash_value
42
+ }
43
+ end
44
+
45
+ def to_json(args={})
46
+ self.to_hash(with_comments: args[:with_comments]).to_json
47
+ end
48
+
49
+ private
50
+
51
+ def build_comment_hash(kv_pairs)
52
+ build_hash { |hash, pair|
53
+ hash[pair.key] = pair.comment
54
+ }
55
+ end
56
+
57
+ def build_hash(&block)
58
+ hash = {}
59
+ kv_pairs.each do |pair|
60
+ yield hash, pair
61
+ end
62
+ hash
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ module Apfel
2
+ # Class for reading in files and returning an array of its content
3
+ class Reader
4
+ # Reads in a file and returns an array consisting of each line of input
5
+ # cleaned of new line characters
6
+ def self.read(file)
7
+ File.open(file, "r") do |f|
8
+ content_array=[]
9
+ content = f.read
10
+ content.each_line do |line|
11
+ line.gsub!("\n","")
12
+ content_array.push(line)
13
+ end
14
+ content_array
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Apfel
2
+ VERSION = "0.0.2"
3
+ end
data/lib/apfel.rb ADDED
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ module Apfel
4
+ require 'apfel/reader'
5
+ require 'apfel/dot_strings_parser'
6
+ # Public module for parsing DotStrings files and returning a parsed dot
7
+ # strings object
8
+ def self.parse(file)
9
+ file = read(file)
10
+ DotStringsParser.new(file).parse_file
11
+ end
12
+
13
+ def self.read(file)
14
+ Reader.read(file)
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'apfel'
3
+ require 'apfel/parsed_dot_strings'
4
+
5
+ describe Apfel do
6
+ describe '::parse_file' do
7
+ context 'when given a valid DotStrings file'do
8
+
9
+ let(:parsed_file) do
10
+ Apfel.parse(valid_file)
11
+ end
12
+
13
+ it 'returns a ParsedDotStrings object' do
14
+ parsed_file.should be_a(Apfel::ParsedDotStrings)
15
+ end
16
+ end
17
+
18
+ context 'when given an invalid strings file' do
19
+ context 'missing a semicolon' do
20
+
21
+ let(:invalid_file_semicolon) do
22
+ create_temp_file( <<-EOS
23
+ /* This is the first comment */
24
+ "key_number_one" = "value number one"
25
+ EOS
26
+ )
27
+ end
28
+
29
+ it 'returns an error' do
30
+ expect {
31
+ Apfel.parse(invalid_file_semicolon)
32
+ }.to raise_error
33
+ end
34
+ end
35
+
36
+ context 'not closed comment' do
37
+ let(:invalid_file_comment) do
38
+ create_temp_file(<<-EOS
39
+ /* This is the first comment
40
+ "key_number_one" = "value number one";
41
+
42
+ /* This is
43
+ a
44
+ multiline comment */
45
+ end
46
+ EOS
47
+ )
48
+ end
49
+
50
+ it 'raises an error' do
51
+ expect {
52
+ Apfel.parse(invalid_file_semicolon)
53
+ }.to raise_error
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'apfel'
3
+ require 'parsed_dot_strings'
4
+
5
+ module Apfel
6
+ describe ParsedDotStrings do
7
+ let(:parsed_file) do
8
+ Apfel.parse(valid_file)
9
+ end
10
+
11
+ describe '#keys' do
12
+ it 'returns an array of all the keys'do
13
+ parsed_file.keys.should eq(
14
+ ["key_number_one",
15
+ "key_number_two",
16
+ "key_number_three"]
17
+ )
18
+ end
19
+ end
20
+
21
+ describe '#values' do
22
+ it 'returns an array of all the values'do
23
+ parsed_file.values.should eq(
24
+ ["value number one",
25
+ "value number two",
26
+ "value number three"]
27
+ )
28
+ end
29
+ end
30
+
31
+ describe '#comments' do
32
+ context 'when :with_keys is passed as true' do
33
+ it 'returns a hash of the comments mapped to their correct keys' do
34
+ parsed_file.comments(with_keys: true).should eq(
35
+ {"key_number_one" => "This is the first comment",
36
+ "key_number_two" => "This is a multiline comment",
37
+ "key_number_three" => "This is comment number 3"}
38
+ )
39
+ end
40
+ end
41
+
42
+ context 'when with_keys is passed as false' do
43
+ it 'returns an array of just the comments' do
44
+ parsed_file.comments(with_keys: false).should eq(
45
+ ["This is the first comment",
46
+ "This is a multiline comment",
47
+ "This is comment number 3"]
48
+ )
49
+ end
50
+ end
51
+
52
+ it 'defaults to returning a hash' do
53
+ parsed_file.comments.should be_a(Hash)
54
+ end
55
+ end
56
+
57
+ describe '#key_values' do
58
+ it 'returns an array of hashes of key and values' do
59
+ parsed_file.key_values.should eq(
60
+ [
61
+ {"key_number_one" => "value number one"},
62
+ {"key_number_two" => "value number two"},
63
+ {"key_number_three" => "value number three"}
64
+ ]
65
+ )
66
+ end
67
+ end
68
+
69
+ describe '#to_hash' do
70
+ it 'returns a hash with the key = key, value = { value => comments }' do
71
+ parsed_file.to_hash.should eq(
72
+ {
73
+ "key_number_one" => { "value number one" => "This is the first comment" },
74
+ "key_number_two" => { "value number two" => "This is a multiline comment" },
75
+ "key_number_three" => { "value number three" => "This is comment number 3" }
76
+ }
77
+ )
78
+ end
79
+
80
+ context 'when :no_comments is passed as true' do
81
+ it 'returns a hash of just keys and values' do
82
+ parsed_file.to_hash(with_comments: false).should eq(
83
+ {
84
+ "key_number_one" => "value number one",
85
+ "key_number_two" => "value number two",
86
+ "key_number_three" => "value number three"
87
+ }
88
+ )
89
+ end
90
+ end
91
+ end
92
+
93
+ describe 'to_json' do
94
+ it 'returns a valid json representation of .strings file' do
95
+ parsed_file.to_json.should eq(
96
+ {
97
+ "key_number_one" => { "value number one" => "This is the first comment" },
98
+ "key_number_two" => { "value number two" => "This is a multiline comment" },
99
+ "key_number_three" => { "value number three" => "This is comment number 3" }
100
+ }.to_json
101
+ )
102
+ end
103
+ context 'when :no_comments is passed as true' do
104
+ it 'returns a valid json representation of just keys and values' do
105
+ parsed_file.to_json(with_comments: false).should eq(
106
+ {
107
+ "key_number_one" => "value number one",
108
+ "key_number_two" => "value number two",
109
+ "key_number_three" => "value number three"
110
+ }.to_json
111
+ )
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'reader'
3
+
4
+ module Apfel
5
+ describe Reader do
6
+ describe '#read' do
7
+ let(:temp_file) do
8
+ create_temp_file(<<-EOS
9
+ This is a file with some lines.
10
+ Roses are red, violets are blue.
11
+ This text is really boring,
12
+ and so are you!
13
+ EOS
14
+ )
15
+ end
16
+
17
+ it 'reads a file a returns an array of its output' do
18
+ Reader.read(temp_file).should eq([
19
+ "This is a file with some lines.",
20
+ "Roses are red, violets are blue.",
21
+ "This text is really boring,",
22
+ "and so are you!"
23
+ ])
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'tempfile'
2
+ require 'json'
3
+ def create_temp_file(string)
4
+ temp_file = Tempfile.new('temp')
5
+ temp_file << string
6
+ temp_file.flush
7
+ end
8
+
9
+ def valid_file
10
+ create_temp_file(<<-EOS
11
+ /* This is the first comment */
12
+ "key_number_one" = "value number one";
13
+
14
+ /* This is
15
+ a
16
+
17
+ multiline comment */
18
+ "key_number_two" = "value number two";
19
+ /* This is comment number 3 */
20
+ "key_number_three" = " value number three ";
21
+ EOS
22
+ )
23
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apfel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Levick
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
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: '0'
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: pry
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
+ description: Parse valid .strings files for easy conversion to other formats
63
+ email:
64
+ - ryan.levick@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - Gemfile
70
+ - Gemfile.lock
71
+ - README.md
72
+ - Rakefile
73
+ - apfel.gemspec
74
+ - lib/apfel.rb
75
+ - lib/apfel/dot_strings_parser.rb
76
+ - lib/apfel/kv_pair.rb
77
+ - lib/apfel/line.rb
78
+ - lib/apfel/parsed_dot_strings.rb
79
+ - lib/apfel/reader.rb
80
+ - lib/apfel/version.rb
81
+ - spec/apfel_spec.rb
82
+ - spec/parsed_dot_strings_spec.rb
83
+ - spec/reader_spec.rb
84
+ - spec/spec_helper.rb
85
+ homepage: https://github.com/rlevick/apfel
86
+ licenses: []
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ segments:
98
+ - 0
99
+ hash: -2408211664317922349
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ segments:
107
+ - 0
108
+ hash: -2408211664317922349
109
+ requirements: []
110
+ rubyforge_project: apfel
111
+ rubygems_version: 1.8.24
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Simple parser for DotStrings Files
115
+ test_files:
116
+ - spec/apfel_spec.rb
117
+ - spec/parsed_dot_strings_spec.rb
118
+ - spec/reader_spec.rb
119
+ - spec/spec_helper.rb