evva 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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'
@@ -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
@@ -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
+ ```
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'evva'
4
+ Evva.run(ARGV)
@@ -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
@@ -0,0 +1,10 @@
1
+ type: Android
2
+
3
+ data_source:
4
+ type: google_sheet
5
+ sheet_id: 1LaJd68os3g_GFlerogC64grNIlXb2iukMznOvdml7A4
6
+
7
+ out_path: analytics
8
+ event_file_name: MixpanelAnalytics
9
+ event_enum_file_name: MixpanelEvent
10
+ people_file_name: MixpanelProperties
@@ -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
@@ -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