evva 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,14 +1,16 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
2
 
3
- gem 'colorize'
4
- gem 'safe_yaml'
3
+ source "https://rubygems.org"
4
+
5
+ gem "colorize"
6
+ gem "safe_yaml"
5
7
 
6
8
  group :test do
7
- gem 'rake'
8
- gem 'rspec'
9
- gem 'rspec-its'
10
- gem 'simplecov', require: false, group: :test
11
- gem 'simplecov-rcov', require: false
12
- gem 'webmock', '~> 1.20'
13
- gem 'rubocop'
9
+ gem "rake"
10
+ gem "rspec"
11
+ gem "rspec-its"
12
+ gem "rubocop"
13
+ gem "simplecov", require: false, group: :test
14
+ gem "simplecov-rcov", require: false
15
+ gem "webmock"
14
16
  end
data/Gemfile.lock CHANGED
@@ -1,61 +1,72 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- addressable (2.8.0)
5
- public_suffix (>= 2.0.2, < 5.0)
6
- ast (2.3.0)
7
- colorize (0.8.1)
8
- crack (0.4.3)
9
- safe_yaml (~> 1.0.0)
10
- diff-lcs (1.3)
11
- docile (1.1.5)
12
- hashdiff (0.3.7)
13
- json (2.3.0)
14
- parallel (1.12.0)
15
- parser (2.4.0.0)
16
- ast (~> 2.2)
17
- powerpack (0.1.1)
18
- public_suffix (4.0.6)
19
- rainbow (2.2.2)
20
- rake
21
- rake (12.3.3)
22
- rspec (3.7.0)
23
- rspec-core (~> 3.7.0)
24
- rspec-expectations (~> 3.7.0)
25
- rspec-mocks (~> 3.7.0)
26
- rspec-core (3.7.0)
27
- rspec-support (~> 3.7.0)
28
- rspec-expectations (3.7.0)
4
+ addressable (2.8.7)
5
+ public_suffix (>= 2.0.2, < 7.0)
6
+ ast (2.4.2)
7
+ bigdecimal (3.1.8)
8
+ colorize (1.1.0)
9
+ crack (1.0.0)
10
+ bigdecimal
11
+ rexml
12
+ diff-lcs (1.5.1)
13
+ docile (1.4.1)
14
+ hashdiff (1.1.1)
15
+ json (2.7.2)
16
+ language_server-protocol (3.17.0.3)
17
+ parallel (1.26.3)
18
+ parser (3.3.5.0)
19
+ ast (~> 2.4.1)
20
+ racc
21
+ public_suffix (6.0.1)
22
+ racc (1.8.1)
23
+ rainbow (3.1.1)
24
+ rake (13.2.1)
25
+ regexp_parser (2.9.2)
26
+ rexml (3.3.8)
27
+ rspec (3.13.0)
28
+ rspec-core (~> 3.13.0)
29
+ rspec-expectations (~> 3.13.0)
30
+ rspec-mocks (~> 3.13.0)
31
+ rspec-core (3.13.1)
32
+ rspec-support (~> 3.13.0)
33
+ rspec-expectations (3.13.3)
29
34
  diff-lcs (>= 1.2.0, < 2.0)
30
- rspec-support (~> 3.7.0)
31
- rspec-its (1.2.0)
35
+ rspec-support (~> 3.13.0)
36
+ rspec-its (1.3.0)
32
37
  rspec-core (>= 3.0.0)
33
38
  rspec-expectations (>= 3.0.0)
34
- rspec-mocks (3.7.0)
39
+ rspec-mocks (3.13.2)
35
40
  diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.7.0)
37
- rspec-support (3.7.0)
38
- rubocop (0.51.0)
41
+ rspec-support (~> 3.13.0)
42
+ rspec-support (3.13.1)
43
+ rubocop (1.67.0)
44
+ json (~> 2.3)
45
+ language_server-protocol (>= 3.17.0)
39
46
  parallel (~> 1.10)
