evva 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +5 -0
- data/.rubocop_todo.yml +477 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +79 -0
- data/README.md +27 -0
- data/bin/evva +4 -0
- data/evva.gemspec +24 -0
- data/evva_config.yml +10 -0
- data/lib/evva.rb +95 -0
- data/lib/evva/android_generator.rb +115 -0
- data/lib/evva/config.rb +68 -0
- data/lib/evva/data_source.rb +23 -0
- data/lib/evva/file_reader.rb +24 -0
- data/lib/evva/google_sheet.rb +91 -0
- data/lib/evva/logger.rb +61 -0
- data/lib/evva/mixpanel_enum.rb +14 -0
- data/lib/evva/mixpanel_event.rb +13 -0
- data/lib/evva/object_extension.rb +41 -0
- data/lib/evva/swift_generator.rb +128 -0
- data/lib/evva/version.rb +4 -0
- data/rubocop.yml +54 -0
- data/spec/evva_spec.rb +40 -0
- data/spec/fixtures/sample_public_enums.html +1 -0
- data/spec/fixtures/sample_public_info.html +1 -0
- data/spec/fixtures/sample_public_people_properties.html +1 -0
- data/spec/fixtures/sample_public_sheet.html +1 -0
- data/spec/fixtures/test.yml +10 -0
- data/spec/lib/evva/android_generator_spec.rb +135 -0
- data/spec/lib/evva/config_spec.rb +40 -0
- data/spec/lib/evva/data_source_spec.rb +28 -0
- data/spec/lib/evva/google_sheet_spec.rb +107 -0
- data/spec/lib/evva/logger_spec.rb +36 -0
- data/spec/lib/evva/object_extension_spec.rb +99 -0
- data/spec/lib/evva/swift_generator_spec.rb +81 -0
- data/spec/spec_helper.rb +113 -0
- metadata +136 -0
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
ruby '2.3.3'
|
3
|
+
|
4
|
+
gem 'colorize', '~> 0.7'
|
5
|
+
gem 'rubocop', '~> 0.49.1'
|
6
|
+
gem 'safe_yaml', '~> 1.0'
|
7
|
+
gem 'xml-simple', '~> 1.1'
|
8
|
+
|
9
|
+
gem 'rspec'
|
10
|
+
gem 'rspec-its'
|
11
|
+
gem 'simplecov', require: false, group: :test
|
12
|
+
gem 'simplecov-rcov', require: false
|
13
|
+
gem 'webmock', '~> 1.20'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.5.1)
|
5
|
+
public_suffix (~> 2.0, >= 2.0.2)
|
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.5)
|
13
|
+
json (2.1.0)
|
14
|
+
parallel (1.12.0)
|
15
|
+
parser (2.4.0.0)
|
16
|
+
ast (~> 2.2)
|
17
|
+
powerpack (0.1.1)
|
18
|
+
public_suffix (2.0.5)
|
19
|
+
rainbow (2.2.2)
|
20
|
+
rake
|
21
|
+
rake (12.0.0)
|
22
|
+
rspec (3.6.0)
|
23
|
+
rspec-core (~> 3.6.0)
|
24
|
+
rspec-expectations (~> 3.6.0)
|
25
|
+
rspec-mocks (~> 3.6.0)
|
26
|
+
rspec-core (3.6.0)
|
27
|
+
rspec-support (~> 3.6.0)
|
28
|
+
rspec-expectations (3.6.0)
|
29
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
+
rspec-support (~> 3.6.0)
|
31
|
+
rspec-its (1.2.0)
|
32
|
+
rspec-core (>= 3.0.0)
|
33
|
+
rspec-expectations (>= 3.0.0)
|
34
|
+
rspec-mocks (3.6.0)
|
35
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
36
|
+
rspec-support (~> 3.6.0)
|
37
|
+
rspec-support (3.6.0)
|
38
|
+
rubocop (0.49.1)
|
39
|
+
parallel (~> 1.10)
|
40
|
+
parser (>= 2.3.3.1, < 3.0)
|
41
|
+
powerpack (~> 0.1)
|
42
|
+
rainbow (>= 1.99.1, < 3.0)
|
43
|
+
ruby-progressbar (~> 1.7)
|
44
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
45
|
+
ruby-progressbar (1.8.1)
|
46
|
+
safe_yaml (1.0.4)
|
47
|
+
simplecov (0.15.0)
|
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)
|
53
|
+
simplecov (>= 0.4.1)
|
54
|
+
unicode-display_width (1.3.0)
|
55
|
+
webmock (1.24.6)
|
56
|
+
addressable (>= 2.3.6)
|
57
|
+
crack (>= 0.3.2)
|
58
|
+
hashdiff
|
59
|
+
xml-simple (1.1.5)
|
60
|
+
|
61
|
+
PLATFORMS
|
62
|
+
ruby
|
63
|
+
|
64
|
+
DEPENDENCIES
|
65
|
+
colorize (~> 0.7)
|
66
|
+
rspec
|
67
|
+
rspec-its
|
68
|
+
rubocop (~> 0.49.1)
|
69
|
+
safe_yaml (~> 1.0)
|
70
|
+
simplecov
|
71
|
+
simplecov-rcov
|
72
|
+
webmock (~> 1.20)
|
73
|
+
xml-simple (~> 1.1)
|
74
|
+
|
75
|
+
RUBY VERSION
|
76
|
+
ruby 2.3.3p222
|
77
|
+
|
78
|
+
BUNDLED WITH
|
79
|
+
1.15.4
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Evva
|
2
|
+
|
3
|
+
# Instalation
|
4
|
+
|
5
|
+
` gem install evva `
|
6
|
+
|
7
|
+
# Usage
|
8
|
+
1. Open the terminal in app project base
|
9
|
+
2. Run `evva`
|
10
|
+
3. That's it (given that someone already configured Evva)
|
11
|
+
|
12
|
+
# Configuration
|
13
|
+
Evva's configuration comes from a evva_config.yml file that should be placed on your
|
14
|
+
app root directory. The .yml file has the following structure.
|
15
|
+
|
16
|
+
```
|
17
|
+
type: Android|iOS
|
18
|
+
|
19
|
+
data_source:
|
20
|
+
type: google_sheet
|
21
|
+
sheet_id: <GOOGLE-DRIVE-SHEET-ID>
|
22
|
+
|
23
|
+
out_path: /folder/where/analytics/classes/are
|
24
|
+
event_file_name: /file/with/tracking/functions
|
25
|
+
event_enum_file_name: /file/with/event/names
|
26
|
+
people_file_name: /file/with/people/properties
|
27
|
+
```
|
data/bin/evva
ADDED
data/evva.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'lib/evva/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'evva'
|
5
|
+
s.version = Evva::VERSION
|
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/'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
|
17
|
+
s.executables << 'evva'
|
18
|
+
|
19
|
+
s.add_runtime_dependency 'safe_yaml', '~> 1.0'
|
20
|
+
s.add_runtime_dependency 'colorize', '~> 0.7'
|
21
|
+
s.add_runtime_dependency 'xml-simple', '~> 1.1'
|
22
|
+
|
23
|
+
s.add_development_dependency 'webmock', '~> 1.20'
|
24
|
+
end
|
data/evva_config.yml
ADDED
data/lib/evva.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'evva/logger'
|
5
|
+
require 'evva/google_sheet'
|
6
|
+
require 'evva/config'
|
7
|
+
require 'evva/file_reader'
|
8
|
+
require 'evva/data_source'
|
9
|
+
require 'evva/mixpanel_event'
|
10
|
+
require 'evva/mixpanel_enum'
|
11
|
+
require 'evva/object_extension'
|
12
|
+
require 'evva/version'
|
13
|
+
require 'evva/android_generator'
|
14
|
+
require 'evva/swift_generator'
|
15
|
+
|
16
|
+
module Evva
|
17
|
+
extend self
|
18
|
+
def run(options)
|
19
|
+
file_reader = Evva::FileReader.new
|
20
|
+
options = command_line_options(options)
|
21
|
+
unless config_file = file_reader.open_file('evva_config.yml', 'r', true)
|
22
|
+
Logger.error("Could not open evva_config.yml")
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
config = Evva::Config.new(hash: YAML.safe_load(config_file))
|
27
|
+
bundle = analytics_data(config: config.data_source)
|
28
|
+
case config.type.downcase
|
29
|
+
when 'android'
|
30
|
+
generator = Evva::AndroidGenerator.new
|
31
|
+
evva_write(bundle, generator, config, 'kt')
|
32
|
+
when 'ios'
|
33
|
+
generator = Evva::SwiftGenerator.new
|
34
|
+
evva_write(bundle, generator, config, 'swift')
|
35
|
+
end
|
36
|
+
Evva::Logger.print_summary
|
37
|
+
end
|
38
|
+
|
39
|
+
def evva_write(bundle, generator, configuration, extension)
|
40
|
+
path = "#{configuration.out_path}/#{configuration.event_file_name}.#{extension}"
|
41
|
+
write_to_file(path, generator.events(bundle[:events]))
|
42
|
+
|
43
|
+
path = "#{configuration.out_path}/#{configuration.event_enum_file_name}.#{extension}"
|
44
|
+
write_to_file(path, generator.event_enum(bundle[:events]))
|
45
|
+
|
46
|
+
path = "#{configuration.out_path}/#{configuration.people_file_name}.#{extension}"
|
47
|
+
write_to_file(path, generator.people_properties(bundle[:people]))
|
48
|
+
|
49
|
+
bundle[:enums].each do |enum|
|
50
|
+
path = "#{configuration.out_path}/#{enum.enum_name}.#{extension}"
|
51
|
+
write_to_file(path, generator.special_property_enum(enum))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def analytics_data(config:)
|
56
|
+
source =
|
57
|
+
case config[:type]
|
58
|
+
when 'google_sheet'
|
59
|
+
Evva::GoogleSheet.new(config[:sheet_id])
|
60
|
+
end
|
61
|
+
events_bundle = {}
|
62
|
+
events_bundle[:events] = source.events
|
63
|
+
events_bundle[:people] = source.people_properties
|
64
|
+
events_bundle[:enums] = source.enum_classes
|
65
|
+
events_bundle
|
66
|
+
end
|
67
|
+
|
68
|
+
def command_line_options(options)
|
69
|
+
opts_hash = {}
|
70
|
+
|
71
|
+
opts_parser = OptionParser.new do |opts|
|
72
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
73
|
+
puts opts
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
|
77
|
+
opts.on_tail('-v', '--version', 'Show version') do
|
78
|
+
puts Evva::VERSION
|
79
|
+
exit
|
80
|
+
end
|
81
|
+
end
|
82
|
+
opts_parser.parse!(options)
|
83
|
+
|
84
|
+
opts_hash
|
85
|
+
end
|
86
|
+
|
87
|
+
def write_to_file(path, data)
|
88
|
+
file_reader = Evva::FileReader.new
|
89
|
+
if file = file_reader.open_file(path, "w", false)
|
90
|
+
file_reader.write_to_file(file, data)
|
91
|
+
else
|
92
|
+
Logger.error("Could not write to file in #{path}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Evva
|
2
|
+
class AndroidGenerator
|
3
|
+
KOTLIN_EVENT_HEADER =
|
4
|
+
"package com.hole19golf.hole19.analytics\n\n"\
|
5
|
+
"import com.hole19golf.hole19.analytics.Event\n"\
|
6
|
+
"import com.hole19golf.hole19.analytics.MixpanelAnalyticsMask\n"\
|
7
|
+
"import org.json.JSONObject\n\n"\
|
8
|
+
"open class MixpanelEvents(private val mixpanelMask: MixpanelAnalyticsMask) {\n".freeze
|
9
|
+
|
10
|
+
KOTLIN_PEOPLE_HEADER =
|
11
|
+
"package com.hole19golf.hole19.analytics\n"\
|
12
|
+
"import com.hole19golf.hole19.analytics.Event\n\n"\
|
13
|
+
"enum class MixpanelProperties(val key: String) {\n".freeze
|
14
|
+
|
15
|
+
KOTLIN_BUNDLE_HEADER =
|
16
|
+
"package com.hole19golf.hole19.analytics\n"\
|
17
|
+
"import com.hole19golf.hole19.analytics.Event\n\n"\
|
18
|
+
"enum class MixpanelEvent(override val key: String) : Event {\n".freeze
|
19
|
+
|
20
|
+
KOTIN_PEOPLE_FUNCTIONS =
|
21
|
+
"\topen fun updateProperties(property: MixpanelProperties, value: Any) {\n"\
|
22
|
+
"\t\tmixpanelMask.updateProperties(property.key, value)"\
|
23
|
+
"\t\n} \n"\
|
24
|
+
"\topen fun incrementCounter(property: MixpanelProperties) {\n"\
|
25
|
+
"\t\tmixpanelMask.incrementCounter(property.key)"\
|
26
|
+
"\t\n} \n".freeze
|
27
|
+
|
28
|
+
NATIVE_TYPES = %w[Long Int String Double Float Boolean].freeze
|
29
|
+
|
30
|
+
def events(bundle)
|
31
|
+
event_file = KOTLIN_EVENT_HEADER
|
32
|
+
bundle.each do |event|
|
33
|
+
event_file += "\n#{kotlin_function(event)}"
|
34
|
+
end
|
35
|
+
event_file += KOTIN_PEOPLE_FUNCTIONS
|
36
|
+
event_file += "\n}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def people_properties(people_bundle)
|
40
|
+
properties = KOTLIN_PEOPLE_HEADER
|
41
|
+
properties += people_bundle.map { |prop| "\t\t#{prop.upcase}(\"#{prop}\")" }.join(",\n")
|
42
|
+
properties += ";\n}\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
def event_enum(bundle)
|
46
|
+
event_file = KOTLIN_BUNDLE_HEADER
|
47
|
+
event_file += bundle.map { |event| "\t\t#{event.event_name.upcase}(\"#{event.event_name}\")"}.join(", \n")
|
48
|
+
event_file += "\n}\n"
|
49
|
+
end
|
50
|
+
|
51
|
+
def kotlin_function(event_data)
|
52
|
+
function_name = 'track' + titleize(event_data.event_name)
|
53
|
+
function_arguments = event_data.properties.map { |name, type| "#{name}: #{type}" }.join(', ')
|
54
|
+
if !function_arguments.empty?
|
55
|
+
props = json_props(event_data.properties)
|
56
|
+
function_body =
|
57
|
+
"open fun #{function_name}(#{function_arguments}) {"\
|
58
|
+
"#{props}"\
|
59
|
+
"\tmixpanelMask.trackEvent(MixpanelEvent.#{event_data.event_name.upcase}, properties)\n"
|
60
|
+
else
|
61
|
+
function_body =
|
62
|
+
"open fun #{function_name}() {\n"\
|
63
|
+
"\tmixpanelMask.trackEvent(MixpanelEvent.#{event_data.event_name.upcase})\n"
|
64
|
+
end
|
65
|
+
function_body += "}\n"
|
66
|
+
end
|
67
|
+
|
68
|
+
def special_property_enum(enum)
|
69
|
+
enum_body = "package com.hole19golf.hole19.analytics\n\n"
|
70
|
+
enum_body += "enum class #{enum.enum_name}(val key: String) {\n"
|
71
|
+
enum_body += enum.values.map { |vals| "\t#{vals.tr(' ', '_').upcase}(\"#{vals}\")"}.join(",\n")
|
72
|
+
enum_body += "\n}\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def json_props(properties)
|
78
|
+
split_properties =
|
79
|
+
properties
|
80
|
+
.map do |name, type|
|
81
|
+
if special_property?(type)
|
82
|
+
if optional_property?(type)
|
83
|
+
"#{name}?.let { put(\"#{name}\", it.key) }"
|
84
|
+
else
|
85
|
+
"put(\"#{name}\", #{name}.key)"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
if optional_property?(type)
|
89
|
+
"#{name}?.let { put(\"#{name}\", it) }"
|
90
|
+
else
|
91
|
+
"put(\"#{name}\", #{name})"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
.map { |line| "\t\t#{line}" }
|
96
|
+
.join("\n")
|
97
|
+
|
98
|
+
resulting_json = "\n\tval properties = JSONObject().apply {\n" +
|
99
|
+
+split_properties.to_s
|
100
|
+
resulting_json += "\n\t}\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
def special_property?(type)
|
104
|
+
!NATIVE_TYPES.include?(type.chomp('?'))
|
105
|
+
end
|
106
|
+
|
107
|
+
def optional_property?(type)
|
108
|
+
type.include?('?')
|
109
|
+
end
|
110
|
+
|
111
|
+
def titleize(str)
|
112
|
+
str.split('_').collect(&:capitalize).join
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/evva/config.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Evva
|
2
|
+
class Config
|
3
|
+
def initialize(hash:)
|
4
|
+
@hash = hash.deep_symbolize
|
5
|
+
@hash.validate_structure!(CONFIG_STRUCT)
|
6
|
+
|
7
|
+
unless dict_struct = DICTIONARY_STRUCT[@hash[:data_source][:type]]
|
8
|
+
raise ArgumentError, "unknown data source type '#{@hash[:data_source][:type]}'"
|
9
|
+
end
|
10
|
+
|
11
|
+
@hash[:data_source].validate_structure!(dict_struct)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
@hash
|
16
|
+
end
|
17
|
+
|
18
|
+
def data_source
|
19
|
+
@hash[:data_source]
|
20
|
+
end
|
21
|
+
|
22
|
+
def type
|
23
|
+
@hash[:type]
|
24
|
+
end
|
25
|
+
|
26
|
+
def out_path
|
27
|
+
@hash[:out_path]
|
28
|
+
end
|
29
|
+
|
30
|
+
def event_file_name
|
31
|
+
@hash[:event_file_name]
|
32
|
+
end
|
33
|
+
|
34
|
+
def people_file_name
|
35
|
+
@hash[:people_file_name]
|
36
|
+
end
|
37
|
+
|
38
|
+
def event_enum_file_name
|
39
|
+
@hash[:event_enum_file_name]
|
40
|
+
end
|
41
|
+
|
42
|
+
CONFIG_STRUCT = {
|
43
|
+
type: Hash,
|
44
|
+
elements: {
|
45
|
+
type: { type: String },
|
46
|
+
data_source: { type: Hash, elements: {
|
47
|
+
type: { type: String }
|
48
|
+
} },
|
49
|
+
out_path: { type: String },
|
50
|
+
event_file_name: { type: String },
|
51
|
+
event_enum_file_name: { type: String },
|
52
|
+
people_file_name: { type: String }
|
53
|
+
}
|
54
|
+
}.freeze
|
55
|
+
|
56
|
+
GOOGLE_SHEET_STRUCT = {
|
57
|
+
type: Hash,
|
58
|
+
elements: {
|
59
|
+
type: { type: String },
|
60
|
+
sheet_id: { type: String }
|
61
|
+
}
|
62
|
+
}.freeze
|
63
|
+
|
64
|
+
DICTIONARY_STRUCT = {
|
65
|
+
'google_sheet' => GOOGLE_SHEET_STRUCT
|
66
|
+
}.freeze
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Evva
|
2
|
+
class DataSource
|
3
|
+
def initialize(keys)
|
4
|
+
unless keys.is_a?(Hash)
|
5
|
+
raise ArgumentError, "keys: expected Hash, got #{keys.class}"
|
6
|
+
end
|
7
|
+
|
8
|
+
keys.each do |property, v|
|
9
|
+
unless v.is_a?(Hash)
|
10
|
+
raise ArgumentError, "keys['#{property}']: expected Hash, got #{v.class}"
|
11
|
+
end
|
12
|
+
|
13
|
+
v.each do |key, v|
|
14
|
+
unless v.is_a?(String) || v.nil?
|
15
|
+
raise ArgumentError, "keys['#{property}']['#{key}']: expected String, got #{v.class}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
@keys = Hash[keys.map { |k, v| [k.downcase, v] }]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|