ios_analytics_cli 1.0.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +135 -0
  3. data/.rubocop.yml +2 -0
  4. data/Gemfile +7 -0
  5. data/Gemfile.lock +62 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +48 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/exe/analytics +34 -0
  12. data/ios_analytics_cli.gemspec +35 -0
  13. data/lib/ios_analytics_cli.rb +52 -0
  14. data/lib/ios_analytics_cli/commands/command.rb +6 -0
  15. data/lib/ios_analytics_cli/commands/generate.rb +77 -0
  16. data/lib/ios_analytics_cli/commands/init.rb +55 -0
  17. data/lib/ios_analytics_cli/error_handler.rb +22 -0
  18. data/lib/ios_analytics_cli/helpers/terminal.rb +11 -0
  19. data/lib/ios_analytics_cli/info.rb +16 -0
  20. data/lib/ios_analytics_cli/interactors/event.rb +22 -0
  21. data/lib/ios_analytics_cli/interactors/interactor.rb +8 -0
  22. data/lib/ios_analytics_cli/interactors/user_property.rb +12 -0
  23. data/lib/ios_analytics_cli/io/config.rb +18 -0
  24. data/lib/ios_analytics_cli/io/io.rb +10 -0
  25. data/lib/ios_analytics_cli/serializers/base.rb +43 -0
  26. data/lib/ios_analytics_cli/serializers/objc/analytics.rb +126 -0
  27. data/lib/ios_analytics_cli/serializers/objc/enums.rb +67 -0
  28. data/lib/ios_analytics_cli/serializers/objc/event.rb +182 -0
  29. data/lib/ios_analytics_cli/serializers/objc/logger.rb +53 -0
  30. data/lib/ios_analytics_cli/serializers/objc/objc.rb +23 -0
  31. data/lib/ios_analytics_cli/serializers/objc/screen.rb +90 -0
  32. data/lib/ios_analytics_cli/serializers/objc/user_property.rb +120 -0
  33. data/lib/ios_analytics_cli/serializers/swift/analytics.rb +63 -0
  34. data/lib/ios_analytics_cli/serializers/swift/event.rb +121 -0
  35. data/lib/ios_analytics_cli/serializers/swift/logger.rb +38 -0
  36. data/lib/ios_analytics_cli/serializers/swift/screen.rb +55 -0
  37. data/lib/ios_analytics_cli/serializers/swift/swift.rb +20 -0
  38. data/lib/ios_analytics_cli/serializers/swift/user_property.rb +69 -0
  39. data/lib/ios_analytics_cli/templates/header.erb +7 -0
  40. data/lib/ios_analytics_cli/templates/templates.rb +12 -0
  41. data/lib/ios_analytics_cli/version.rb +4 -0
  42. metadata +172 -0
