eclipse-plugin 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ h1. Eclipse::Plugin
2
+
3
+ A Ruby Gem to extract information about views, properties and perspectives from an eclipse plugin or a workspace
4
+
5
+ Use it at your own risk, as it is just a small utility gem to help for some release engineering for the Elexis-RCP. See http://elexis.info.
6
+
7
+ But I have an open ear for suggestions for improvements, especially if they come with a patch including specs.
8
+
9
+ License: GPLv3 or later
10
+
11
+ Copyright: 2014 (c) by Niklaus Giger <niklaus.giger@member.fsf.org>
12
+
13
+ h2. Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ bc. $ gem 'eclipse-plugin'
18
+
19
+ And then execute:
20
+
21
+ bc. $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ bc. $ gem install eclipse-plugin
26
+
27
+ h2. Usage
28
+
29
+ bc. require 'eclipse/plugin'
30
+ workspace = Eclipse::Workspace.new('/path/to/eclipse/app')
31
+ workspace.parsePluginDir
32
+ workspace.views.first {|view| puts view.id }
33
+
34
+ Or you can inspire you from the spec/*_spec.rb files to see tested examples.
35
+
36
+ h2. Contributing
37
+
38
+ # Fork it ( http://github.com/<my-github-username>/eclipse-plugin/fork )
39
+ # Create your feature branch (`git checkout -b my-new-feature`)
40
+ # Commit your changes (`git commit -am 'Add some feature'`)
41
+ # Push to the branch (`git push origin my-new-feature`)
42
+ # Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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 'eclipse/plugin/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "eclipse-plugin"
8
+ spec.version = Eclipse::Plugin::VERSION
9
+ spec.authors = ["Niklaus Giger"]
10
+ spec.email = ["niklaus.giger@member.fsf.org"]
11
+ spec.summary = "Extract information about views, properties and perspectives from an eclipse plugin"
12
+ spec.description = "Extracts localized info out of an Eclipse plugin jar"
13
+ spec.homepage = ""
14
+ spec.license = "GPLv3"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
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
+ spec.add_dependency 'rubyzip', '>= 1.0.0'
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "simplecov"
25
+ spec.add_development_dependency "pry-debugger"
26
+ end
@@ -0,0 +1,21 @@
1
+ #encoding : utf-8
2
+ require 'iconv' unless String.method_defined?(:encode)
3
+
4
+ module Eclipse
5
+
6
+ module Helpers
7
+
8
+ module_function
9
+
10
+ # properties files are saved (at least in the cases, that interest me)
11
+ # as ISO-8859-15 files
12
+ def unescape(inhalt)
13
+ if String.method_defined?(:encode)
14
+ inhalt.encode!('UTF-8', 'ISO-8859-15', :invalid => :replace)
15
+ else
16
+ ic = Iconv.new('UTF-8//IGNORE', 'ISO-8859-15')
17
+ inhalt = ic.iconv(inhalt)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,201 @@
1
+ require "eclipse/plugin/version"
2
+
3
+ require 'zip'
4
+ require "rexml/document"
5
+ include REXML # so that we don't have to prefix everything with REXML::...
6
+
7
+ module Eclipse
8
+ class Workspace
9
+ attr_reader :workspace_dir, :views, :view_categories, :preferencePages, :perspectives, :preferencePage_categories
10
+ def initialize(workspace_dir)
11
+ @workspace_dir = workspace_dir
12
+ @views = Hash.new
13
+ @view_categories = Hash.new
14
+ @preferencePages = Hash.new
15
+ @perspectives = Hash.new
16
+ @preferencePage_categories = Hash.new
17
+ end
18
+
19
+ def parsePluginDir(plugins_dir = File.join(@workspace_dir, "plugins"))
20
+ name = "#{plugins_dir}/*.jar"
21
+ Dir.glob(name).each{
22
+ |jarname|
23
+ info = Plugin::Info.new(jarname)
24
+ fields = [:views, :view_categories, :preferencePages, :perspectives, :preferencePage_categories]
25
+ info.views.each{ |k, v| @views[k] = v }
26
+ info.view_categories.each{ |k, v| @views[k] = v }
27
+ info.perspectives.each{ |k, v| @views[k] = v }
28
+ info.preferencePages.each{ |k, v| @views[k] = v }
29
+ info.preferencePage_categories.each{ |k, v| @views[k] = v }
30
+ }
31
+ end
32
+ end
33
+
34
+ module Plugin
35
+ class Info
36
+ # Some helper classes for the extension points we are interested in
37
+ UI_PreferencePage = Struct.new('UI_PreferencePage', :id, :category, :translation)
38
+ UI_View = Struct.new('UI_View', :id, :category, :translation)
39
+ UI_Perspective = Struct.new('UI_Perspective', :id, :category, :translation)
40
+ Category = Struct.new('Category', :id, :name, :translation)
41
+ attr_reader :iso, :views, :view_categories, :preferencePages, :perspectives, :workspace, :preferencePage_categories
42
+
43
+ def initialize(jarname, iso='de')
44
+ @workspace = File.dirname(jarname).sub(/\/plugins$/, '')
45
+ @iso = iso
46
+ @jarname = jarname
47
+ @jarfile = Zip::File.open(jarname)
48
+ @views = Hash.new
49
+ @view_categories = Hash.new
50
+ @preferencePages = Hash.new
51
+ @perspectives = Hash.new
52
+ @preferencePage_categories = Hash.new
53
+ # we use hashes to be able to find the categories fast
54
+ readPluginXML(File.basename(jarname))
55
+ rescue => e # HACK: we need this to handle org.apache.commons.lang under Windows-7
56
+ puts "Skipped plugin #{File.expand_path(jarname)}"
57
+ puts "error was #{e.inspect}"
58
+ puts caller
59
+ end
60
+
61
+ def addCategory(hash, id, name = nil)
62
+ return if hash[id] and hash[id].translation
63
+ hash[id] = Category.new(id, name) unless hash[id]
64
+ translation = getTranslationForPlugin(name, @iso) if name
65
+ hash[id].translation = translation if name and translation
66
+ puts "#{File.basename(@jarname)}: Added category #{id} name #{name} tr '#{translation}'" if $VERBOSE
67
+ end
68
+
69
+ def getTranslatedPreferencePages
70
+ all = []
71
+ @preferencePages.each{
72
+ |id, content|
73
+ unless content.category
74
+ next if @preferencePages.find { |sub_id, x| x.category.eql?(content.id) }
75
+ end
76
+ category = content.category
77
+ cat_trans = content.translation
78
+ text = nil
79
+ if @preferencePage_categories[category]
80
+ text = "#{@preferencePage_categories[category].translation}/#{content.translation}"
81
+ puts "preferencePages #{id} category #{category.inspect} text #{cat_trans}" if $VERBOSE
82
+ else
83
+ text = content.translation
84
+ puts "preferencePages #{id} text #{text}" if $VERBOSE
85
+ end
86
+ all << text
87
+ }
88
+ all.sort.reverse.uniq if all and all.size > 0
89
+ end
90
+
91
+ def getTranslatedViews
92
+ all = []
93
+ @views.each{
94
+ |id, content|
95
+ category = content.category
96
+ cat_trans = content.translation
97
+ text = nil
98
+ if category
99
+ text = "#{@view_categories[category].translation}/#{content.translation}"
100
+ else
101
+ text = "Other/#{content.translation}"
102
+ end
103
+ all << text if text
104
+ }
105
+ all.sort.reverse.uniq if all and all.size > 0
106
+ end
107
+ def getTranslatedPerspectives
108
+ all = []
109
+ @perspectives.each{
110
+ |id, content|
111
+ category = content.category
112
+ cat_trans = content.translation
113
+ text = nil
114
+ if category
115
+ text = "#{@perspectives[category].translation}/#{content.translation}"
116
+ puts "perspectives #{id} category #{category.inspect} text #{cat_trans}" if $VERBOSE
117
+ else
118
+ text = content.translation
119
+ puts "perspectives #{id} categories #{category} text #{text}" if $VERBOSE
120
+ end
121
+ all << text
122
+ }
123
+ all.sort.reverse.uniq if all and all.size > 0
124
+ end
125
+
126
+ def getTranslationForPlugin(look_for, iso)
127
+ properties = "plugin_#{iso}.properties"
128
+ properties = "plugin.properties" unless @jarfile.find_entry(properties)
129
+ puts "Looking for translation of #{look_for} in #{properties}" if $VERBOSE
130
+ line_nr = 0
131
+ @jarfile.read(properties).split("\n").each {
132
+ |line|
133
+ line_nr += 1
134
+ id,value = line.split(' = ')
135
+ if id and id.index(look_for) and value
136
+ return Helpers::unescape(value.sub("\r","").sub("\n",""))
137
+ else id,value = line.split('=')
138
+ return Helpers::unescape(value.sub("\r","").sub("\n","")) if id and id.index(look_for)
139
+ end
140
+ } if @jarfile.find_entry(properties)
141
+ return look_for # default
142
+ end
143
+
144
+ def readPluginXML(plugin_xml)
145
+ return unless @jarfile.find_entry('plugin.xml')
146
+ doc = Document.new @jarfile.read('plugin.xml')
147
+ # Get all perspectives
148
+ root = doc.root
149
+ res = []
150
+ root.elements.collect { |x| res << x if /org.eclipse.ui.perspectives/.match(x.attributes['point']) }
151
+ res[0].elements.each{
152
+ |x|
153
+ id = x.attributes['name'].sub(/^%/,'')
154
+ @perspectives[id] = UI_Perspective.new(id, nil, getTranslationForPlugin(id, @iso))
155
+ } if res and res[0] and res[0].elements
156
+ puts "found #{@perspectives.size} perspectives in #{plugin_xml}" if $VERBOSE
157
+
158
+ # Get all views
159
+ res = []
160
+ root.elements.collect { |x| res << x if /org.eclipse.ui.views/.match(x.attributes['point']) }
161
+ res[0].elements.each{
162
+ |x|
163
+ name = x.attributes['name'].sub(/^%/,'') if x.attributes['name']
164
+ id = x.attributes['id'].sub(/^%/,'')
165
+ if x.name.eql?('category')
166
+ addCategory(@view_categories, id, name)
167
+ elsif x.attributes['name']
168
+ category = x.attributes['category']
169
+ translation = getTranslationForPlugin(name, @iso)
170
+ puts "#{File.basename(@jarname, '.jar')}: Adding view: id #{id} category #{category.inspect} translation #{translation}" if $VERBOSE
171
+ unless category
172
+ @views[id] = UI_View.new(id, nil, translation)
173
+ else
174
+ @views[id] = UI_View.new(id, category, translation)
175
+ end
176
+ end
177
+ } if res and res[0] and res[0].elements
178
+ puts "found #{@views.size} views and #{@view_categories.size} categories" if $VERBOSE
179
+
180
+ # Get all preferencePages
181
+ res = []
182
+ root.elements.collect { |x| res << x if /org.eclipse.ui.preferencePages/.match(x.attributes['point']) }
183
+ res[0].elements.each{
184
+ |x|
185
+ name = x.attributes['name'].sub(/^%/,'')
186
+ id = x.attributes['id'].sub(/^%/,'')
187
+ category = x.attributes['category']
188
+ addCategory(@preferencePage_categories, id, name) unless category
189
+ translation = getTranslationForPlugin(name, @iso)
190
+ puts "Adding preferences: id #{id} category #{category.inspect} translation #{translation}" if $VERBOSE
191
+ unless category
192
+ @preferencePages[id] = UI_PreferencePage.new(id, nil, translation)
193
+ else
194
+ @preferencePages[id] = UI_PreferencePage.new(id, category, translation)
195
+ end
196
+ } if res and res[0] and res[0].elements
197
+ puts "#{sprintf("%-40s", File.basename(File.dirname(plugin_xml)))}: now #{@preferencePages.size} preferencePages" if $VERBOSE
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,5 @@
1
+ module Eclipse
2
+ module Plugin
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ #Created by JInto - www.guh-software.de
2
+ Ansicht=Agenda-Ansicht
3
+ Add_Recurring_Appointment=Terminserie hinzuf�gen
@@ -0,0 +1,21 @@
1
+ #encoding : utf-8
2
+ require 'spec_helper'
3
+
4
+ require 'eclipse/plugin'
5
+ require 'eclipse/helpers'
6
+
7
+ describe 'Helpers' do
8
+
9
+ before :each do
10
+ @dataDir = File.expand_path(File.join(File.dirname(__FILE__), 'data'))
11
+ end
12
+
13
+ it "should escape a property_string into an UTF-8 string" do
14
+ IO.readlines(File.join(@dataDir, 'plugin_de.properties')).each{
15
+ |line|
16
+ next unless line.index('Add_Recurring_Appointment')
17
+ unescaped = Eclipse::Helpers.unescape(line)
18
+ unescaped.should match /hinzufügen/
19
+ }
20
+ end
21
+ end
@@ -0,0 +1,84 @@
1
+ #encoding : utf-8
2
+ require 'spec_helper'
3
+
4
+ require 'eclipse/plugin'
5
+
6
+ describe 'Plugin' do
7
+
8
+ CHECK_4_VIEW = 'ch.elexis.views.TemplatePrintView'
9
+ CHECK_4_PERSPECTIVE = 'elexis.articlePerspective'
10
+ CHECK_4_PREFERENCE_PAGE = 'org.iatrix.preferences.IatrixPreferences'
11
+ APP_JAR = 'ch.elexis.core.application_3.0.0.v20140314-1352.jar'
12
+ IATRIX_JAR = 'org.iatrix_3.0.0.v20140313-1017.jar'
13
+ JAR_WITHOUT_LOCALIZE = 'ch.elexis.laborimport.hl7.allg-3.0.0-SNAPSHOT.jar'
14
+ before :all do
15
+ @dataDir = File.expand_path(File.join(File.dirname(__FILE__), 'data'))
16
+ end
17
+
18
+ it "should run the readme example" do
19
+ require 'eclipse/plugin'
20
+ workspace = Eclipse::Workspace.new(@dataDir)
21
+ workspace.parsePluginDir
22
+ workspace.views.first {|view| puts view.id }
23
+ workspace.views.size.should == 66
24
+ end
25
+
26
+ it "must be able to analyse a plugin.xml without localization" do
27
+ plugin = File.join(@dataDir, 'plugins', JAR_WITHOUT_LOCALIZE)
28
+ info = Eclipse::Plugin::Info.new(plugin)
29
+ info.preferencePages['ch.elexis.laborimport.hl7.preferences'].should_not be nil
30
+ info.workspace.should == @dataDir
31
+ end
32
+
33
+ it "must be able to analyse a plugin.xml with localization" do
34
+ plugin = File.join(@dataDir, 'plugins', IATRIX_JAR)
35
+ info = Eclipse::Plugin::Info.new(plugin)
36
+ info.views['org.iatrix.views.JournalView'].should_not be nil
37
+ info.view_categories['org.iatrix'].should_not be nil
38
+ info.workspace.should == @dataDir
39
+ end
40
+
41
+ it "must return the perspectives" do
42
+ plugin = File.join(@dataDir, 'plugins', APP_JAR)
43
+ info = Eclipse::Plugin::Info.new(plugin)
44
+ info.perspectives[CHECK_4_PERSPECTIVE].should_not be nil
45
+ end
46
+
47
+ it "must return the preferencePages" do
48
+ plugin = File.join(@dataDir, 'plugins', IATRIX_JAR)
49
+ info = Eclipse::Plugin::Info.new(plugin)
50
+ info.preferencePages[CHECK_4_PREFERENCE_PAGE].should_not be nil
51
+ end
52
+
53
+ it "must return the correct translation for a view" do
54
+ plugin = File.join(@dataDir, 'plugins', APP_JAR)
55
+ info = Eclipse::Plugin::Info.new(plugin)
56
+ info.views[CHECK_4_VIEW].should_not be nil
57
+ german = 'Vorlage Drucken'
58
+ info.views[CHECK_4_VIEW].translation.should == german
59
+ pp info.getTranslatedViews.find_all{ |item| item.match(german) }
60
+ info.workspace.should == @dataDir
61
+ info.getTranslatedViews.find_all{ |item| item.match(german) }.should_not be nil
62
+ end
63
+
64
+ it "must return the correct translation for a preferencePage" do
65
+ plugin = File.join(@dataDir, 'plugins', IATRIX_JAR)
66
+ info = Eclipse::Plugin::Info.new(plugin)
67
+ info.preferencePages[CHECK_4_PREFERENCE_PAGE].should_not be nil
68
+ german = 'Iatrix'
69
+ info.preferencePages[CHECK_4_PREFERENCE_PAGE].translation.should == german
70
+ info.workspace.should == @dataDir
71
+ info.getTranslatedPreferencePages.find_all{ |item| item.match(german) }.should_not be nil
72
+ end
73
+
74
+ it "must return the correct translation for a perspective" do
75
+ plugin = File.join(@dataDir, 'plugins', APP_JAR)
76
+ info = Eclipse::Plugin::Info.new(plugin)
77
+ info.perspectives[CHECK_4_PERSPECTIVE].should_not be nil
78
+ german = 'Artikel'
79
+ info.perspectives[CHECK_4_PERSPECTIVE].translation.should == german
80
+ info.workspace.should == @dataDir
81
+ info.getTranslatedPerspectives.find_all{ |item| item.match(german) }.should_not be nil
82
+ end
83
+
84
+ end
@@ -0,0 +1,13 @@
1
+ require "bundler"
2
+ Bundler.setup(:default, :development)
3
+
4
+ unless RUBY_PLATFORM =~ /java/
5
+ require "simplecov"
6
+ SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
7
+ SimpleCov.start do
8
+ add_filter "spec"
9
+ end
10
+ end
11
+
12
+ require "rspec"
13
+
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eclipse-plugin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Niklaus Giger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubyzip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
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: rspec
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
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-debugger
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Extracts localized info out of an Eclipse plugin jar
98
+ email:
99
+ - niklaus.giger@member.fsf.org
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - .travis.yml
106
+ - Gemfile
107
+ - Gemfile.lock
108
+ - LICENSE
109
+ - README.textile
110
+ - Rakefile
111
+ - eclipse-plugin.gemspec
112
+ - lib/eclipse/helpers.rb
113
+ - lib/eclipse/plugin.rb
114
+ - lib/eclipse/plugin/version.rb
115
+ - spec/data/plugin_de.properties
116
+ - spec/data/plugins/ch.elexis.core.application_3.0.0.v20140314-1352.jar
117
+ - spec/data/plugins/ch.elexis.laborimport.hl7.allg-3.0.0-SNAPSHOT.jar
118
+ - spec/data/plugins/org.iatrix_3.0.0.v20140313-1017.jar
119
+ - spec/helpers_spec.rb
120
+ - spec/plugin_spec.rb
121
+ - spec/spec_helper.rb
122
+ homepage: ''
123
+ licenses:
124
+ - GPLv3
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.2.1
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Extract information about views, properties and perspectives from an eclipse
146
+ plugin
147
+ test_files:
148
+ - spec/data/plugin_de.properties
149
+ - spec/data/plugins/ch.elexis.core.application_3.0.0.v20140314-1352.jar
150
+ - spec/data/plugins/ch.elexis.laborimport.hl7.allg-3.0.0-SNAPSHOT.jar
151
+ - spec/data/plugins/org.iatrix_3.0.0.v20140313-1017.jar
152
+ - spec/helpers_spec.rb
153
+ - spec/plugin_spec.rb
154
+ - spec/spec_helper.rb