apfel 0.0.2

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