@@ -0,0 +1,6 @@
1
+ module Analytics
2
+ # Command module is used to namespace all the command-classes,
3
+ # i.e. classes that are handling all commands that can be run from the CLI.
4
+ module Command
5
+ end
6
+ end
@@ -0,0 +1,77 @@
1
+ require 'commander/blank'
2
+ require 'commander/command'
3
+
4
+ module Analytics
5
+ module Command
6
+ # A class that's called from CLI's 'generate' argument,
7
+ # and used to generate analytics files from the resource JSON file.
8
+ class Generate
9
+ include Helpers::Terminal
10
+
11
+ def perform
12
+ unless File.file?(config_src_path)
13
+ prompt.error("Unable to find #{config_src_path}")
14
+ return
15
+ end
16
+ prompt.say('Generating analytics files...')
17
+ generate
18
+ prompt.ok('Analytics files are successfully generated!')
19
+ end
20
+
21
+ ## The following methods are used to generate analytics files,
22
+ ## based on settings defined in 'analytics.yml' configuration file.
23
+
24
+ private
25
+
26
+ ## Retrieving values from configuration file.
27
+
28
+ def config
29
+ Analytics::IO::Config.read
30
+ end
31
+
32
+ def config_lang
33
+ config[:language]
34
+ end
35
+
36
+ def config_analytics_path
37
+ config[:analyticsFilesPath]
38
+ end
39
+
40
+ def config_src_path
41
+ config[:sourcePath]
42
+ end
43
+
44
+ ## Generating
45
+
46
+ def generate
47
+ json_file = File.open(config_src_path)
48
+ json_src = JSON.load(json_file)
49
+
50
+ case config_lang
51
+ when 'swift'
52
+ FileUtils.mkdir_p config_analytics_path unless File.exist? config_analytics_path
53
+ Analytics::Serializer::Swift.new(json_src).save(config_analytics_path)
54
+ when 'objc'
55
+ FileUtils.mkdir_p config_analytics_path unless File.exist? config_analytics_path
56
+ Analytics::Serializer::ObjC.new(json_src).save(config_analytics_path)
57
+ when 'swift-objc'
58
+
59
+ swift_path = config_analytics_path + '/Swift'
60
+ objc_path = config_analytics_path + '/ObjC'
61
+
62
+ FileUtils.mkdir_p swift_path unless File.exist? swift_path
63
+ FileUtils.mkdir_p objc_path unless File.exist? objc_path
64
+
65
+ Analytics::Serializer::Swift.new(json_src).save(swift_path)
66
+ Analytics::Serializer::ObjC.new(json_src).save(objc_path)
67
+ else
68
+ prompt.error("Unsupported value '#{config_lang}' for 'language'. Supported values are:\n"\
69
+ "- 'swift' - if You're using only Swift in Your project,\n"\
70
+ "- 'objc' - if You're using only ObjC in Your project,\n"\
71
+ "- 'swift-objc' if You're using both Swift & ObjC in Your project.")
72
+ return
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,55 @@
1
+ require 'commander/blank'
2
+ require 'commander/command'
3
+
4
+ module Analytics
5
+ module Command
6
+ # A class called from CLI's 'init' argument,
7
+ # which generates the project's configuration file depending on the user's input.
8
+ class Init
9
+ include Helpers::Terminal
10
+
11
+ # Entry point of this class - the only public method in it.
12
+ def perform
13
+ lang = lang_selection
14
+ analytics_path = analytics_path_selection
15
+ src_path = src_path_selection
16
+
17
+ config = {
18
+ language: lang,
19
+ analyticsFilesPath: analytics_path,
20
+ sourcePath: src_path
21
+ }
22
+ Analytics::IO::Config.write(config)
23
+
24
+ prompt.ok('Configuration file is properly created. You\'re good to go!')
25
+ end
26
+
27
+ ## The following methods are used to retrieve project information,
28
+ ## in order to create analytitcs.yml configuration file.
29
+
30
+ private
31
+
32
+ # Prompts a user to select a programming language.
33
+ def lang_selection
34
+ languages = {
35
+ 'Swift' => 'swift',
36
+ 'Objective-C' => 'objc',
37
+ 'Both' => 'swift-objc'
38
+ }
39
+ prompt.select('Select lang:', languages)
40
+ end
41
+
42
+ # Prompts a user to select a path to which the generated
43
+ # analytics files will be saved.
44
+ def analytics_path_selection
45
+ prompt.ask('Where would You like to store analytics files? (Press enter to use default path)', default: './Common/Analytics/')
46
+ end
47
+
48
+ # Prompts a user to select a path in which he'll hold a source json file,
49
+ # from which the analytics files will be generated.
50
+ def src_path_selection
51
+ prompt.ask('Where would You like to store json file from which analytics files will be generated? (Press enter to use default path)', default: 'analytics-source.json')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,22 @@
1
+ module Analytics
2
+ class ErrorHandler
3
+ class << self
4
+ def rescuable
5
+ yield
6
+ rescue StandardError => e
7
+ handle(e)
8
+ end
9
+
10
+ def handle(e)
11
+ prompt.error(
12
+ case e
13
+ when Errno::ENOENT
14
+ "We could not find a file that we need:\n\n#{e.message}"
15
+ else
16
+ "An error happened. This might help:\n\n#{e.message}"
17
+ end
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ require 'tty-prompt'
2
+
3
+ module Analytics
4
+ module Helpers
5
+ module Terminal
6
+ def prompt
7
+ @prompt ||= TTY::Prompt.new(interrupt: :exit)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module Analytics
2
+ def self.summary
3
+ <<-Summary
4
+ CLI tool that generates Analytics files, for iOS project, from a JSON file.
5
+ Summary
6
+ end
7
+
8
+ def self.description
9
+ <<-Desc
10
+ This gem is develop in order to reduce the time needed to even start with the implementation of Analytics in iOS apps,
11
+ by generating files, classes, methods & everything else that\'s required to call a specific event, change a user property,
12
+ or even track an open screen. By calling just 2 commands, analytics init & analytics generate,
13
+ all of these are generated for You from the appropriately configured JSON file.
14
+ Desc
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ require 'tty-prompt'
2
+
3
+ module Analytics
4
+ module Interactor
5
+ class Event
6
+ # Returns all properties, from all events, which uses an enum as the value.
7
+ def self.all_enum_properties(events)
8
+ events
9
+ .reject { |event| event['properties'].nil? }
10
+ .flat_map { |event| event['properties'] }
11
+ .reject { |property| property['values'].nil? }
12
+ end
13
+
14
+ def self.enum_properties_from_event(event)
15
+ properties = event['properties']
16
+ return nil if properties.nil?
17
+
18
+ properties.reject { |property| property['values'].nil? }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ require 'tty-prompt'
2
+
3
+ module Analytics
4
+ # Interactor module is used to namespace all business logic
5
+ # specific to some use-case.
6
+ module Interactor
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ require 'tty-prompt'
2
+
3
+ module Analytics
4
+ module Interactor
5
+ class UserProperty
6
+ # Returns all user properties which uses an enum as the value.
7
+ def self.all_enum_user_properties(user_properties)
8
+ user_properties.reject { |up| up['values'].nil? }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Analytics::IO
2
+ # Config operates on .yml configuration file
3
+ # inside project's root folder.
4
+ class Config
5
+ # A path to the .yml configuration file.
6
+ CONFIG_PATH = "#{PROJ_DIR}/analytics.yml".freeze
7
+
8
+ # Writes to a configuration file.
9
+ def self.write(data)
10
+ File.open(CONFIG_PATH, 'w') { |file| YAML.dump(data, file) }
11
+ end
12
+
13
+ # Reads the configuration file.
14
+ def self.read
15
+ YAML.load_file(CONFIG_PATH)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Analytics
2
+ # IO is a module that holds all IO stuff,
3
+ # such as reading/writing to a file or similar.
4
+ module IO
5
+ # The directory of the current project.
6
+ # Currently it's just a `Dir.pwd`, but we'll may
7
+ # receive a path in the CLI command in future.
8
+ PROJ_DIR = Dir.pwd.freeze
9
+ end
10
+ end
@@ -0,0 +1,43 @@
1
+ module Analytics
2
+ module Serializer
3
+ class Base
4
+ def initialize(src)
5
+ @src = src
6
+ end
7
+
8
+ # An abstract method used to save a file at provided path.
9
+ def save(_path)
10
+ raise NotImplementedError, 'Abstract Method'
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ # String extensions - will be moved somewhere else. Somewhere..
17
+ class String
18
+ # Converts a string to snake_case.
19
+ def snake_case
20
+ gsub(/::/, '/')
21
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
22
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
23
+ .tr('-', '_')
24
+ .downcase
25
+ end
26
+
27
+ # Converts a string to camelCase.
28
+ def camel_case
29
+ gsub(' ', '_')
30
+ .gsub('-', '_')
31
+ .snake_case
32
+ .split('_')
33
+ .inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }
34
+ .join
35
+ end
36
+
37
+ # Converts a string to PascalCase.
38
+ def pascal_case
39
+ val = camel_case
40
+ val[0] = val[0].capitalize
41
+ val
42
+ end
43
+ end
@@ -0,0 +1,126 @@
1
+ require_relative '../base'
2
+
3
+ module Analytics
4
+ module Serializer
5
+ class ObjC < Base
6
+ # It's named AnalyticsFile instead of Analytics,
7
+ # because it failed to have a class which used the same name as the parent module.
8
+ class AnalyticsFile
9
+ def initialize(src)
10
+ @src = src
11
+ end
12
+
13
+ def save(path)
14
+ @class_name = 'Analytics'
15
+
16
+ @file_name = @class_name + '.h'
17
+ output_path = File.join(path, @file_name)
18
+ File.write(output_path, render_h)
19
+
20
+ @file_name = @class_name + '.m'
21
+ output_path = File.join(path, @file_name)
22
+ File.write(output_path, render_m)
23
+ end
24
+
25
+ private
26
+
27
+ def render_h
28
+ ERB.new(template_h, nil, '-').result(binding)
29
+ end
30
+
31
+ def template_h
32
+ <<~TEMPLATE
33
+ <%= ERB.new(Analytics::Templates.tmpl_at('header.erb')).result(binding) %>
34
+
35
+ #import <Foundation/Foundation.h>
36
+ #import "AnalyticsLogger.h"
37
+ #import "AnalyticsEvent.h"
38
+ #import "AnalyticsScreen.h"
39
+ #import "AnalyticsUserProperty.h"
40
+
41
+ NS_ASSUME_NONNULL_BEGIN
42
+
43
+ __swift_unavailable("This class is only available in ObjC.")
44
+ @interface Analytics : NSObject <AnalyticsLogger>
45
+
46
+ - (instancetype)init NS_UNAVAILABLE;
47
+ + (instancetype)new NS_UNAVAILABLE;
48
+
49
+ - (instancetype)initWithLoggers:(NSArray<id<AnalyticsLogger>> *)loggers;
50
+
51
+ + (instancetype)analyticsWithLoggers:(NSArray<id<AnalyticsLogger>> *)loggers;
52
+
53
+ @end
54
+
55
+ NS_ASSUME_NONNULL_END
56
+ TEMPLATE
57
+ end
58
+
59
+ def render_m
60
+ ERB.new(template_m, nil, '-').result(binding)
61
+ end
62
+
63
+ def template_m
64
+ <<~TEMPLATE
65
+ <%= ERB.new(Analytics::Templates.tmpl_at('header.erb')).result(binding) %>
66
+
67
+ #import "<%= @class_name + '.h' %>"
68
+
69
+ @interface Analytics ()
70
+
71
+ @property (nonatomic, strong, readonly) NSArray<id<AnalyticsLogger>> *loggers;
72
+
73
+ @end
74
+
75
+ @implementation Analytics
76
+
77
+ - (instancetype)initWithLoggers:(NSArray<id<AnalyticsLogger>> *)loggers
78
+ {
79
+ if (self = [super init]) {
80
+ _loggers = loggers;
81
+ }
82
+ return self;
83
+ }
84
+
85
+ + (instancetype)analyticsWithLoggers:(NSArray<id<AnalyticsLogger>> *)loggers
86
+ {
87
+ return [[self alloc] initWithLoggers:loggers];
88
+ }
89
+
90
+ #pragma mark - AnalyticsLogger
91
+
92
+ - (void)setEnabled:(BOOL)enabled
93
+ {
94
+ for (id<AnalyticsLogger> logger in self.loggers) {
95
+ [logger setEnabled:enabled];
96
+ }
97
+ }
98
+
99
+ - (void)logEvent:(AnalyticsEvent *)event
100
+ {
101
+ for (id<AnalyticsLogger> logger in self.loggers) {
102
+ [logger logEvent:event];
103
+ }
104
+ }
105
+
106
+ - (void)trackScreen:(AnalyticsScreen *)screen
107
+ {
108
+ for (id<AnalyticsLogger> logger in self.loggers) {
109
+ [logger trackScreen:screen];
110
+ }
111
+ }
112
+
113
+ - (void)setUserProperty:(AnalyticsUserProperty *)userProperty
114
+ {
115
+ for (id<AnalyticsLogger> logger in self.loggers) {
116
+ [logger setUserProperty:userProperty];
117
+ }
118
+ }
119
+
120
+ @end
121
+ TEMPLATE
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,67 @@
1
+ require_relative '../base'
2
+
3
+ module Analytics
4
+ module Serializer
5
+ class ObjC < Base
6
+ # It's named AnalyticsFile instead of Analytics,
7
+ # because it failed to have a class which used the same name as the parent module.
8
+ class Enums
9
+ def initialize(src)
10
+ @src = src
11
+ end
12
+
13
+ def save(path)
14
+ @class_name = 'AnalyticsEnums'
15
+
16
+ @file_name = @class_name + '.h'
17
+ output_path = File.join(path, @file_name)
18
+ File.write(output_path, render_h)
19
+ end
20
+
21
+ private
22
+
23
+ def render_h
24
+ ERB.new(template_h, nil, '-').result(binding)
25
+ end
26
+
27
+ def template_h
28
+ <<~TEMPLATE
29
+ <%= ERB.new(Analytics::Templates.tmpl_at('header.erb')).result(binding) %>
30
+
31
+ #import <Foundation/Foundation.h>
32
+
33
+ NS_ASSUME_NONNULL_BEGIN
34
+
35
+ #pragma mark - Analytics Event Enums (AE)
36
+ <% Interactor::Event.all_enum_properties(@src['events']).each do |property| -%>
37
+ <% @enum_name = 'AE' + property['name'].pascal_case -%>
38
+
39
+ __swift_unavailable("This enum is only available in ObjC.")
40
+ typedef NS_CLOSED_ENUM(NSInteger, <%= @enum_name %>) {
41
+ <% property['values'].each do |value| -%>
42
+ <%= @enum_name + value.pascal_case %>,
43
+ <% end -%>
44
+ };
45
+ <% end -%>
46
+
47
+
48
+ #pragma mark - Analytics User Property Enums (AUP)
49
+ <% Interactor::UserProperty.all_enum_user_properties(@src['userProperties']).each do |property| -%>
50
+ <% @enum_name = 'AUP' + property['name'].pascal_case -%>
51
+
52
+ __swift_unavailable("This enum is only available in ObjC.")
53
+ typedef NS_CLOSED_ENUM(NSInteger, <%= @enum_name %>) {
54
+ <% property['values'].each do |value| -%>
55
+ <%= @enum_name + value.pascal_case %>,
56
+ <% end -%>
57
+ <%= @enum_name + 'Nil' %>
58
+ };
59
+ <% end -%>
60
+
61
+ NS_ASSUME_NONNULL_END
62
+ TEMPLATE
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end