cocoapods-source-fix 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '08cd70f594d0ebf1ec6b40982fee5ec16e2caf54faf9335ffb953dcd54df929b'
4
+ data.tar.gz: 30347776d6e39f8575937f225754da7b7d7a37de3f86831738ecd1e9500d07bf
5
+ SHA512:
6
+ metadata.gz: 7917a8bec1021d0721de8a7ef524951054493496dbd5480b2b39006c1c0298e342945898404ae8d42f91dea94fa81f16f4d7b73457635e93895cbe93d6f147d9
7
+ data.tar.gz: 6ea0f0d4652d8c6224db28d2ff36fe9d7a54f3245fc00dc99ad849e22ffcfa68760cfd1b120f70f0dd44e43626afb38aa47cd2bd1ef47d8d87ac0d642a4c432f
data/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # cocoapods-source-fix
2
+
3
+ CocoaPods 插件:在 `pod install` 后自动对指定 Pod 的源码/头文件做**文本替换**,用于修复第三方库的编译问题(表达式修复、私有头引入修复、任意文本替换等)。
4
+
5
+ > 原名 `cocoapods-header-fix` 已废弃——插件能力不止「头文件修复」(如 YYText 是表达式修复),故更名为 `cocoapods-source-fix`(源码文本替换修复)。
6
+
7
+ ## 特点
8
+
9
+ - **Podfile 零改动**:通过 monkey-patch `Pod::Installer#install!`(与 `cocoapods-local-override` 相同机制,绕过 HooksManager 白名单),gem 以 `cocoapods-*` 前缀安装后由 CocoaPods 自动加载即生效,**无需在 Podfile 写 `plugin` 指令**
10
+ - **配置独立**:所有替换规则写在一个独立 YAML 文件中(默认 `Podfile 同目录/cocoapods-source-fix.yml`)
11
+ - **6 种目标定位**:相对目录 / 绝对目录 / 相对文件 / 绝对文件 / Pod 模块 / 全局
12
+ - **幂等**:重复执行 `pod install` 不重复破坏文件(内容无变化则跳过写文件)
13
+ - **安全**:写文件前 `chmod 0644`;文件不存在 / glob 无匹配静默跳过
14
+
15
+ ## 安装与集成
16
+
17
+ ### 方式一:Gemfile(推荐,项目用 `bundle exec pod install` 时)
18
+
19
+ 在项目的 Gemfile 中加:
20
+
21
+ ```ruby
22
+ gem 'cocoapods-source-fix', :path => '/Volumes/macc/ZCZY56/Dispatch-dymic/Dispatch-workspace/cocoapods-source-fix'
23
+ ```
24
+
25
+ 然后 `bundle install && bundle exec pod install`。
26
+
27
+ ### 方式二:gem 全局安装(项目直接用 `pod install` 时)
28
+
29
+ ```bash
30
+ cd /Volumes/macc/ZCZY56/Dispatch-dymic/Dispatch-workspace/cocoapods-source-fix
31
+ gem build cocoapods-source-fix.gemspec
32
+ gem install cocoapods-source-fix-0.1.0.gem
33
+ ```
34
+
35
+ 之后 `pod install` 自动生效。
36
+
37
+ ## 配置
38
+
39
+ 创建 `cocoapods-source-fix.yml` 放在 **Podfile 同目录**(或通过环境变量 `COCOAPODS_SOURCE_FIX_CONFIG` 指定路径):
40
+
41
+ ```yaml
42
+ rules:
43
+ # Pod 模块:glob 相对该 Pod 目录
44
+ - name: YYText 表达式修复
45
+ pod: YYText
46
+ glob: 'YYText/Component/YYTextLayout.m'
47
+ pairs:
48
+ - from: 'fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next)'
49
+ to: '(fabs(left - point.y) < fabs(right - point.y)) == (right ? prev : next)'
50
+ - from: 'fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next)'
51
+ to: '(fabs(left - point.x) < fabs(right - point.x)) == (right ? prev : next)'
52
+
53
+ # 相对 Podfile 的目录
54
+ - dir: 'Pods/SomePod'
55
+ glob: '**/*.{h,m}'
56
+ from: 'OLD_TEXT'
57
+ to: 'NEW_TEXT'
58
+
59
+ # 绝对路径目录
60
+ - dir: '/Users/xxx/Source/Pods/SomePod'
61
+ glob: '**/*.m'
62
+ from: 'OLD_TEXT'
63
+ to: 'NEW_TEXT'
64
+
65
+ # 相对 Podfile 的文件
66
+ - file: 'Pods/SomePod/SomePod/Foo.m'
67
+ from: 'OLD_TEXT'
68
+ to: 'NEW_TEXT'
69
+
70
+ # 绝对文件
71
+ - file: '/Users/xxx/Source/Pods/SomePod/SomePod/Foo.m'
72
+ from: 'OLD_TEXT'
73
+ to: 'NEW_TEXT'
74
+
75
+ # 不指定目标:全局替换(glob 相对 Podfile 目录)
76
+ - glob: 'Pods/**/*.{h,m}'
77
+ from: 'OLD_TEXT'
78
+ to: 'NEW_TEXT'
79
+ ```
80
+
81
+ 完整可运行示例见 [config.example.yml](config.example.yml)。
82
+
83
+ ### 规则字段
84
+
85
+ | 字段 | 必填 | 说明 |
86
+ |---|---|---|
87
+ | `name` | 否 | 日志标签,便于识别规则 |
88
+ | `pod` / `dir` / `file` | 六选一 | 目标定位;均不指定则为全局(glob 相对 Podfile 目录) |
89
+ | `glob` | 目录/全局必填 | 文件匹配模式;Pod 模块下相对 Pod 目录,目录/全局下相对目标目录 |
90
+ | `exclude` | 否 | 排除文件/目录,字符串或数组,glob 模式,基准与 `glob` 相同;`'xx/**'` 或 `'**'` 表示排除整棵目录树 |
91
+ | `from` / `to` | 至少一组 | 单组字面量替换(`regex: true` 时按正则) |
92
+ | `pairs` | 否 | 多组替换 `[{from, to, regex?}]` |
93
+ | `regex` | 否 | 默认 `false`(字面量替换,自动转义正则元字符);`true` 时按正则替换 |
94
+ | `enabled` | 否 | 默认 `true`,可临时关闭某条规则 |
95
+
96
+ `exclude` 使用示例:
97
+
98
+ ```yaml
99
+ - name: LFPhoneInfo 头引入方式修复
100
+ pod: LFPhoneInfo
101
+ glob: '**/*.{h,m,mm,swift}'
102
+ from: '#import <LFPhoneDefine.h>'
103
+ to: '#import "LFPhoneDefine.h"'
104
+ exclude:
105
+ - 'LFPhoneInfo/LFPhoneInfo.h' # 排除单个文件
106
+ - 'LFPhoneInfo/Private/**' # 排除整棵目录树
107
+ ```
108
+
109
+ ## 日志输出
110
+
111
+ 日志统一走 `Pod::UI.puts`,格式参照 `cocoapods-user-defined-build-types` 插件(`🛠️ [cocoapods-source-fix] ...` 蓝色)。**不修复也会输出日志**,并区分原因:
112
+
113
+ ```
114
+ 🛠️ [cocoapods-source-fix] 使用配置文件:/path/to/cocoapods-source-fix.yml(3 条规则)
115
+ 🛠️ [cocoapods-source-fix] patching source files...
116
+ 🛠️ [cocoapods-source-fix] processing rule: YYText 表达式修复
117
+ 🛠️ [cocoapods-source-fix] ✅ 已修复文件:/path/to/Pods/YYText/YYText/Component/YYTextLayout.m
118
+ 🛠️ [cocoapods-source-fix] processing rule: AFNetworking 私有头修复
119
+ 🛠️ [cocoapods-source-fix] ⏭️ 跳过(2 个文件内容已是最新,无需修复)
120
+ 🛠️ [cocoapods-source-fix] processing rule: 某条无匹配的规则
121
+ 🛠️ [cocoapods-source-fix] ⏭️ 跳过(无匹配文件)
122
+ 🛠️ [cocoapods-source-fix] finished patching source files (1 fixed, 2 skipped)
123
+ ```
124
+
125
+ 各场景日志:
126
+
127
+ | 场景 | 日志 |
128
+ |------|------|
129
+ | 修复了文件 | `✅ 已修复文件:<path>` |
130
+ | 匹配到文件但内容已是最新(幂等) | `⏭️ 跳过(N 个文件内容已是最新,无需修复)` |
131
+ | 无文件匹配 glob | `⏭️ 跳过(无匹配文件)` |
132
+ | 读取失败 | `⚠️ 读取失败:<path>` |
133
+ | 结束汇总 | `finished patching source files (N fixed, M skipped)` |
134
+
135
+ ### 详细日志(verbose)
136
+
137
+ 默认只输出规则级汇总;配置 `verbose: true`(或环境变量 `COCOAPODS_SOURCE_FIX_VERBOSE=1`)后逐文件输出:
138
+
139
+ ```
140
+ 🛠️ [cocoapods-source-fix] ⏭️ 已是最新:/path/to/Pods/YYText/YYText/Component/YYTextLayout.m
141
+ 🛠️ [cocoapods-source-fix] 🚫 已排除:/path/to/Pods/LFPhoneInfo/LFPhoneInfo/Private/Secret.h
142
+ ```
143
+
144
+ - 每个实际发生替换的文件:`✅ 已修复文件:<path>`
145
+ - 无匹配的规则:`⏭️ 跳过(无匹配)`
146
+ - 配置文件不存在时提示跳过,不中断安装
147
+
148
+ ## 文档导航
149
+
150
+ | 文件 | 说明 |
151
+ |---|---|
152
+ | `REQUIREMENTS.md` | 需求梳理(背景 / 方案 / 验收标准) |
153
+ | `lib/` | 插件实现 |
154
+ | `config.example.yml` | 配置示例(覆盖 6 种目标定位) |
155
+ | `cocoapods-source-fix.gemspec` | gem 定义 |
156
+
157
+ ## 验证
158
+
159
+ ```bash
160
+ pod install # 首次:看到 ✅ 已修复文件
161
+ pod install # 再次:看到 ⏭️ 跳过(幂等验证)
162
+ ```
@@ -0,0 +1,86 @@
1
+ # ============================================================
2
+ # cocoapods-source-fix 配置文件示例
3
+ #
4
+ # 默认位置:Podfile 同目录下的 cocoapods-source-fix.yml
5
+ # 也可用环境变量指定:COCOAPODS_SOURCE_FIX_CONFIG=/path/to/config.yml pod install
6
+ #
7
+ # 目标定位(每条规则六选一,均不指定则为全局替换):
8
+ # pod: '模块名' → 定位 Pod 目录(installer.sandbox.pod_dir),glob 相对该目录
9
+ # dir: '路径' → 目录;绝对路径直接用,相对路径以 Podfile 目录为基准
10
+ # file: '路径' → 文件;绝对路径直接用,相对路径以 Podfile 目录为基准
11
+ # (无以上字段) → 全局;glob 以 Podfile 目录为基准(如 'Pods/**/*.{h,m}')
12
+ #
13
+ # 替换内容(from/to 与 pairs 至少提供一组;均可选 regex: true 走正则):
14
+ # from/to 单组字面量替换
15
+ # pairs 多组替换 [{from, to, regex?}]
16
+ #
17
+ # 排除(可选):exclude 支持字符串或数组,glob 模式,基准与 glob 相同
18
+ # (pod → Pod 目录;dir → 目标目录;全局 → Podfile 目录),命中文件不修复。
19
+ # 以 '/**' 结尾(或 '**')表示排除整个目录树。
20
+ #
21
+ # 详细日志(可选):verbose: true 时逐文件输出「已是最新」「已排除」,
22
+ # 默认 false(只输出规则级汇总)。也可用环境变量 COCOAPODS_SOURCE_FIX_VERBOSE=1 覆盖。
23
+ #
24
+ # 其他字段:name(日志标签,可选)、glob(文件匹配,目录/全局必填)、enabled(默认 true)
25
+ # ============================================================
26
+
27
+ rules:
28
+ # ---------- 示例 1:指定 Pod 模块 + 单文件多组替换(迁移自 YYText 表达式修复) ----------
29
+ - name: YYText 表达式修复
30
+ pod: YYText
31
+ glob: 'YYText/Component/YYTextLayout.m'
32
+ pairs:
33
+ - from: 'fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next)'
34
+ to: '(fabs(left - point.y) < fabs(right - point.y)) == (right ? prev : next)'
35
+ - from: 'fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next)'
36
+ to: '(fabs(left - point.x) < fabs(right - point.x)) == (right ? prev : next)'
37
+
38
+ # ---------- 示例 2:指定 Pod 模块 + 多文件 glob(迁移自 AFNetworking 私有头修复) ----------
39
+ - name: AFNetworking 私有头修复
40
+ pod: AFNetworking
41
+ glob: 'AFNetworking/AF{NetworkReachabilityManager,HTTPSessionManager}.m'
42
+ from: '#import <netinet6/in6.h>'
43
+ to: '#include <sys/socket.h>'
44
+
45
+ # ---------- 示例 3:指定 Pod 模块 + 目录遍历(迁移自 LFPhoneInfo 头引入修复) ----------
46
+ - name: LFPhoneInfo 头引入方式修复
47
+ pod: LFPhoneInfo
48
+ glob: '**/*.{h,m,mm,swift}'
49
+ from: '#import <LFPhoneDefine.h>'
50
+ to: '#import "LFPhoneDefine.h"'
51
+ # 排除示例:不修复 LFPhoneInfo.h 自身(exclude 也可传数组)
52
+ # exclude: 'LFPhoneInfo/LFPhoneInfo.h'
53
+ # 排除整棵目录树:
54
+ # exclude: 'LFPhoneInfo/Private/**'
55
+
56
+ # ---------- 示例 4:相对 Podfile 文件的目录 ----------
57
+ # - name: 相对目录示例
58
+ # dir: 'Pods/SomePod'
59
+ # glob: '**/*.{h,m}'
60
+ # from: 'OLD_TEXT'
61
+ # to: 'NEW_TEXT'
62
+
63
+ # ---------- 示例 5:绝对路径目录 ----------
64
+ # - name: 绝对目录示例
65
+ # dir: '/Users/xxx/Source/Pods/SomePod'
66
+ # glob: '**/*.m'
67
+ # from: 'OLD_TEXT'
68
+ # to: 'NEW_TEXT'
69
+
70
+ # ---------- 示例 6:相对 Podfile 文件的文件 ----------
71
+ # - name: 相对文件示例
72
+ # file: 'Pods/SomePod/SomePod/Foo.m'
73
+ # from: 'OLD_TEXT'
74
+ # to: 'NEW_TEXT'
75
+
76
+ # ---------- 示例 7:绝对文件 ----------
77
+ # - name: 绝对文件示例
78
+ # file: '/Users/xxx/Source/Pods/SomePod/SomePod/Foo.m'
79
+ # from: 'OLD_TEXT'
80
+ # to: 'NEW_TEXT'
81
+
82
+ # ---------- 示例 8:不指定目标(全局替换,glob 相对 Podfile 目录) ----------
83
+ # - name: 全局替换示例
84
+ # glob: 'Pods/**/*.{h,m}'
85
+ # from: 'OLD_TEXT'
86
+ # to: 'NEW_TEXT'
@@ -0,0 +1,39 @@
1
+ require 'yaml'
2
+ require 'cocoapods-source-fix/rule'
3
+
4
+ module CocoapodsSourceFix
5
+ # 读取独立配置文件(默认 <Podfile 目录>/cocoapods-source-fix.yml)
6
+ class Configuration
7
+ CONFIG_FILE_NAME = 'cocoapods-source-fix.yml'
8
+ ENV_KEY = 'COCOAPODS_SOURCE_FIX_CONFIG'
9
+ ENV_VERBOSE_KEY = 'COCOAPODS_SOURCE_FIX_VERBOSE'
10
+
11
+ attr_reader :path, :rules, :verbose
12
+
13
+ # 定位配置文件:环境变量优先,其次 Podfile 同目录默认文件名
14
+ def self.locate(podfile_dir)
15
+ env_path = ENV[ENV_KEY]
16
+ return Pathname.new(env_path) if env_path && File.file?(env_path)
17
+
18
+ default = File.join(podfile_dir, CONFIG_FILE_NAME)
19
+ File.file?(default) ? Pathname.new(default) : nil
20
+ end
21
+
22
+ def initialize(path, podfile_dir:)
23
+ @path = path
24
+ @data = YAML.load_file(path) || {}
25
+ @rules = Array(@data['rules']).map { |raw| Rule.new(raw, base_dir: podfile_dir) }
26
+ @verbose = parse_verbose
27
+ end
28
+
29
+ private
30
+
31
+ # verbose 优先级:环境变量 > 配置文件 > 默认 false
32
+ def parse_verbose
33
+ env = ENV[ENV_VERBOSE_KEY]
34
+ return %w[1 true yes on].include?(env.downcase) if env && !env.empty?
35
+
36
+ @data['verbose'] == true
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ module CocoapodsSourceFix
2
+ PLUGIN_NAME = 'cocoapods-source-fix'
3
+
4
+ # 统一日志出口,参照 cocoapods-user-defined-build-types 风格:
5
+ # Pod::UI.puts "🛠️ [cocoapods-user-defined-build-types] patching build types...".blue
6
+ module Logger
7
+ def self.info(msg)
8
+ line = "🛠️ [#{PLUGIN_NAME}] #{msg}"
9
+ if defined?(Pod) && defined?(Pod::UI)
10
+ begin
11
+ Pod::UI.puts line.blue
12
+ rescue StandardError
13
+ Pod::UI.puts line
14
+ end
15
+ else
16
+ Kernel.puts line
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,72 @@
1
+ require 'pathname'
2
+
3
+ module CocoapodsSourceFix
4
+ # 单条替换规则
5
+ #
6
+ # 目标定位(互斥,均不指定则为全局):
7
+ # pod: 'YYText' → 定位到 installer.sandbox.pod_dir('YYText'),glob 相对该 Pod 目录
8
+ # dir: 'path' → 目录,路径绝对则直接使用,相对则相对 Podfile 目录
9
+ # file: 'path' → 文件,路径绝对则直接使用,相对则相对 Podfile 目录
10
+ # (无以上字段) → 全局,glob 相对 Podfile 目录(如 'Pods/**/*.{h,m}')
11
+ #
12
+ # 替换内容(至少一组):
13
+ # from/to 单组字面量替换;pairs 多组替换;均可选 regex: true 启用正则
14
+ #
15
+ # 排除(可选):
16
+ # exclude: '模式' 或 ['模式1', '模式2'],glob 模式,基准与 glob 相同
17
+ # (pod → Pod 目录;dir → 目标目录;全局 → Podfile 目录),命中文件不修复
18
+ class Rule
19
+ attr_reader :name, :target_type, :target, :glob, :replaces, :enabled, :excludes
20
+
21
+ def initialize(raw, base_dir:)
22
+ @raw = raw || {}
23
+ @base_dir = base_dir
24
+ @name = @raw['name']
25
+ @glob = @raw['glob']
26
+ @enabled = @raw.fetch('enabled', true)
27
+ @excludes = Array(@raw['exclude']).compact.map(&:to_s)
28
+ @replaces = parse_replaces
29
+ resolve_target
30
+ end
31
+
32
+ def enabled?
33
+ @enabled
34
+ end
35
+
36
+ def label
37
+ @name || "target=#{@target_type}#{@target ? "(#{@target})" : ''}"
38
+ end
39
+
40
+ private
41
+
42
+ def parse_replaces
43
+ list = []
44
+ if @raw['from'] && @raw['to']
45
+ list << build_replace(@raw['from'], @raw['to'], @raw.fetch('regex', false))
46
+ end
47
+ Array(@raw['pairs']).each do |pair|
48
+ list << build_replace(pair['from'], pair['to'], pair.fetch('regex', false))
49
+ end
50
+ list
51
+ end
52
+
53
+ def build_replace(from, to, regex)
54
+ { from: from, to: to, regex: regex }
55
+ end
56
+
57
+ def resolve_target
58
+ if @raw['pod']
59
+ @target_type = :pod
60
+ @target = @raw['pod']
61
+ elsif @raw['dir']
62
+ @target_type = :dir
63
+ @target = @raw['dir']
64
+ elsif @raw['file']
65
+ @target_type = :file
66
+ @target = @raw['file']
67
+ else
68
+ @target_type = :global
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,150 @@
1
+ require 'fileutils'
2
+ require 'cocoapods-source-fix/logger'
3
+
4
+ module CocoapodsSourceFix
5
+ # 执行替换:收集目标文件 → 逐文件替换 → 幂等写回 → 日志输出
6
+ class Runner
7
+ def initialize(context, config, podfile_dir: nil)
8
+ @context = context
9
+ @config = config
10
+ @podfile_dir = podfile_dir || default_podfile_dir
11
+ end
12
+
13
+ def run
14
+ Logger.info "patching source files..."
15
+ fixed_total = 0
16
+ skipped_total = 0
17
+ @config.rules.each do |rule|
18
+ next unless rule.enabled?
19
+
20
+ files = collect_files(rule)
21
+ fixed, skipped = process_rule(rule, files)
22
+ fixed_total += fixed
23
+ skipped_total += skipped
24
+ end
25
+ Logger.info "finished patching source files (#{fixed_total} fixed, #{skipped_total} skipped)"
26
+ end
27
+
28
+ private
29
+
30
+ # 按目标类型收集待处理文件(含 exclude 过滤)
31
+ def collect_files(rule)
32
+ base_dir, files = files_for_target(rule)
33
+ apply_excludes(files, rule, base_dir)
34
+ end
35
+
36
+ def files_for_target(rule)
37
+ case rule.target_type
38
+ when :pod
39
+ dir = @context.sandbox.pod_dir(rule.target).to_s
40
+ [dir, glob_files(dir, rule.glob || '**/*')]
41
+ when :dir
42
+ dir = resolve_path(rule.target)
43
+ [dir, glob_files(dir, rule.glob || '**/*')]
44
+ when :file
45
+ path = resolve_path(rule.target)
46
+ [File.dirname(path), File.file?(path) ? [path] : []]
47
+ when :global
48
+ [@podfile_dir, glob_files(@podfile_dir, rule.glob || '**/*')]
49
+ end
50
+ end
51
+
52
+ # exclude 为 glob 模式,基准与 glob 相同(相对规则目标目录)。
53
+ # 注意:模式以 /** 结尾时 fnmatch 只匹配一层,追加 /**/* 变体覆盖整棵目录树。
54
+ def apply_excludes(files, rule, base_dir)
55
+ return files if rule.excludes.empty?
56
+
57
+ patterns = rule.excludes.flat_map do |pattern|
58
+ if pattern == '**' || pattern.end_with?('/**')
59
+ [pattern, File.join(pattern, '*')]
60
+ else
61
+ [pattern]
62
+ end
63
+ end
64
+ flags = File::FNM_PATHNAME | File::FNM_EXTGLOB
65
+
66
+ files.reject do |file|
67
+ rel = file.delete_prefix(base_dir + '/')
68
+ hit = patterns.any? { |p| File.fnmatch(p, rel, flags) }
69
+ Logger.info " 🚫 已排除:#{file}" if hit && @config.verbose
70
+ hit
71
+ end
72
+ end
73
+
74
+ # 兜底:优先全局 Pod::Config 中的 Podfile,其次 sandbox_root 上级,最后 Dir.pwd
75
+ def default_podfile_dir
76
+ if defined?(Pod::Config) && Pod::Config.instance.respond_to?(:podfile)
77
+ pf = Pod::Config.instance.podfile
78
+ if pf && pf.respond_to?(:defined_in_file) && pf.defined_in_file
79
+ return File.dirname(pf.defined_in_file.to_s)
80
+ end
81
+ end
82
+ if @context.respond_to?(:sandbox_root) && @context.sandbox_root
83
+ return File.dirname(@context.sandbox_root)
84
+ end
85
+ Dir.pwd
86
+ end
87
+
88
+ def glob_files(dir, glob)
89
+ return [] unless File.directory?(dir)
90
+
91
+ Dir.glob(File.join(dir, glob)).select { |f| File.file?(f) }
92
+ end
93
+
94
+ # 绝对路径直接使用;相对路径以 Podfile 目录为基准
95
+ def resolve_path(path)
96
+ p = Pathname.new(path)
97
+ p.absolute? ? path : File.expand_path(path, @podfile_dir)
98
+ end
99
+
100
+ # 返回 [fixed, skipped]:fixed 为实际写入的文件数,skipped 为内容已是最新(无变化)的文件数
101
+ def process_rule(rule, files)
102
+ Logger.info " processing rule: #{rule.label}"
103
+ if files.empty?
104
+ Logger.info " ⏭️ 跳过(无匹配文件)"
105
+ return [0, 0]
106
+ end
107
+
108
+ touched = 0
109
+ skipped = 0
110
+ files.each do |file|
111
+ begin
112
+ content = File.read(file)
113
+ rescue StandardError => e
114
+ Logger.info " ⚠️ 读取失败:#{file}(#{e.message})"
115
+ next
116
+ end
117
+
118
+ new_content = rule.replaces.reduce(content) { |acc, rep| apply_replace(acc, rep) }
119
+ if new_content == content # 幂等:无变化跳过写文件
120
+ skipped += 1
121
+ Logger.info " ⏭️ 已是最新:#{file}" if @config.verbose
122
+ next
123
+ end
124
+
125
+ FileUtils.chmod 0644, file
126
+ File.write(file, new_content)
127
+ Logger.info " ✅ 已修复文件:#{file}"
128
+ touched += 1
129
+ end
130
+ if touched.zero?
131
+ Logger.info " ⏭️ 跳过(#{skipped} 个文件内容已是最新,无需修复)"
132
+ elsif skipped.positive?
133
+ Logger.info " ✅ 修复完成(#{touched} 个文件;#{skipped} 个已是最新)"
134
+ end
135
+ [touched, skipped]
136
+ end
137
+
138
+ def apply_replace(content, rep)
139
+ # 必须显式构造 Regexp:Ruby 3.x 下 gsub(字符串) 不会把字符串当正则源码解析
140
+ pattern = if rep[:regex]
141
+ Regexp.new(rep[:from])
142
+ else
143
+ # 默认字面量替换,避免被当作正则元字符
144
+ Regexp.new(Regexp.escape(rep[:from]))
145
+ end
146
+ # block 形式传 replacement,避免 \1 等被解释为捕获组引用
147
+ content.gsub(pattern) { rep[:to] }
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,3 @@
1
+ module CocoapodsSourceFix
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,70 @@
1
+ require 'ostruct'
2
+ require 'cocoapods-source-fix/version'
3
+ require 'cocoapods-source-fix/logger'
4
+ require 'cocoapods-source-fix/configuration'
5
+ require 'cocoapods-source-fix/runner'
6
+
7
+ module CocoapodsSourceFix
8
+ class << self
9
+ # install! 完成后执行源码替换(加载即生效,无需 Podfile 声明 plugin)
10
+ def run_post_install(installer)
11
+ podfile_dir = podfile_dir_from(installer)
12
+ config_path = Configuration.locate(podfile_dir)
13
+ unless config_path
14
+ Logger.info "未找到配置文件(#{Configuration::ENV_KEY} 或 #{Configuration::CONFIG_FILE_NAME}),跳过替换"
15
+ return
16
+ end
17
+
18
+ config = Configuration.new(config_path, podfile_dir: podfile_dir)
19
+ Logger.info "使用配置文件:#{config_path}(#{config.rules.size} 条规则)"
20
+ # Runner 依赖 sandbox 定位 pod 目录,从 installer 实例提取并包装成轻量 context
21
+ context = OpenStruct.new(
22
+ sandbox: installer.sandbox,
23
+ sandbox_root: installer.sandbox.root.to_s
24
+ )
25
+ Runner.new(context, config, podfile_dir: podfile_dir).run
26
+ end
27
+
28
+ # 安装 monkey-patch:拦截 Pod::Installer#install!,在其执行完成后运行替换。
29
+ # 与 cocoapods-local-override 相同的机制——绕过 HooksManager 的 whitelist
30
+ # (whitelist 只放行 Podfile 中 plugin 声明的 hook),因此无需修改 Podfile。
31
+ def apply_installer_hook!
32
+ return if @installer_hook_applied
33
+ @installer_hook_applied = true
34
+
35
+ Pod::Installer.class_eval do
36
+ unless method_defined?(:source_fix_original_install!)
37
+ alias_method :source_fix_original_install!, :install!
38
+
39
+ define_method :install! do |*args|
40
+ source_fix_original_install!(*args)
41
+ CocoapodsSourceFix.run_post_install(self)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def podfile_dir_from(installer)
50
+ # 1. 全局 Pod::Config 中已加载的 Podfile(最可靠,含 defined_in_file)
51
+ if defined?(Pod::Config) && Pod::Config.instance.respond_to?(:podfile)
52
+ pf = Pod::Config.instance.podfile
53
+ if pf && pf.respond_to?(:defined_in_file) && pf.defined_in_file
54
+ return File.dirname(pf.defined_in_file.to_s)
55
+ end
56
+ end
57
+ # 2. 兜底:sandbox.root(默认 <Podfile 目录>/Pods)的上级目录
58
+ if installer.respond_to?(:sandbox) && installer.sandbox &&
59
+ installer.sandbox.respond_to?(:root) && installer.sandbox.root
60
+ return File.dirname(installer.sandbox.root.to_s)
61
+ end
62
+ # 3. 最终兜底:当前工作目录
63
+ Dir.pwd
64
+ end
65
+ end
66
+ end
67
+
68
+ # 说明:CocoaPods 启动时自动 require gem 内的 lib/cocoapods_plugin.rb(无需 Podfile
69
+ # 声明),此处直接 monkey-patch Pod::Installer#install!,加载即生效。
70
+ CocoapodsSourceFix.apply_installer_hook!
@@ -0,0 +1,6 @@
1
+ # CocoaPods 插件发现入口
2
+ #
3
+ # CocoaPods 启动时自动扫描所有已安装 gem 中的 `cocoapods_plugin.rb` 并 require,
4
+ # 无需在 Podfile 中写 plugin 指令(与 cocoapods-local-override 相同机制)。
5
+ # 加载主入口,注册 :post_install hook。
6
+ require 'cocoapods-source-fix'
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cocoapods-source-fix
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ZCW
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cocoapods
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ description: Automatically apply text replacements (expression fixes, private header
28
+ import fixes, etc.) to Pod sources after `pod install`, configured via a standalone
29
+ YAML file. No Podfile modification required.
30
+ email:
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - README.md
36
+ - config.example.yml
37
+ - lib/cocoapods-source-fix.rb
38
+ - lib/cocoapods-source-fix/configuration.rb
39
+ - lib/cocoapods-source-fix/logger.rb
40
+ - lib/cocoapods-source-fix/rule.rb
41
+ - lib/cocoapods-source-fix/runner.rb
42
+ - lib/cocoapods-source-fix/version.rb
43
+ - lib/cocoapods_plugin.rb
44
+ homepage: https://github.com/charleszhao888888/cocoapods-source-fix
45
+ licenses:
46
+ - MIT
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '2.6'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.3.3
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: CocoaPods plugin to apply text replacements to pod sources after pod install
67
+ test_files: []