40
- parser (>= 2.3.3.1, < 3.0)
41
- powerpack (~> 0.1)
42
- rainbow (>= 2.2.2, < 3.0)
47
+ parser (>= 3.3.0.2)
48
+ rainbow (>= 2.2.2, < 4.0)
49
+ regexp_parser (>= 2.4, < 3.0)
50
+ rubocop-ast (>= 1.32.2, < 2.0)
43
51
  ruby-progressbar (~> 1.7)
44
- unicode-display_width (~> 1.0, >= 1.0.1)
45
- ruby-progressbar (1.9.0)
46
- safe_yaml (1.0.4)
47
- simplecov (0.15.1)
48
- docile (~> 1.1.0)
49
- json (>= 1.8, < 3)
50
- simplecov-html (~> 0.10.0)
51
- simplecov-html (0.10.2)
52
- simplecov-rcov (0.2.3)
52
+ unicode-display_width (>= 2.4.0, < 3.0)
53
+ rubocop-ast (1.32.3)
54
+ parser (>= 3.3.1.0)
55
+ ruby-progressbar (1.13.0)
56
+ safe_yaml (1.0.5)
57
+ simplecov (0.22.0)
58
+ docile (~> 1.1)
59
+ simplecov-html (~> 0.11)
60
+ simplecov_json_formatter (~> 0.1)
61
+ simplecov-html (0.13.1)
62
+ simplecov-rcov (0.3.7)
53
63
  simplecov (>= 0.4.1)
54
- unicode-display_width (1.3.0)
55
- webmock (1.24.6)
56
- addressable (>= 2.3.6)
64
+ simplecov_json_formatter (0.1.4)
65
+ unicode-display_width (2.6.0)
66
+ webmock (3.24.0)
67
+ addressable (>= 2.8.0)
57
68
  crack (>= 0.3.2)
58
- hashdiff
69
+ hashdiff (>= 0.4.0, < 2.0.0)
59
70
 
60
71
  PLATFORMS
61
72
  ruby
@@ -69,7 +80,7 @@ DEPENDENCIES
69
80
  safe_yaml
70
81
  simplecov
71
82
  simplecov-rcov
72
- webmock (~> 1.20)
83
+ webmock
73
84
 
74
85
  BUNDLED WITH
