cocoapods-entitlements-statistics 0.0.1
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 +3 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +134 -0
- data/Rakefile +13 -0
- data/cocoapods-entitlements-statistics.gemspec +27 -0
- data/lib/cocoapods-entitlements-statistics/app_entitlements_statistics/analyze.rb +218 -0
- data/lib/cocoapods-entitlements-statistics/app_entitlements_statistics/core_ext/inflector.rb +35 -0
- data/lib/cocoapods-entitlements-statistics/app_entitlements_statistics/core_ext/try.rb +112 -0
- data/lib/cocoapods-entitlements-statistics/app_entitlements_statistics/entitlements.rb +185 -0
- data/lib/cocoapods-entitlements-statistics/app_entitlements_statistics/extracter.rb +112 -0
- data/lib/cocoapods-entitlements-statistics/app_entitlements_statistics/helper.rb +90 -0
- data/lib/cocoapods-entitlements-statistics/app_entitlements_statistics/info_plist.rb +202 -0
- data/lib/cocoapods-entitlements-statistics/app_entitlements_statistics/mobile_provision.rb +317 -0
- data/lib/cocoapods-entitlements-statistics/gem_version.rb +3 -0
- data/lib/cocoapods-entitlements-statistics/project_info.rb +82 -0
- data/lib/cocoapods-entitlements-statistics.rb +1 -0
- data/lib/cocoapods_plugin.rb +34 -0
- data/spec/command/statistics_spec.rb +12 -0
- data/spec/spec_helper.rb +50 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b18c67b431404139e32390800cee36de2c9443a053716f29832d32d22424954a
|
4
|
+
data.tar.gz: be771fb8171db38589b66f32155b946958d5d34b79b4ace92f8e8e6ea146bbd5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e3596496a3b67f9ae0cb268fe08118f415ca5e9073cea82017bb8176cef932158b44768cfbcebb9b7185b74ff293420895f2221c8aa02d069ebae6dc04305c70
|
7
|
+
data.tar.gz: 9c740e1c280ab955be62a9f51a7678bc07a50c5a28d16049cedf1964283f17ec0c12d87a6cab6710c0540821f6da7a888130264b3e7ca0d72b63a2288951f1a1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in cocoapods-entitlements-statistics.gemspec
|
4
|
+
gemspec
|
5
|
+
gem 'crimp'
|
6
|
+
gem 'zip'
|
7
|
+
|
8
|
+
group :development do
|
9
|
+
gem 'cocoapods'
|
10
|
+
|
11
|
+
gem 'mocha'
|
12
|
+
gem 'bacon'
|
13
|
+
gem 'mocha-on-bacon'
|
14
|
+
gem 'prettybacon'
|
15
|
+
gem 'crimp'
|
16
|
+
gem 'zip'
|
17
|
+
|
18
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2023 bin <tang.bin@olaola.chat>
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# cocoapods-entitlements-statistics
|
2
|
+
|
3
|
+
iOS app 权限统计, 使用cocoapods plugin 触发
|
4
|
+
|
5
|
+
|
6
|
+
## 安装
|
7
|
+
|
8
|
+
```shell
|
9
|
+
# 下载Releases v0.0.1 gem文件,然后进入下载目录执行下面命令
|
10
|
+
sudo gem install cocoapods-entitlements-statistics-0.0.1.gem
|
11
|
+
```
|
12
|
+
|
13
|
+
## 使用
|
14
|
+
|
15
|
+
在 Podfile 文件添加插件引用
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
#引入插件, 可以自定义 report_path, 默认在(iOS)项目根目录
|
19
|
+
plugin 'cocoapods-entitlements-statistics', :report_path => '/Users/rd01/Desktop'
|
20
|
+
#plugin 'cocoapods-entitlements-statistics'
|
21
|
+
|
22
|
+
target 'xxx' do
|
23
|
+
....
|
24
|
+
|
25
|
+
```
|
26
|
+
|
27
|
+
执行 pod install 会输出统计报告路径
|
28
|
+
|
29
|
+
```shell
|
30
|
+
$ pod install
|
31
|
+
.....
|
32
|
+
[!] Entitlements Statistics Report : path/to/entitlements_statistics/analyze_report
|
33
|
+
Pod installation complete! There are 75 dependencies from the Podfile and 116 total pods installed.
|
34
|
+
|
35
|
+
```
|
36
|
+
|
37
|
+
## 说明
|
38
|
+
|
39
|
+
##### 版本权限统计
|
40
|
+
|
41
|
+
默认会在 ~/appInfo-#{app_bundle_id}/ 下为每个版本建立权限统计文件
|
42
|
+
|
43
|
+
```yaml
|
44
|
+
|
45
|
+
├── appInfo-com.ola.chat
|
46
|
+
│ ├── entitlements_5.2.0.yml
|
47
|
+
│ ├── entitlements_5.3.0.yml
|
48
|
+
│ └── entitlements_versions.yml
|
49
|
+
```
|
50
|
+
|
51
|
+
##### 报告内容如下:
|
52
|
+
|
53
|
+
* 权限变化对比分析(增、删、改)
|
54
|
+
* (对比的各)版本权限列表详情
|
55
|
+
|
56
|
+
```yaml
|
57
|
+
|
58
|
+
compared 5.3.0 5.2.0
|
59
|
+
|
60
|
+
modify capabilitys :
|
61
|
+
5.3.0
|
62
|
+
- items ..
|
63
|
+
5.2.0
|
64
|
+
- items ..
|
65
|
+
------------------------------
|
66
|
+
add capabilitys :
|
67
|
+
- items ..
|
68
|
+
------------------------------
|
69
|
+
remove capabilitys :
|
70
|
+
- items ..
|
71
|
+
------------------------------
|
72
|
+
|
73
|
+
5.3.0 entitlements list:
|
74
|
+
...
|
75
|
+
|
76
|
+
5.2.0 entitlements list:
|
77
|
+
...
|
78
|
+
|
79
|
+
```
|
80
|
+
|
81
|
+
## iOS 项目权限
|
82
|
+
|
83
|
+
#### 大致分类 Capabilitys、info.plist(Cocoa Keys)
|
84
|
+
|
85
|
+
* Capabilitys
|
86
|
+
|
87
|
+
> Xcode->Target->Signing&Capabilitys->Capabilitys 下添加和删除 Capabilitys , 这里不是随便添加的,需要首先在苹果开发者后台注册或者声明对应Capabilitys. 更新的Provisioning Profile文件,其中包含Entitlements 字段包含(几乎所有的)已注册Capabilitys, Xcode中添加Capabilitys后会同Profile文件校验。不匹配则无法通过编译签名。
|
88
|
+
|
89
|
+
Supported capabilities (iOS):
|
90
|
+
https://developer.apple.com/help/account/reference/supported-capabilities-ios
|
91
|
+
|
92
|
+
* info.plist (Cocoa Key-Values)
|
93
|
+
* NS{Permissions}UsageDescription
|
94
|
+
> info.plist 中声明使用相机、麦克风、摄像头..等权限的说明
|
95
|
+
|
96
|
+
* 其它权限声明Keys
|
97
|
+
> info.plist 中也会包含一些权限的详细设置、声明,比如屏幕高刷、代理、后台模式...
|
98
|
+
|
99
|
+
info.plist 涉及的所有 Cocoa Keys
|
100
|
+
https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW1
|
101
|
+
|
102
|
+
#### 涉及的文件
|
103
|
+
|
104
|
+
* info.plist
|
105
|
+
* Runner.entitlements Capabilitys被添加后的,Xcode自动创建plist格式文件,里面包含相关Capability的声明、设置
|
106
|
+
* {embedded/xxx-xx-xx}.mobileprovision
|
107
|
+
包括里面的Entitlements字段, 保存一些 Capabilitys被添加后的 声明、设置
|
108
|
+
* Runner.xcodeproj
|
109
|
+
工程文件SystemCapabilities字段包含了一些Capabilitys声明
|
110
|
+
|
111
|
+
相关文档:
|
112
|
+
|
113
|
+
xx.mobileprovision
|
114
|
+
https://developer.apple.com/forums/thread/685723
|
115
|
+
|
116
|
+
Runner.entitlements
|
117
|
+
https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AboutEntitlements.html#//apple_ref/doc/uid/TP40011195-CH1-SW1
|
118
|
+
|
119
|
+
#### 问题
|
120
|
+
|
121
|
+
* IPA包中找不到 In-App Purchase 功能标记
|
122
|
+
|
123
|
+
> 一般Capabilitys添加后会在Runner.entitlements、xx.mobileprovision找到对应key:value声明、设置
|
124
|
+
|
125
|
+
添加 In-App Purchase后, 仅在Runner.xcodeproj中有 "com.apple.InAppPurchase"=>{"enabled"=>"1"} 声明
|
126
|
+
|
127
|
+
* Background Modes 属于 Capabilitys 中的异类
|
128
|
+
|
129
|
+
> 一般Capabilitys添加后会在Runner.entitlements、xx.mobileprovision找到对应key:value声明、设置, 而Background Modes在这里没有任何记录
|
130
|
+
|
131
|
+
Background Modes的声明位置:
|
132
|
+
* Runner.xcodeproj : "com.apple.BackgroundModes"=>{"enabled"=>"1"}
|
133
|
+
* info.plist : "UIBackgroundModes"=>{"audio"、"remote-notification" ...}
|
134
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cocoapods-entitlements-statistics/gem_version.rb'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'cocoapods-entitlements-statistics'
|
8
|
+
spec.version = CocoapodsEntitlementsStatistics::VERSION
|
9
|
+
spec.authors = ['bin']
|
10
|
+
spec.email = ['tang.bin@olaola.chat']
|
11
|
+
spec.description = %q{A short description of cocoapods-entitlements-statistics.}
|
12
|
+
spec.summary = %q{A longer description of cocoapods-entitlements-statistics.}
|
13
|
+
spec.homepage = 'https://github.com/EXAMPLE/cocoapods-entitlements-statistics'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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
|
+
|
21
|
+
spec.add_runtime_dependency "yaml"
|
22
|
+
spec.add_runtime_dependency "crimp"
|
23
|
+
spec.add_dependency 'CFPropertyList', '< 3.1.0', '>= 2.3.4'
|
24
|
+
spec.add_development_dependency 'bundler', '>= 1.12'
|
25
|
+
spec.add_dependency 'rubyzip', '>= 1.2', '< 3.0'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helper"
|
4
|
+
require 'fileutils'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module AppEntitlementsStatistics
|
8
|
+
|
9
|
+
class Analyzer
|
10
|
+
include Helper::Utils
|
11
|
+
|
12
|
+
attr_reader :store_path
|
13
|
+
attr_reader :report_path
|
14
|
+
attr_reader :identifier
|
15
|
+
|
16
|
+
def initialize(identifier,report_path: nil, store_path: nil)
|
17
|
+
@identifier = identifier
|
18
|
+
@report_path = report_path
|
19
|
+
@store_path = store_path
|
20
|
+
end
|
21
|
+
|
22
|
+
def analyze
|
23
|
+
load_version
|
24
|
+
return singleGenerate unless @versions.length > 1
|
25
|
+
pre_version = @versions[1]
|
26
|
+
cur_version = @versions[0]
|
27
|
+
pre_entitlements = load_entitlements_yaml(pre_version)
|
28
|
+
cur_entitlements = load_entitlements_yaml(cur_version)
|
29
|
+
return "" unless pre_entitlements.any?
|
30
|
+
return "" unless cur_entitlements.any?
|
31
|
+
|
32
|
+
capabilities_diff = analyze_capabilities_diff(pre_entitlements,cur_entitlements)
|
33
|
+
usage_descs_diff = analyze_usage_descs_diff(pre_entitlements,cur_entitlements);
|
34
|
+
report_path = generate(capabilities_diff,usage_descs_diff)
|
35
|
+
report_path
|
36
|
+
end
|
37
|
+
|
38
|
+
def singleGenerate
|
39
|
+
return "" unless @versions.length > 0
|
40
|
+
output = "no more versions to compare !! "
|
41
|
+
output += "\n\n\n#{@versions.first} entitlements list: "
|
42
|
+
output += "\n" + read_each_line(@versions.first)
|
43
|
+
output += "\n" + "----------------------------------------"
|
44
|
+
report_path = report_file_name(path: @report_path)
|
45
|
+
File.open(report_path, 'w') { |file|
|
46
|
+
file.write(output)
|
47
|
+
}
|
48
|
+
report_path
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate(capabilities_diff,usage_descs_diff)
|
52
|
+
return unless @versions.length > 1
|
53
|
+
cur_version = @versions[0]
|
54
|
+
pre_version = @versions[1]
|
55
|
+
|
56
|
+
output = "\n" + "compared #{cur_version} #{pre_version}"
|
57
|
+
output += "\n" + capabilities_diff unless capabilities_diff.empty?
|
58
|
+
output += "\n" + usage_descs_diff unless usage_descs_diff.empty?
|
59
|
+
output += "\n\nThere is no difference between the two versions \n\n" unless (!capabilities_diff.empty? && !usage_descs_diff.empty?)
|
60
|
+
# puts output
|
61
|
+
|
62
|
+
output += "\n\n\n#{cur_version} entitlements list: "
|
63
|
+
output += "\n" + read_each_line(cur_version)
|
64
|
+
output += "\n" + "----------------------------------------"
|
65
|
+
output += "\n\n#{pre_version} entitlements list: "
|
66
|
+
output += "\n" + read_each_line(pre_version)
|
67
|
+
output += "\n" + "----------------------------------------"
|
68
|
+
|
69
|
+
report_path = report_file_name(path: @report_path)
|
70
|
+
File.open(report_path, 'w') { |file|
|
71
|
+
file.write(output)
|
72
|
+
}
|
73
|
+
report_path
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_each_line(version)
|
77
|
+
file_name = entitlements_yaml_name(version,@identifier, path: @store_path)
|
78
|
+
file_content = ""
|
79
|
+
File.open(file_name,"r").each_line do |line|
|
80
|
+
file_content += "\n" + line
|
81
|
+
end
|
82
|
+
file_content
|
83
|
+
end
|
84
|
+
|
85
|
+
def load_version
|
86
|
+
yaml_name = versions_yaml_name(@identifier, path: @store_path)
|
87
|
+
if File.exist?(yaml_name)
|
88
|
+
@versions = YAML.load_file(yaml_name)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def load_entitlements_yaml(version)
|
93
|
+
yaml_name = entitlements_yaml_name(version,@identifier, path: @store_path)
|
94
|
+
yaml_content = [ ]
|
95
|
+
if File.exist?(yaml_name)
|
96
|
+
yaml_content = YAML.load_file(yaml_name)
|
97
|
+
end
|
98
|
+
puts "#{yaml_name} not found!!!" unless !yaml_content.empty?
|
99
|
+
yaml_content
|
100
|
+
end
|
101
|
+
|
102
|
+
def analyze_capabilities_diff(pre,cur)
|
103
|
+
output = ""
|
104
|
+
cur_capabilities_summary = cur['Capabilities_Summary']
|
105
|
+
pre_capabilities_summary = pre['Capabilities_Summary']
|
106
|
+
cur_capabilities = cur['Capabilities']
|
107
|
+
pre_capabilities = pre['Capabilities']
|
108
|
+
output = ""
|
109
|
+
# 修改
|
110
|
+
comm_capabilities = cur_capabilities_summary.keys & pre_capabilities_summary.keys
|
111
|
+
modifys = modifys_cur = modifys_pre = ""
|
112
|
+
comm_capabilities.each do |key|
|
113
|
+
if cur_capabilities_summary[key] != pre_capabilities_summary[key]
|
114
|
+
modifys_cur += "\n" + '- ' + key + ': ' + cur_capabilities[key].to_s
|
115
|
+
modifys_pre += "\n" + '- ' + key + ': ' + pre_capabilities[key].to_s
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
modifys += "\n" + @versions[0] unless modifys_cur.empty?
|
120
|
+
modifys += modifys_cur unless modifys_cur.empty?
|
121
|
+
modifys += "\n" + @versions[1] unless modifys_pre.empty?
|
122
|
+
modifys += modifys_pre unless modifys_pre.empty?
|
123
|
+
|
124
|
+
#新增
|
125
|
+
add_capabilities = cur_capabilities_summary.keys - pre_capabilities_summary.keys
|
126
|
+
adds = ""
|
127
|
+
add_capabilities.each do |key|
|
128
|
+
adds += "\n" + '- ' + key + ': ' + cur_capabilities[key].to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
#移除
|
132
|
+
remove_capabilities = pre_capabilities_summary.keys - cur_capabilities_summary.keys
|
133
|
+
removes = ""
|
134
|
+
remove_capabilities.each do |key|
|
135
|
+
removes += "\n" + '- ' + key + ': ' + pre_capabilities[key].to_s
|
136
|
+
end
|
137
|
+
|
138
|
+
if !modifys.empty?
|
139
|
+
output += "\n" + "modify capabilitys : "
|
140
|
+
output += modifys
|
141
|
+
output += "\n------------------------------"
|
142
|
+
end
|
143
|
+
|
144
|
+
if !adds.empty?
|
145
|
+
output += "\n" + "add capabilitys : "
|
146
|
+
output += adds
|
147
|
+
output += "\n------------------------------"
|
148
|
+
end
|
149
|
+
|
150
|
+
if !removes.empty?
|
151
|
+
output += "\n" + "remove capabilitys : "
|
152
|
+
output += removes
|
153
|
+
output += "\n------------------------------"
|
154
|
+
end
|
155
|
+
|
156
|
+
output
|
157
|
+
end
|
158
|
+
|
159
|
+
def analyze_usage_descs_diff(pre,cur)
|
160
|
+
output = ""
|
161
|
+
cur_usage_descs_summary = cur['PermissionsUsageDescription_Summary']
|
162
|
+
pre_usage_descs_summary = pre['PermissionsUsageDescription_Summary']
|
163
|
+
|
164
|
+
cur_usage_descs = cur['PermissionsUsageDescription']
|
165
|
+
pre_usage_descs = pre['PermissionsUsageDescription']
|
166
|
+
|
167
|
+
# 修改
|
168
|
+
comm_usage_descs = cur_usage_descs_summary.keys & pre_usage_descs_summary.keys
|
169
|
+
modifys = modifys_cur = modifys_pre = ""
|
170
|
+
comm_usage_descs.each do |key|
|
171
|
+
if cur_usage_descs_summary[key] != pre_usage_descs_summary[key]
|
172
|
+
modifys_cur += "\n" + '- ' + key + ': ' + cur_usage_descs[key].to_s
|
173
|
+
modifys_pre += "\n" + '- ' + key + ': ' + pre_usage_descs[key].to_s
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
modifys += "\n" + @versions[0] unless modifys_cur.empty?
|
178
|
+
modifys += modifys_cur unless modifys_cur.empty?
|
179
|
+
modifys += "\n" + @versions[1] unless modifys_pre.empty?
|
180
|
+
modifys += modifys_pre unless modifys_pre.empty?
|
181
|
+
|
182
|
+
#新增
|
183
|
+
add_usage_descs = cur_usage_descs_summary.keys - pre_usage_descs_summary.keys
|
184
|
+
adds = ""
|
185
|
+
add_usage_descs.each do |key|
|
186
|
+
adds += "\n" + '- ' + key + ': ' + cur_usage_descs[key].to_s
|
187
|
+
end
|
188
|
+
|
189
|
+
#移除
|
190
|
+
remove_usage_descs = pre_usage_descs_summary.keys - cur_usage_descs_summary.keys
|
191
|
+
removes = ""
|
192
|
+
remove_usage_descs.each do |key|
|
193
|
+
removes += "\n" + '- ' + key + ': ' + pre_usage_descs[key].to_s
|
194
|
+
end
|
195
|
+
|
196
|
+
if !modifys.empty?
|
197
|
+
output += "\n" + "modify permissions usage description : "
|
198
|
+
output += modifys
|
199
|
+
output += "\n------------------------------"
|
200
|
+
end
|
201
|
+
|
202
|
+
if !adds.empty?
|
203
|
+
output += "\n" + "add permissions usage description : "
|
204
|
+
output += adds
|
205
|
+
output += "\n------------------------------"
|
206
|
+
end
|
207
|
+
|
208
|
+
if !removes.empty?
|
209
|
+
output += "\n" + "remove permissions usage description : "
|
210
|
+
output += removes
|
211
|
+
output += "\n------------------------------"
|
212
|
+
end
|
213
|
+
|
214
|
+
output
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppEntitlementsStatistics
|
4
|
+
module Inflector
|
5
|
+
def ai_snakecase
|
6
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
7
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
8
|
+
.tr('-', '_')
|
9
|
+
.gsub(/\s/, '_')
|
10
|
+
.gsub(/__+/, '_')
|
11
|
+
.downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def ai_camelcase(first_letter: :upper, separators: ['-', '_', '\s'])
|
15
|
+
str = dup
|
16
|
+
|
17
|
+
separators.each do |s|
|
18
|
+
str = str.gsub(/(?:#{s}+)([a-z])/) { $1.upcase }
|
19
|
+
end
|
20
|
+
|
21
|
+
case first_letter
|
22
|
+
when :upper, true
|
23
|
+
str = str.gsub(/(\A|\s)([a-z])/) { $1 + $2.upcase }
|
24
|
+
when :lower, false
|
25
|
+
str = str.gsub(/(\A|\s)([A-Z])/) { $1 + $2.downcase }
|
26
|
+
end
|
27
|
+
|
28
|
+
str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class String
|
34
|
+
include AppEntitlementsStatistics::Inflector
|
35
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyrights rails
|
4
|
+
# Copy from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/try.rb
|
5
|
+
|
6
|
+
module AppEntitlementsStatistics
|
7
|
+
module Tryable # :nodoc:
|
8
|
+
##
|
9
|
+
# :method: try
|
10
|
+
#
|
11
|
+
# :call-seq:
|
12
|
+
# try(*a, &b)
|
13
|
+
#
|
14
|
+
# Invokes the public method whose name goes as first argument just like
|
15
|
+
# +public_send+ does, except that if the receiver does not respond to it the
|
16
|
+
# call returns +nil+ rather than raising an exception.
|
17
|
+
#
|
18
|
+
# This method is defined to be able to write
|
19
|
+
#
|
20
|
+
# @person.try(:name)
|
21
|
+
#
|
22
|
+
# instead of
|
23
|
+
#
|
24
|
+
# @person.name if @person
|
25
|
+
#
|
26
|
+
# +try+ calls can be chained:
|
27
|
+
#
|
28
|
+
# @person.try(:spouse).try(:name)
|
29
|
+
#
|
30
|
+
# instead of
|
31
|
+
#
|
32
|
+
# @person.spouse.name if @person && @person.spouse
|
33
|
+
#
|
34
|
+
# +try+ will also return +nil+ if the receiver does not respond to the method:
|
35
|
+
#
|
36
|
+
# @person.try(:non_existing_method) # => nil
|
37
|
+
#
|
38
|
+
# instead of
|
39
|
+
#
|
40
|
+
# @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
|
41
|
+
#
|
42
|
+
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
|
43
|
+
# to the method:
|
44
|
+
#
|
45
|
+
# nil.try(:to_i) # => nil, rather than 0
|
46
|
+
#
|
47
|
+
# Arguments and blocks are forwarded to the method if invoked:
|
48
|
+
#
|
49
|
+
# @posts.try(:each_slice, 2) do |a, b|
|
50
|
+
# ...
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# The number of arguments in the signature must match. If the object responds
|
54
|
+
# to the method the call is attempted and +ArgumentError+ is still raised
|
55
|
+
# in case of argument mismatch.
|
56
|
+
#
|
57
|
+
# If +try+ is called without arguments it yields the receiver to a given
|
58
|
+
# block unless it is +nil+:
|
59
|
+
#
|
60
|
+
# @person.try do |p|
|
61
|
+
# ...
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# You can also call try with a block without accepting an argument, and the block
|
65
|
+
# will be instance_eval'ed instead:
|
66
|
+
#
|
67
|
+
# @person.try { upcase.truncate(50) }
|
68
|
+
#
|
69
|
+
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
|
70
|
+
# with instances of classes that do not have +Object+ among their ancestors,
|
71
|
+
# like direct subclasses of +BasicObject+.
|
72
|
+
def try(method_name = nil, *args, &block)
|
73
|
+
if method_name.nil? && block_given?
|
74
|
+
if block.arity.zero?
|
75
|
+
instance_eval(&b)
|
76
|
+
else
|
77
|
+
yield self
|
78
|
+
end
|
79
|
+
elsif respond_to?(method_name)
|
80
|
+
public_send(method_name, *args, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# :method: try!
|
86
|
+
#
|
87
|
+
# :call-seq:
|
88
|
+
# try!(*a, &b)
|
89
|
+
#
|
90
|
+
# Same as #try, but raises a +NoMethodError+ exception if the receiver is
|
91
|
+
# not +nil+ and does not implement the tried method.
|
92
|
+
#
|
93
|
+
# "a".try!(:upcase) # => "A"
|
94
|
+
# nil.try!(:upcase) # => nil
|
95
|
+
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
|
96
|
+
def try!(method_name = nil, *args, &block)
|
97
|
+
if method_name.nil? && block_given?
|
98
|
+
if block.arity.zero?
|
99
|
+
instance_eval(&block)
|
100
|
+
else
|
101
|
+
yield self
|
102
|
+
end
|
103
|
+
else
|
104
|
+
public_send(method_name, *args, &block)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Object
|
111
|
+
include AppEntitlementsStatistics::Tryable
|
112
|
+
end
|