elect 1.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/LICENSE +21 -0
- data/README.md +2 -0
- data/elect.gemspec +38 -0
- data/exe/elect +6 -0
- data/lib/elect/cleaner.rb +49 -0
- data/lib/elect/cli.rb +110 -0
- data/lib/elect/pattern.rb +97 -0
- data/lib/elect/processor.rb +140 -0
- data/lib/elect/version.rb +6 -0
- data/lib/elect.rb +29 -0
- metadata +98 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4f7352a40c19964176a0d00add7f035879262fd8188804c1da6e1bd43b85b9d6
|
|
4
|
+
data.tar.gz: 46a8cce817616178a6a4c016975e6f0a494e38136c84100bc7fb0c73b2eb638a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 20c1005f70e2e588f547cf32adee6fbe67ddb07c7b9e802ef32051cadda62f1c3f8825b3f40d6e4df10f89209f188bd05c6dbc781fcd6cafa7653881d40c2a50
|
|
7
|
+
data.tar.gz: 6a677ae493ac9819e0797df3bde53761d14818ab61f0fc4c182a4ed730c4d1d217965d5ad233245af2ebe99fa15dba125c8924be38fa59342d85e02881e5951d
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Frampt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/elect.gemspec
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require "elect/version"
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = "elect"
|
|
9
|
+
spec.version = Elect::VERSION
|
|
10
|
+
spec.authors = ["arc-v"]
|
|
11
|
+
spec.email = ["arc-v@example.com"]
|
|
12
|
+
|
|
13
|
+
spec.summary = "AIGC 水印干掉器 — 从代码/文档中移除 AIGC 水印块"
|
|
14
|
+
spec.description = <<~DESC
|
|
15
|
+
elect 是一个 Ruby gem,用于从代码和文档文件中检测并移除 AIGC 水印块。
|
|
16
|
+
支持目录/文件级扫描,提供 dry-run、自定义扩展名、目录排除等功能。
|
|
17
|
+
零运行时依赖,纯标准库实现。
|
|
18
|
+
DESC
|
|
19
|
+
spec.homepage = "https://github.com/arc-v/elect"
|
|
20
|
+
spec.license = "MIT"
|
|
21
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
|
22
|
+
|
|
23
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
24
|
+
spec.metadata["source_code_uri"] = "https://github.com/arc-v/elect"
|
|
25
|
+
|
|
26
|
+
# 文件列表
|
|
27
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
28
|
+
Dir["lib/**/*", "exe/*", "README.md", "LICENSE", "elect.gemspec"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
spec.bindir = "exe"
|
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
33
|
+
spec.require_paths = ["lib"]
|
|
34
|
+
|
|
35
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
36
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
37
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
38
|
+
end
|
data/exe/elect
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "pattern"
|
|
5
|
+
|
|
6
|
+
module Elect
|
|
7
|
+
# 清理器:接收文本,移除 AIGC 水印块。
|
|
8
|
+
#
|
|
9
|
+
module Cleaner
|
|
10
|
+
# 从文本中移除所有水印块。
|
|
11
|
+
#
|
|
12
|
+
# 策略:
|
|
13
|
+
# 1. 用 Pattern::REGEX 全局替换为空串
|
|
14
|
+
# 2. 清理因移除产生的连续空行(>2 连续空行压缩为 2)
|
|
15
|
+
# 3. 清理文件首尾多余空白
|
|
16
|
+
#
|
|
17
|
+
# 返回 [清理后文本, 移除数量]
|
|
18
|
+
#
|
|
19
|
+
def self.clean(text)
|
|
20
|
+
n = Pattern.count(text)
|
|
21
|
+
return [text, 0] if n.zero?
|
|
22
|
+
|
|
23
|
+
cleaned = text.gsub(Pattern::REGEX, "")
|
|
24
|
+
|
|
25
|
+
# 压缩连续空行(保留最多 2 换行 = 1 空行),兼容 CRLF
|
|
26
|
+
cleaned.gsub!(/(?:\r\n|\n){3,}/, "\n\n")
|
|
27
|
+
|
|
28
|
+
# 移除首尾多余空白
|
|
29
|
+
cleaned.strip!
|
|
30
|
+
|
|
31
|
+
# 确保文件结尾有且仅有一个换行
|
|
32
|
+
cleaned << "\n" unless cleaned.empty?
|
|
33
|
+
|
|
34
|
+
[cleaned, n]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# 仅检测不修改,返回水印数量
|
|
38
|
+
#
|
|
39
|
+
def self.detect(text)
|
|
40
|
+
Pattern.count(text)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# 判断文本是否含水印
|
|
44
|
+
#
|
|
45
|
+
def self.watermarked?(text)
|
|
46
|
+
Pattern.watermarked?(text)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/elect/cli.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "pathname"
|
|
6
|
+
|
|
7
|
+
module Elect
|
|
8
|
+
# 命令行接口
|
|
9
|
+
#
|
|
10
|
+
# 用法:
|
|
11
|
+
# elect [选项] <文件或目录...>
|
|
12
|
+
#
|
|
13
|
+
# 选项:
|
|
14
|
+
# -d, --dry-run 仅检测,不修改文件
|
|
15
|
+
# -v, --verbose 详细输出
|
|
16
|
+
# -e, --exts x,y,z 自定义扩展名(逗号分隔)
|
|
17
|
+
# -x, --exclude a,b 排除目录名(逗号分隔)
|
|
18
|
+
# -h, --help 显示帮助
|
|
19
|
+
# -V, --version 显示版本
|
|
20
|
+
#
|
|
21
|
+
module CLI
|
|
22
|
+
def self.run(argv = ARGV)
|
|
23
|
+
opts = {
|
|
24
|
+
dry_run: false,
|
|
25
|
+
verbose: false,
|
|
26
|
+
exts: nil,
|
|
27
|
+
exclude: nil,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
parser = build_parser(opts)
|
|
31
|
+
paths = parser.parse(argv)
|
|
32
|
+
|
|
33
|
+
if paths.empty?
|
|
34
|
+
warn parser
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
all_results = []
|
|
39
|
+
paths.each do |p|
|
|
40
|
+
path = Pathname.new(p)
|
|
41
|
+
if path.directory?
|
|
42
|
+
results = Processor.process_dir(
|
|
43
|
+
path,
|
|
44
|
+
dry_run: opts[:dry_run],
|
|
45
|
+
verbose: opts[:verbose],
|
|
46
|
+
exts: opts[:exts],
|
|
47
|
+
exclude: opts[:exclude],
|
|
48
|
+
)
|
|
49
|
+
all_results.concat(results)
|
|
50
|
+
elsif path.file?
|
|
51
|
+
r = Processor.process_file(path, dry_run: opts[:dry_run], verbose: opts[:verbose])
|
|
52
|
+
all_results << r
|
|
53
|
+
else
|
|
54
|
+
warn "[elect] 路径不存在: #{p}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Processor.report(all_results)
|
|
59
|
+
|
|
60
|
+
# 有错误时退出码非零
|
|
61
|
+
exit 1 if all_results.any? { |r| r.status == :error }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.build_parser(opts)
|
|
65
|
+
OptionParser.new do |o|
|
|
66
|
+
o.banner = <<~BANNER
|
|
67
|
+
elect — AIGC 水印干掉器
|
|
68
|
+
|
|
69
|
+
用法:
|
|
70
|
+
elect [选项] <文件或目录...>
|
|
71
|
+
|
|
72
|
+
示例:
|
|
73
|
+
elect ./src # 清理 src 目录下所有水印
|
|
74
|
+
elect -d ./docs # 仅检测不修改(dry-run)
|
|
75
|
+
elect -v main.py # 清理单个文件,详细输出
|
|
76
|
+
elect -e md,txt ./docs # 只扫描 md 和 txt
|
|
77
|
+
elect -x vendor,tmp ./src # 排除 vendor 和 tmp 目录
|
|
78
|
+
|
|
79
|
+
选项:
|
|
80
|
+
BANNER
|
|
81
|
+
|
|
82
|
+
o.on("-d", "--dry-run", "仅检测水印,不修改文件") do
|
|
83
|
+
opts[:dry_run] = true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
o.on("-v", "--verbose", "详细输出") do
|
|
87
|
+
opts[:verbose] = true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
o.on("-e", "--exts x,y,z", Array, "自定义扫描扩展名(逗号分隔)") do |v|
|
|
91
|
+
opts[:exts] = v
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
o.on("-x", "--exclude a,b", Array, "排除目录名(逗号分隔)") do |v|
|
|
95
|
+
opts[:exclude] = v
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
o.on("-h", "--help", "显示帮助") do
|
|
99
|
+
puts o
|
|
100
|
+
exit
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
o.on("-V", "--version", "显示版本") do
|
|
104
|
+
puts "elect #{Elect::VERSION}"
|
|
105
|
+
exit
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Elect
|
|
5
|
+
# 水印模式定义与匹配逻辑。
|
|
6
|
+
#
|
|
7
|
+
# AIGC 水印结构如下(YAML front-matter 风格,用三横线包裹):
|
|
8
|
+
#
|
|
9
|
+
# ---
|
|
10
|
+
# AIGC:
|
|
11
|
+
# ContentProducer: '001191110102xxxx5U9H0F10002'
|
|
12
|
+
# ContentPropagator: '001191110102xxxx5U9H0F10002'
|
|
13
|
+
# Label: '1'
|
|
14
|
+
# ProduceID: '7e123539-xxxx-4617-9fdb-d10422064fe9'
|
|
15
|
+
# PropagateID: '7e123539-xxxx-4617-9fdb-d10422064fe9'
|
|
16
|
+
# ReservedCode1: '718161f2-xxxx-4528-9e76-b071818e46dd'
|
|
17
|
+
# ReservedCode2: '718161f2-xxxx-4528-9e76-b071818e46dd'
|
|
18
|
+
# ---
|
|
19
|
+
#
|
|
20
|
+
# 字段值的内容是动态的,因此以结构模式匹配,而非硬编码值。
|
|
21
|
+
#
|
|
22
|
+
module Pattern
|
|
23
|
+
# 水印块匹配的正则。
|
|
24
|
+
#
|
|
25
|
+
# 约束:
|
|
26
|
+
# - 以 `---` 开头(行首)
|
|
27
|
+
# - 后面跟着 `AIGC:` 键
|
|
28
|
+
# - 包含若干缩进的子字段行
|
|
29
|
+
# - 以 `---` 结尾
|
|
30
|
+
# - 支持前后空白行
|
|
31
|
+
#
|
|
32
|
+
# 允许的子字段名(不限制值内容):
|
|
33
|
+
# ContentProducer / ContentPropagator / Label /
|
|
34
|
+
# ProduceID / PropagateID / ReservedCode1 / ReservedCode2
|
|
35
|
+
#
|
|
36
|
+
# 对非列表中字段同样宽容——只要 AIGC: dict 下有任意子键即匹配,
|
|
37
|
+
# 这样未来新增字段也可被自动识别。
|
|
38
|
+
#
|
|
39
|
+
# 换行兼容:用 \r?\n 同时匹配 LF 和 CRLF。
|
|
40
|
+
#
|
|
41
|
+
REGEX = /
|
|
42
|
+
(?:
|
|
43
|
+
^---[ \t]*\r?\n
|
|
44
|
+
[ \t]*AIGC:[ \t]*\r?\n
|
|
45
|
+
(?:[ \t]+[A-Za-z_][A-Za-z0-9_]*:[ \t]*[^\r\n]*\r?\n)+
|
|
46
|
+
[ \t]*---[ \t]*\r?\n?
|
|
47
|
+
)
|
|
48
|
+
/x.freeze
|
|
49
|
+
|
|
50
|
+
# 匹配水印块,返回 MatchData 或 nil
|
|
51
|
+
#
|
|
52
|
+
def self.match(text)
|
|
53
|
+
s = sanitize(text)
|
|
54
|
+
REGEX.match(s)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 判断是否包含水印
|
|
58
|
+
#
|
|
59
|
+
def self.watermarked?(text)
|
|
60
|
+
s = sanitize(text)
|
|
61
|
+
REGEX.match?(s)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# 统计水印块数量
|
|
65
|
+
#
|
|
66
|
+
def self.count(text)
|
|
67
|
+
s = sanitize(text)
|
|
68
|
+
s.scan(REGEX).size
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# 提取所有水印块的文本(供调试用)
|
|
72
|
+
#
|
|
73
|
+
def self.extract(text)
|
|
74
|
+
s = sanitize(text)
|
|
75
|
+
s.scan(REGEX)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# 对非 UTF-8 合法字节做替换,避免 scan/match 抛 ArgumentError。
|
|
79
|
+
# 如果传入的 String 编码不是 utf-8,先强制转码再清洗非法字节。
|
|
80
|
+
#
|
|
81
|
+
def self.sanitize(text)
|
|
82
|
+
return text if text.encoding == Encoding::UTF_8 && text.valid_encoding?
|
|
83
|
+
if text.encoding != Encoding::UTF_8
|
|
84
|
+
begin
|
|
85
|
+
text = text.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
86
|
+
return text if text.valid_encoding?
|
|
87
|
+
rescue Encoding::UndefinedConversionError, Encoding::ConverterNotFoundError
|
|
88
|
+
# 走 fallback
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
# encoding 是 utf-8 但有非法字节,或转码失败:直接 force + clean
|
|
92
|
+
text.dup.force_encoding(Encoding::UTF_8).encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "?")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private_class_method :sanitize
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "cleaner"
|
|
5
|
+
require "pathname"
|
|
6
|
+
|
|
7
|
+
module Elect
|
|
8
|
+
# 文件与目录扫描处理器。
|
|
9
|
+
#
|
|
10
|
+
# 职责:
|
|
11
|
+
# - 遍历目录树,找出所有文本类文件
|
|
12
|
+
# - 逐文件读取、检测水印、按选项清理或仅报告
|
|
13
|
+
# - 输出统计信息
|
|
14
|
+
#
|
|
15
|
+
module Processor
|
|
16
|
+
# 默认扫描的文件扩展名
|
|
17
|
+
#
|
|
18
|
+
DEFAULT_EXTS = %w[
|
|
19
|
+
.md .txt .py .rb .js .ts .tsx .jsx .java .go .rs
|
|
20
|
+
.c .h .cpp .hpp .cc .cs .php .swift .kt .scala
|
|
21
|
+
.html .css .scss .less .vue .svelte
|
|
22
|
+
.json .yaml .yml .toml .xml .sql .sh .bat .ps1
|
|
23
|
+
.tex .rst .adoc .org .csv .tsv .ini .conf .cfg
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
# 文件结果
|
|
27
|
+
#
|
|
28
|
+
Result = Struct.new(:path, :removed, :status, keyword_init: true)
|
|
29
|
+
|
|
30
|
+
# 扫描并处理单个文件。
|
|
31
|
+
#
|
|
32
|
+
# 参数:
|
|
33
|
+
# path - 文件路径(String 或 Pathname)
|
|
34
|
+
# dry_run - true 时仅检测不改写文件
|
|
35
|
+
# verbose - true 时输出详细日志
|
|
36
|
+
#
|
|
37
|
+
# 返回: Result
|
|
38
|
+
#
|
|
39
|
+
def self.process_file(path, dry_run: false, verbose: false)
|
|
40
|
+
p = Pathname.new(path)
|
|
41
|
+
result = Result.new(path: p.to_s, removed: 0, status: :skip)
|
|
42
|
+
|
|
43
|
+
unless p.file?
|
|
44
|
+
result.status = :not_found
|
|
45
|
+
return result
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 统一走二进制读取 + 编码强制转换,避免非 UTF-8 文件触发 ArgumentError
|
|
49
|
+
raw = begin
|
|
50
|
+
data = File.binread(p.to_s)
|
|
51
|
+
data = data.force_encoding(Encoding::UTF_8)
|
|
52
|
+
unless data.valid_encoding?
|
|
53
|
+
data = data.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "?")
|
|
54
|
+
end
|
|
55
|
+
data
|
|
56
|
+
rescue => e
|
|
57
|
+
warn "[elect] 跳过 #{p}: #{e.message}" if verbose
|
|
58
|
+
result.status = :error
|
|
59
|
+
return result
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
n = Cleaner.detect(raw)
|
|
63
|
+
if n.zero?
|
|
64
|
+
result.status = :clean
|
|
65
|
+
return result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if dry_run
|
|
69
|
+
result.removed = n
|
|
70
|
+
result.status = :found
|
|
71
|
+
puts " [DRY] #{p} — 发现 #{n} 处水印"
|
|
72
|
+
return result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
cleaned, removed = Cleaner.clean(raw)
|
|
76
|
+
p.write(cleaned, encoding: "utf-8")
|
|
77
|
+
result.removed = removed
|
|
78
|
+
result.status = :cleaned
|
|
79
|
+
puts " [OK] #{p} — 移除 #{removed} 处水印" if verbose
|
|
80
|
+
result
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# 扫描并处理目录。
|
|
84
|
+
#
|
|
85
|
+
# 参数:
|
|
86
|
+
# dir - 目录路径
|
|
87
|
+
# dry_run - true 时仅检测不修改
|
|
88
|
+
# verbose - true 时输出详细日志
|
|
89
|
+
# exts - 自定义扩展名数组(nil 用 DEFAULT_EXTS)
|
|
90
|
+
# exclude - 排除的目录名数组(如 ["node_modules", ".git"])
|
|
91
|
+
#
|
|
92
|
+
# 返回: [Result, ...]
|
|
93
|
+
#
|
|
94
|
+
def self.process_dir(dir, dry_run: false, verbose: false, exts: nil, exclude: nil)
|
|
95
|
+
d = Pathname.new(dir)
|
|
96
|
+
exts = (exts || DEFAULT_EXTS).map { |e| e.start_with?(".") ? e : ".#{e}" }
|
|
97
|
+
exclude_set = (exclude || %w[node_modules .git .svn __pycache__ dist build]).to_set
|
|
98
|
+
|
|
99
|
+
results = []
|
|
100
|
+
d.find do |path|
|
|
101
|
+
next if path == d
|
|
102
|
+
if path.directory?
|
|
103
|
+
next if exclude_set.include?(path.basename.to_s)
|
|
104
|
+
next
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
next unless exts.include?(path.extname.downcase)
|
|
108
|
+
|
|
109
|
+
r = process_file(path, dry_run: dry_run, verbose: verbose)
|
|
110
|
+
results << r
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
results
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# 打印汇总报告
|
|
117
|
+
#
|
|
118
|
+
def self.report(results)
|
|
119
|
+
total = results.size
|
|
120
|
+
found = results.count { |r| r.status == :found }
|
|
121
|
+
cleaned = results.count { |r| r.status == :cleaned }
|
|
122
|
+
clean = results.count { |r| r.status == :clean }
|
|
123
|
+
skipped = results.count { |r| r.status == :skip }
|
|
124
|
+
errors = results.count { |r| r.status == :error }
|
|
125
|
+
total_rm = results.sum { |r| r.removed }
|
|
126
|
+
|
|
127
|
+
puts
|
|
128
|
+
puts "=" * 50
|
|
129
|
+
puts " 扫描完毕"
|
|
130
|
+
puts "=" * 50
|
|
131
|
+
puts " 扫描文件 : #{total}"
|
|
132
|
+
puts " 发现水印 : #{found + cleaned} 个文件"
|
|
133
|
+
puts " 已清理 : #{cleaned} 个文件 (共移除 #{total_rm} 处水印)"
|
|
134
|
+
puts " 无水印 : #{clean} 个文件"
|
|
135
|
+
puts " 跳过 : #{skipped}"
|
|
136
|
+
puts " 错误 : #{errors}" if errors.positive?
|
|
137
|
+
puts "=" * 50
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/elect.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "elect/version"
|
|
5
|
+
require_relative "elect/pattern"
|
|
6
|
+
require_relative "elect/cleaner"
|
|
7
|
+
require_relative "elect/processor"
|
|
8
|
+
require_relative "elect/cli"
|
|
9
|
+
|
|
10
|
+
# elect — AIGC 水印干掉器
|
|
11
|
+
#
|
|
12
|
+
# 从代码/文档文件中移除天翼星辰超级智能体等工具注入的 AIGC 水印块。
|
|
13
|
+
#
|
|
14
|
+
# 快速使用:
|
|
15
|
+
#
|
|
16
|
+
# require 'elect'
|
|
17
|
+
#
|
|
18
|
+
# # Ruby 代码中直接调用
|
|
19
|
+
# cleaned, n = Elect::Cleaner.clean(text)
|
|
20
|
+
# puts "移除 #{n} 处水印" if n > 0
|
|
21
|
+
#
|
|
22
|
+
# 命令行:
|
|
23
|
+
#
|
|
24
|
+
# elect ./src # 清理目录
|
|
25
|
+
# elect -d ./docs # 仅检测
|
|
26
|
+
# elect -v main.py # 单文件
|
|
27
|
+
#
|
|
28
|
+
module Elect
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: elect
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- arc-v
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bundler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.0'
|
|
54
|
+
description: |
|
|
55
|
+
elect 是一个 Ruby gem,用于从代码和文档文件中检测并移除 AIGC 水印块。
|
|
56
|
+
支持目录/文件级扫描,提供 dry-run、自定义扩展名、目录排除等功能。
|
|
57
|
+
零运行时依赖,纯标准库实现。
|
|
58
|
+
email:
|
|
59
|
+
- arc-v@example.com
|
|
60
|
+
executables:
|
|
61
|
+
- elect
|
|
62
|
+
extensions: []
|
|
63
|
+
extra_rdoc_files: []
|
|
64
|
+
files:
|
|
65
|
+
- LICENSE
|
|
66
|
+
- README.md
|
|
67
|
+
- elect.gemspec
|
|
68
|
+
- exe/elect
|
|
69
|
+
- lib/elect.rb
|
|
70
|
+
- lib/elect/cleaner.rb
|
|
71
|
+
- lib/elect/cli.rb
|
|
72
|
+
- lib/elect/pattern.rb
|
|
73
|
+
- lib/elect/processor.rb
|
|
74
|
+
- lib/elect/version.rb
|
|
75
|
+
homepage: https://github.com/arc-v/elect
|
|
76
|
+
licenses:
|
|
77
|
+
- MIT
|
|
78
|
+
metadata:
|
|
79
|
+
homepage_uri: https://github.com/arc-v/elect
|
|
80
|
+
source_code_uri: https://github.com/arc-v/elect
|
|
81
|
+
rdoc_options: []
|
|
82
|
+
require_paths:
|
|
83
|
+
- lib
|
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: 2.5.0
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
requirements: []
|
|
95
|
+
rubygems_version: 4.0.19
|
|
96
|
+
specification_version: 4
|
|
97
|
+
summary: AIGC 水印干掉器 — 从代码/文档中移除 AIGC 水印块
|
|
98
|
+
test_files: []
|