75
- 2.2.26
86
+ 2.5.21
data/README.md CHANGED
@@ -4,7 +4,7 @@ Evva
4
4
  [![Status](https://travis-ci.org/hole19/evva.svg?branch=master)](https://travis-ci.org/hole19/evva?branch=master)
5
5
  [![Gem](https://img.shields.io/gem/v/evva.svg?style=flat)](http://rubygems.org/gems/evva "View this project in Rubygems")
6
6
 
7
- Evva automatically generates code for triggering events based on a Google Sheets specification. It generated code for both iOS (Swift) and Android (Kotlin).
7
+ Evva automatically generates code for triggering events based on a Google Sheets specification. It generates code for both Swift (iOS) and Kotlin (Android).
8
8
 
9
9
  # Instalation
10
10
 
data/bin/evva CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'evva'
3
+ require "evva"
4
4
  Evva.run(ARGV)
data/changelog.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## [0.5.0] - 2024-10-16
6
+
7
+ - Update all dependencies
8
+
9
+ ### Breaking Changes
10
+ - Fix deprecation warnings
11
+ - Set minimum ruby version as 3.2
12
+ - Change `object` events in Kotlin to `data object` as an improvement brought by Kotlin 2.0
13
+
5
14
  ## [0.4.4] - 2024-10-16
6
15
 
7
16
  ### Breaking Changes
data/evva.gemspec CHANGED
@@ -1,21 +1,23 @@
1
- require_relative 'lib/evva/version'
1
+ require_relative "lib/evva/version"
2
2
 
3
3
  Gem::Specification.new do |s|
4
- s.name = 'evva'
4
+ s.name = "evva"
5
5
  s.version = Evva::VERSION
6
6
  s.date = Evva::VERSION_UPDATED_AT
7
- s.summary = 'An event generating service'
8
- s.description = 'Evva generates all the analytics event tracking functions for you'
9
- s.authors = ['RicardoTrindade']
10
- s.email = 'ricardo.trindade743@gmail.com'
11
- s.license = 'MIT'
12
- s.homepage = 'https://github.com/hole19/'
7
+ s.summary = "An event generating service"
8
+ s.description = "Evva generates all the analytics event tracking functions for you"
9
+ s.authors = ["André Andrade", "João Costa", "Ricardo Trindade"]
10
+ s.email = "ricardo.trindade743@gmail.com"
11
+ s.license = "MIT"
12
+ s.homepage = "https://github.com/hole19/"
13
13
 
14
14
  s.files = `git ls-files`.split("\n")
15
- s.require_paths = ['lib']
15
+ s.require_paths = ["lib"]
16
16
 
17
- s.executables << 'evva'
17
+ s.executables << "evva"
18
18
 
19
- s.add_runtime_dependency 'safe_yaml', '~> 1.0'
20
- s.add_runtime_dependency 'colorize', '~> 0.7'
19
+ s.required_ruby_version = ">= 3.2.0"
20
+
21
+ s.add_runtime_dependency "colorize", "~> 1.0"
22
+ s.add_runtime_dependency "safe_yaml", "~> 1.0"
21
23
  end
data/lib/evva/config.rb CHANGED
@@ -83,7 +83,7 @@ module Evva
83
83
  }.freeze
84
84
 
85
85
  DICTIONARY_STRUCT = {
86
- 'google_sheet' => GOOGLE_SHEET_STRUCT
86
+ "google_sheet" => GOOGLE_SHEET_STRUCT
87
87
  }.freeze
88
88
  end
89
89
  end
@@ -1,4 +1,4 @@
1
- require 'fileutils'
1
+ require "fileutils"
2
2
 
3
3
  module Evva
4
4
  class FileReader
@@ -1,18 +1,18 @@
1
- require 'net/https'
2
- require 'csv'
1
+ require "net/https"
2
+ require "csv"
3
3
 
4
4
  module Evva
5
5
  class GoogleSheet
6
- EVENT_NAME = 'Event Name'
7
- EVENT_PROPERTIES = 'Event Properties'
8
- EVENT_DESTINATION = 'Event Destination'
6
+ EVENT_NAME = "Event Name"
7
+ EVENT_PROPERTIES = "Event Properties"
8
+ EVENT_DESTINATION = "Event Destination"
9
9
 
10
- PROPERTY_NAME = 'Property Name'
11
- PROPERTY_TYPE = 'Property Type'
12
- PROPERTY_DESTINATION = 'Property Destination'
10
+ PROPERTY_NAME = "Property Name"
11
+ PROPERTY_TYPE = "Property Type"
12
+ PROPERTY_DESTINATION = "Property Destination"
13
13
 
14
- ENUM_NAME = 'Enum Name'
15
- ENUM_VALUES = 'Possible Values'
14
+ ENUM_NAME = "Enum Name"
15
+ ENUM_VALUES = "Possible Values"
16
16
 
17
17
  def initialize(events_url, people_properties_url, enum_classes_url)
18
18
  @events_url = events_url
@@ -29,7 +29,7 @@ module Evva
29
29
  @events ||= @events_csv.map do |row|
30
30
  event_name = row[EVENT_NAME]
31
31
  properties = hash_parser(row[EVENT_PROPERTIES])
32
- destinations = row[EVENT_DESTINATION]&.split(',')
32
+ destinations = row[EVENT_DESTINATION]&.split(",")
33
33
  Evva::AnalyticsEvent.new(event_name, properties, destinations || [])
34
34
  end
35
35
  end
@@ -43,7 +43,7 @@ module Evva
43
43
  @people_properties ||= @people_properties_csv.map do |row|
44
44
  property_name = row[PROPERTY_NAME]
45
45
  property_type = row[PROPERTY_TYPE]
46
- destinations = row[PROPERTY_DESTINATION]&.split(',')
46
+ destinations = row[PROPERTY_DESTINATION]&.split(",")
47
47
  Evva::AnalyticsProperty.new(property_name, property_type, destinations || [])
48
48
  end
49
49
  end
@@ -56,7 +56,7 @@ module Evva
56
56
 
57
57
  @enum_classes ||= @enum_classes_csv.map do |row|
58
58
  enum_name = row[ENUM_NAME]
59
- values = row[ENUM_VALUES].split(',')
59
+ values = row[ENUM_VALUES].split(",")
60
60
  Evva::AnalyticsEnum.new(enum_name, values)
61
61
  end
62
62
  end
@@ -65,7 +65,7 @@ module Evva
65
65
  @destinations ||= events.map(&:destinations).flatten.uniq
66
66
  end
67
67
 
68
- private
68
+ private
69
69
 
70
70
  def get_csv(url)
71
71
  data = get(url)
@@ -89,7 +89,7 @@ module Evva
89
89
  request = Net::HTTP::Get.new(uri.request_uri)
90
90
  response = http.request(request)
91
91
 
92
- return get(response['location'], max_redirects - 1) if response.is_a? Net::HTTPRedirection
92
+ return get(response["location"], max_redirects - 1) if response.is_a? Net::HTTPRedirection
93
93
 
94
94
  raise "Http Error #{response.body}" if response.code.to_i >= 400
95
95
 
@@ -99,8 +99,8 @@ module Evva
99
99
  def hash_parser(property_array)
100
100
  h = {}
101
101
  unless property_array.nil? || property_array.empty?
102
- property_array.split(',').each do |prop|
103
- split_prop = prop.split(':')
102
+ property_array.split(",").each do |prop|
103
+ split_prop = prop.split(":")
104
104
  prop_name = split_prop[0].to_sym
105
105
  prop_type = split_prop[1].to_s
106
106
  h[prop_name] = prop_type
@@ -1,5 +1,5 @@
1
1
  module Evva
2
- class AndroidGenerator
2
+ class KotlinGenerator
3
3
  attr_accessor :package_name
4
4
 
5
5
  def initialize(package_name)
@@ -32,7 +32,7 @@ module Evva
32
32
  value_fetcher = param_name
33
33
 
34
34
  if is_special_property?(type)
35
- if type.end_with?('?')
35
+ if type.end_with?("?")
36
36
  # optional value, we need ? to access a parameter
37
37
  value_fetcher += "?"
38
38
  end
@@ -142,7 +142,7 @@ module Evva
142
142
  end
143
143
  end
144
144
 
145
- private
145
+ private
146
146
 
147
147
  def header_footer_wrapper
148
148
  package_name = @package_name
@@ -156,10 +156,8 @@ module Evva
156
156
  def template_from(path)
157
157
  file = File.read(path)
158
158
 
159
- # - 2nd argument (nil) changes nothing
160
- # - 3rd argument activates trim mode using "-" so that you can decide to
161
- # not include a line (useful on loops and if statements)
162
- ERB.new(file, nil, '-')
159
+ # trim mode using "-" so that you can decide to not include a line (useful on loops and if statements)
160
+ ERB.new(file, trim_mode: "-")
163
161
  end
164
162
 
165
163
  # extracted from Rails' ActiveSupport
@@ -174,16 +172,16 @@ module Evva
174
172
  end
175
173
 
176
174
  def constantize(string)
177
- string.tr(' ', '_').upcase
175
+ string.tr(" ", "_").upcase
178
176
  end
179
177
 
180
178
  def native_type(type)
181
179
  type
182
- .gsub('Date','String')
180
+ .gsub("Date","String")
183
181
  end
184
182
 
185
183
  def is_special_property?(type)
186
- !NATIVE_TYPES.include?(type.chomp('?'))
184
+ !NATIVE_TYPES.include?(type.chomp("?"))
187
185
  end
188
186
  end
189
187
  end
data/lib/evva/logger.rb CHANGED
@@ -1,5 +1,5 @@
1
- require 'singleton'
2
- require 'colorize'
1
+ require "singleton"
2
+ require "colorize"
3
3
 
4
4
  module Evva
5
5
  module Logger
@@ -27,11 +27,11 @@ module Evva
27
27
 
28
28
  def print_summary
29
29
  if @levels[:warn] > 0 || @levels[:error] > 0
30
- info ''
31
- info 'Finished with:'
30
+ info ""
31
+ info "Finished with:"
32
32
  info " #{@levels[:warn]} warnings" if @levels[:warn] > 0
33
33
  info " #{@levels[:error]} errors" if @levels[:error] > 0
34
- info ''
34
+ info ""
35
35
  end
36
36
  end
37
37
 
@@ -39,7 +39,7 @@ module Evva
39
39
  @silent_mode = value
40
40
  end
41
41
 
42
- private
42
+ private
43
43
 
44
44
  @levels = { info: 0, warn: 0, error: 0 }
45
45
  @silent_mode = false
@@ -13,7 +13,7 @@ Object.class_eval do
13
13
  def validate_structure!(structure, error_prefix = [])
14
14
  return if nil? && structure[:optional]
15
15
 
16
- prepend_error = error_prefix.empty? ? '' : (['self'] + error_prefix + [': ']).join
16
+ prepend_error = error_prefix.empty? ? "" : (["self"] + error_prefix + [": "]).join
17
17
 
18
18
  unless is_a? structure[:type]
19
19
  raise ArgumentError, "#{prepend_error}Expected #{structure[:type]}, got #{self.class}"
@@ -1,4 +1,4 @@
1
- require 'erb'
1
+ require "erb"
2
2
 
3
3
  module Evva
4
4
  class SwiftGenerator
@@ -21,7 +21,7 @@ module Evva
21
21
  value_fetcher = k.to_s
22
22
 
23
23
  if is_special_property?(type)
24
- if type.end_with?('?')
24
+ if type.end_with?("?")
25
25
  # optional value, we need ? to access a parameter
26
26
  value_fetcher += "?"
27
27
  end
@@ -100,7 +100,7 @@ module Evva
100
100
  end
101
101
  end
102
102
 
103
- private
103
+ private
104
104
 
105
105
  def header_footer_wrapper
106
106
  content = yield
@@ -113,24 +113,22 @@ module Evva
113
113
  def template_from(path)
114
114
  file = File.read(path)
115
115
 
116
- # - 2nd argument (nil) changes nothing
117
- # - 3rd argument activates trim mode using "-" so that you can decide to
118
- # not include a line (useful on loops and if statements)
119
- ERB.new(file, nil, '-')
116
+ # trim mode using "-" so that you can decide to not include a line (useful on loops and if statements)
117
+ ERB.new(file, trim_mode: "-")
120
118
  end
121
119
 
122
120
  def native_type(type)
123
121
  type
124
- .gsub('Boolean','Bool')
125
- .gsub('Long', 'Int')
122
+ .gsub("Boolean","Bool")
123
+ .gsub("Long", "Int")
126
124
  end
127
125
 
128
126
  def is_special_property?(type)
129
- !NATIVE_TYPES.include?(type.chomp('?'))
127
+ !NATIVE_TYPES.include?(type.chomp("?"))
130
128
  end
131
129
 
132
130
  def camelize(term)
133
- string = term.to_s.tr(' ', '_').downcase
131
+ string = term.to_s.tr(" ", "_").downcase
134
132
  string = string.sub(/^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
135
133
  string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
136
134
  string.gsub!("/".freeze, "::".freeze)
@@ -7,7 +7,7 @@ sealed class <%= class_name %>(
7
7
 
8
8
  <%- events.each_with_index do |e, index| -%>
9
9
  <%- if e[:properties].count == 0 -%>
10
- object <%= e[:class_name] %> : <%= class_name %>(
10
+ data object <%= e[:class_name] %> : <%= class_name %>(
11
11
  <%- else -%>
12
12
  data class <%= e[:class_name] %>(
13
13
  <%- e[:properties].each_with_index do |p, index| -%>
data/lib/evva/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Evva
2
- VERSION = '0.4.4'.freeze
3
- VERSION_UPDATED_AT = '2024-10-16'.freeze
2
+ VERSION = "0.5.0".freeze
3
+ VERSION_UPDATED_AT = "2024-10-16".freeze
4
4
  end
data/lib/evva.rb CHANGED
@@ -1,24 +1,24 @@
1
- require 'optparse'
2
- require 'yaml'
1
+ require "optparse"
2
+ require "yaml"
3
3
 
4
- require 'evva/logger'
5
- require 'evva/google_sheet'
6
- require 'evva/config'
7
- require 'evva/file_reader'
8
- require 'evva/analytics_event'
9
- require 'evva/analytics_enum'
10
- require 'evva/analytics_property'
11
- require 'evva/object_extension'
12
- require 'evva/version'
13
- require 'evva/android_generator'
14
- require 'evva/swift_generator'
4
+ require "evva/logger"
5
+ require "evva/google_sheet"
6
+ require "evva/config"
7
+ require "evva/file_reader"
8
+ require "evva/analytics_event"
9
+ require "evva/analytics_enum"
10
+ require "evva/analytics_property"
11
+ require "evva/object_extension"
12
+ require "evva/version"
13
+ require "evva/kotlin_generator"
14
+ require "evva/swift_generator"
15
15
 
16
16
  module Evva
17
17
  extend self
18
18
  def run(options)
19
19
  file_reader = Evva::FileReader.new
20
20
  options = command_line_options(options)
21
- unless config_file = file_reader.open_file('evva_config.yml', 'r', true)
21
+ unless config_file = file_reader.open_file("evva_config.yml", "r", true)
22
22
  Logger.error("Could not open evva_config.yml")
23
23
  return
24
24
  end
@@ -26,12 +26,12 @@ module Evva
26
26
  config = Evva::Config.new(hash: YAML.safe_load(config_file))
27
27
  bundle = analytics_data(config: config.data_source)
28
28
  case config.type.downcase
29
- when 'android'
30
- generator = Evva::AndroidGenerator.new(config.package_name)
31
- evva_write(bundle, generator, config, 'kt')
32
- when 'ios'
29
+ when "android"
30
+ generator = Evva::KotlinGenerator.new(config.package_name)
31
+ evva_write(bundle, generator, config, "kt")
32
+ when "ios"
33
33
  generator = Evva::SwiftGenerator.new
34
- evva_write(bundle, generator, config, 'swift')
34
+ evva_write(bundle, generator, config, "swift")
35
35
  end
36
36
  Evva::Logger.print_summary
37
37
  end
@@ -40,7 +40,7 @@ module Evva
40
40
  path = "#{configuration.out_path}/#{configuration.event_file_name}.#{extension}"
41
41
  write_to_file(path, generator.events(bundle[:events], configuration.event_file_name, configuration.event_enum_file_name, configuration.destinations_file_name))
42
42
 
43
- unless configuration.type.downcase == 'ios'
43
+ unless configuration.type.downcase == "ios"
44
44
  path = "#{configuration.out_path}/#{configuration.event_enum_file_name}.#{extension}"
45
45
  write_to_file(path, generator.event_enum(bundle[:events], configuration.event_enum_file_name))
46
46
  end
@@ -48,7 +48,7 @@ module Evva
48
48
  path = "#{configuration.out_path}/#{configuration.people_file_name}.#{extension}"
49
49
  write_to_file(path, generator.people_properties(bundle[:people], configuration.people_file_name, configuration.people_enum_file_name, configuration.destinations_file_name))
50
50
 
51
- unless configuration.type.downcase == 'ios'
51
+ unless configuration.type.downcase == "ios"
52
52
  path = "#{configuration.out_path}/#{configuration.people_enum_file_name}.#{extension}"
53
53
  write_to_file(path, generator.people_properties_enum(bundle[:people], configuration.people_enum_file_name))
54
54
  end
@@ -63,7 +63,7 @@ module Evva
63
63
  def analytics_data(config:)
64
64
  source =
65
65
  case config[:type]
66
- when 'google_sheet'
66
+ when "google_sheet"
67
67
  Evva::GoogleSheet.new(config[:events_url], config[:people_properties_url], config[:enum_classes_url])
68
68
  end
69
69
  events_bundle = {}
@@ -78,12 +78,12 @@ module Evva
78
78
  opts_hash = {}
79
79
 
80
80
  opts_parser = OptionParser.new do |opts|
81
- opts.on_tail('-h', '--help', 'Show this message') do
81
+ opts.on_tail("-h", "--help", "Show this message") do
82
82
  puts opts
83
83
  exit
84
84
  end
85
85
 
86
- opts.on_tail('-v', '--version', 'Show version') do
86
+ opts.on_tail("-v", "--version", "Show version") do
87
87
  puts Evva::VERSION
88
88
  exit
89
89
  end