rage_flip 2.3.0 → 2.5.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 +4 -4
- data/README.md +13 -0
- data/exe/confusable +18 -0
- data/lib/rage_flip/confusable.rb +64 -0
- data/lib/rage_flip/emote.rb +17 -6
- data/lib/rage_flip/version.rb +1 -1
- data/lib/rage_flip.rb +1 -0
- data/spec/rage_flip_spec.rb +23 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67b5dd4341d2242b6f1372e026865302c6e2ca16a1005cb0801a4a6e1de4a7e1
|
|
4
|
+
data.tar.gz: 134945b628dbb3a591b22ae544f338022c12dd7065affa53b7a35ae30dd67137
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 822af43cd588ffe11a99bde6aa24d0de90d7b99b80c271d09d038b563dbc6a52eff1fdf5e136073e763e8a567a48a24a3c9ac7dee59da09af86e0c9a1f84b501
|
|
7
|
+
data.tar.gz: 04a1906d61a7093b3586657009dbfb19b82b528bd08bd58bb18e45bdb841c0681067b278477bfa4bad7040423f8d7adf43a4bf83c8365a9f5327d6a3e88fbf5f
|
data/README.md
CHANGED
|
@@ -145,6 +145,15 @@ frustrated "one more day"
|
|
|
145
145
|
# Output: ONE. MORE. DAY.
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
+
### confusable
|
|
149
|
+
|
|
150
|
+
Swaps every letter for a random lookalike glyph: homoglyphs from other scripts (Cyrillic, Greek, Armenian, Cherokee), unaccented letter variants (dotless ı, small capitals), and allographs (fullwidth, bold, script, fraktur, monospace):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
confusable "hello world"
|
|
154
|
+
# Output (random each time): һ𝐞𝓁ⅼ𝔬 ԝoг𝚕ԁ
|
|
155
|
+
```
|
|
156
|
+
|
|
148
157
|
### strikethrough
|
|
149
158
|
|
|
150
159
|
Adds strikethrough formatting to text using Unicode combining characters:
|
|
@@ -272,6 +281,10 @@ RageFlip::Sarcasm.process("hello world")
|
|
|
272
281
|
RageFlip::Frustrated.process("one more day")
|
|
273
282
|
# => "ONE. MORE. DAY."
|
|
274
283
|
|
|
284
|
+
# Confusable (random lookalike glyphs)
|
|
285
|
+
RageFlip::Confusable.process("hello")
|
|
286
|
+
# => "һ𝐞𝓁ⅼ𝔬"
|
|
287
|
+
|
|
275
288
|
# Strikethrough
|
|
276
289
|
RageFlip::Strikethrough.process("text")
|
|
277
290
|
# => "t̶e̶x̶t̶"
|
data/exe/confusable
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/rage_flip"
|
|
4
|
+
|
|
5
|
+
text = ARGV.join(" ")
|
|
6
|
+
|
|
7
|
+
if text.empty?
|
|
8
|
+
puts "Usage: confusable <text_to_confuse>"
|
|
9
|
+
exit 1
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
result = RageFlip::Confusable.process(text)
|
|
13
|
+
|
|
14
|
+
if RageFlip::Clipboard.copy(result)
|
|
15
|
+
else
|
|
16
|
+
puts "Failed to copy to clipboard, but here's your confusable text:"
|
|
17
|
+
end
|
|
18
|
+
puts result
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module RageFlip
|
|
2
|
+
class Confusable
|
|
3
|
+
# Homoglyphs (Cyrillic, Greek, Armenian, Cherokee, etc.), unaccented letter
|
|
4
|
+
# variants, and allographs (fullwidth, bold, script, fraktur, monospace).
|
|
5
|
+
CONFUSABLES = {
|
|
6
|
+
"a" => %w[а ɑ α ⍺ a 𝐚 𝒶 𝔞 𝚊],
|
|
7
|
+
"b" => %w[Ь ᖯ b 𝐛 𝒷 𝔟 𝚋],
|
|
8
|
+
"c" => %w[с ϲ ᴄ ⅽ c 𝐜 𝒸 𝔠 𝚌],
|
|
9
|
+
"d" => %w[ԁ ⅾ d 𝐝 𝒹 𝔡 𝚍],
|
|
10
|
+
"e" => %w[е ҽ ℮ e 𝐞 ℯ 𝔢 𝚎],
|
|
11
|
+
"f" => %w[ꬵ f 𝐟 𝒻 𝔣 𝚏],
|
|
12
|
+
"g" => %w[ɡ ց g 𝐠 ℊ 𝔤 𝚐],
|
|
13
|
+
"h" => %w[һ հ h 𝐡 𝒽 𝔥 𝚑],
|
|
14
|
+
"i" => %w[і ı ɩ ι ⅰ i 𝐢 𝒾 𝔦 𝚒],
|
|
15
|
+
"j" => %w[ј ϳ j 𝐣 𝒿 𝔧 𝚓],
|
|
16
|
+
"k" => %w[ĸ κ k 𝐤 𝓀 𝔨 𝚔],
|
|
17
|
+
"l" => %w[ӏ ǀ ⅼ ℓ l 𝐥 𝓁 𝔩 𝚕],
|
|
18
|
+
"m" => %w[ⅿ m 𝐦 𝓂 𝔪 𝚖],
|
|
19
|
+
"n" => %w[ո ռ n 𝐧 𝓃 𝔫 𝚗],
|
|
20
|
+
"o" => %w[о ο օ ᴏ o 𝐨 ℴ 𝔬 𝚘],
|
|
21
|
+
"p" => %w[р ρ p 𝐩 𝓅 𝔭 𝚙],
|
|
22
|
+
"q" => %w[ԛ զ q 𝐪 𝓆 𝔮 𝚚],
|
|
23
|
+
"r" => %w[г ꭇ r 𝐫 𝓇 𝔯 𝚛],
|
|
24
|
+
"s" => %w[ѕ ꜱ s 𝐬 𝓈 𝔰 𝚜],
|
|
25
|
+
"t" => %w[t 𝐭 𝓉 𝔱 𝚝],
|
|
26
|
+
"u" => %w[υ ս ᴜ u 𝐮 𝓊 𝔲 𝚞],
|
|
27
|
+
"v" => %w[ν ѵ ᴠ ⅴ v 𝐯 𝓋 𝔳 𝚟],
|
|
28
|
+
"w" => %w[ԝ ᴡ w 𝐰 𝓌 𝔴 𝚠],
|
|
29
|
+
"x" => %w[х ⅹ x 𝐱 𝓍 𝔵 𝚡],
|
|
30
|
+
"y" => %w[у ү ɣ y 𝐲 𝓎 𝔶 𝚢],
|
|
31
|
+
"z" => %w[ᴢ z 𝐳 𝓏 𝔷 𝚣],
|
|
32
|
+
"A" => %w[А Α Ꭺ ᗅ A 𝐀 𝒜 𝔄 𝙰],
|
|
33
|
+
"B" => %w[В Β Ᏼ ℬ B 𝐁 𝔅 𝙱],
|
|
34
|
+
"C" => %w[С Ϲ Ꮯ Ⅽ ℭ C 𝐂 𝒞 𝙲],
|
|
35
|
+
"D" => %w[Ꭰ Ⅾ D 𝐃 𝒟 𝔇 𝙳],
|
|
36
|
+
"E" => %w[Е Ε Ꭼ E 𝐄 ℰ 𝔈 𝙴],
|
|
37
|
+
"F" => %w[Ϝ ᖴ F 𝐅 ℱ 𝔉 𝙵],
|
|
38
|
+
"G" => %w[Ԍ Ꮐ G 𝐆 𝒢 𝔊 𝙶],
|
|
39
|
+
"H" => %w[Н Η Ꮋ ℋ ℌ H 𝐇 𝙷],
|
|
40
|
+
"I" => %w[І Ι Ӏ Ⅰ I 𝐈 ℐ ℑ 𝙸],
|
|
41
|
+
"J" => %w[Ј Ꭻ J 𝐉 𝒥 𝔍 𝙹],
|
|
42
|
+
"K" => %w[К Κ Ꮶ K K 𝐊 𝒦 𝔎 𝙺],
|
|
43
|
+
"L" => %w[Ꮮ ᒪ Ⅼ L 𝐋 ℒ 𝔏 𝙻],
|
|
44
|
+
"M" => %w[М Μ Ꮇ Ⅿ M 𝐌 ℳ 𝔐 𝙼],
|
|
45
|
+
"N" => %w[Ν N 𝐍 𝒩 𝔑 𝙽],
|
|
46
|
+
"O" => %w[О Ο Օ O 𝐎 𝒪 𝔒 𝙾],
|
|
47
|
+
"P" => %w[Р Ρ Ꮲ P 𝐏 𝒫 𝔓 𝙿],
|
|
48
|
+
"Q" => %w[Ԛ Q 𝐐 𝒬 𝔔 𝚀],
|
|
49
|
+
"R" => %w[Ꭱ Ꮢ ℛ ℜ R 𝐑 𝚁],
|
|
50
|
+
"S" => %w[Ѕ Ꮪ S 𝐒 𝒮 𝔖 𝚂],
|
|
51
|
+
"T" => %w[Т Τ Ꭲ T 𝐓 𝒯 𝔗 𝚃],
|
|
52
|
+
"U" => %w[Ս ᑌ U 𝐔 𝒰 𝔘 𝚄],
|
|
53
|
+
"V" => %w[Ѵ Ꮩ Ⅴ V 𝐕 𝒱 𝔙 𝚅],
|
|
54
|
+
"W" => %w[Ԝ Ꮃ W 𝐖 𝒲 𝔚 𝚆],
|
|
55
|
+
"X" => %w[Х Χ Ⅹ X 𝐗 𝒳 𝔛 𝚇],
|
|
56
|
+
"Y" => %w[Υ Ү Y 𝐘 𝒴 𝔜 𝚈],
|
|
57
|
+
"Z" => %w[Ζ Ꮓ ℤ Z 𝐙 𝒵 ℨ 𝚉]
|
|
58
|
+
}.freeze
|
|
59
|
+
|
|
60
|
+
def self.process(text)
|
|
61
|
+
text.each_char.map { |c| CONFUSABLES[c]&.sample || c }.join
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/rage_flip/emote.rb
CHANGED
|
@@ -1911,6 +1911,18 @@ module RageFlip
|
|
|
1911
1911
|
def self.refresh_emotes
|
|
1912
1912
|
@all_emotes = nil
|
|
1913
1913
|
@emoji_only_emotes = nil
|
|
1914
|
+
@emote_by_first_char = nil
|
|
1915
|
+
@compound_memo = nil
|
|
1916
|
+
end
|
|
1917
|
+
|
|
1918
|
+
def self.emote_by_first_char
|
|
1919
|
+
@emote_by_first_char ||= emoji_only_emotes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(name, value), h|
|
|
1920
|
+
h[name[0]] << [name, value]
|
|
1921
|
+
end
|
|
1922
|
+
end
|
|
1923
|
+
|
|
1924
|
+
def self.compound_memo
|
|
1925
|
+
@compound_memo ||= {}
|
|
1914
1926
|
end
|
|
1915
1927
|
|
|
1916
1928
|
# Check if an emote value contains only emoji (no ASCII art or text)
|
|
@@ -1941,22 +1953,21 @@ module RageFlip
|
|
|
1941
1953
|
end
|
|
1942
1954
|
|
|
1943
1955
|
# Find a valid combination of emoji-only emotes that match the compound name
|
|
1944
|
-
def self.find_emote_combination(name, start = 0, memo =
|
|
1956
|
+
def self.find_emote_combination(name, start = 0, memo = compound_memo)
|
|
1945
1957
|
return [] if start >= name.length
|
|
1946
1958
|
return memo[[name, start]] if memo.key?([name, start])
|
|
1947
1959
|
|
|
1948
|
-
#
|
|
1949
|
-
|
|
1950
|
-
|
|
1960
|
+
# Only check emotes whose name starts with the same character — avoids O(n) scan
|
|
1961
|
+
candidates = emote_by_first_char[name[start]] || []
|
|
1962
|
+
candidates.each do |emote_name, emote_value|
|
|
1963
|
+
next unless name[start..].start_with?(emote_name)
|
|
1951
1964
|
|
|
1952
1965
|
rest_start = start + emote_name.length
|
|
1953
1966
|
if rest_start >= name.length
|
|
1954
|
-
# We've matched the entire string
|
|
1955
1967
|
memo[[name, start]] = [emote_value]
|
|
1956
1968
|
return [emote_value]
|
|
1957
1969
|
end
|
|
1958
1970
|
|
|
1959
|
-
# Try to match the rest
|
|
1960
1971
|
rest = find_emote_combination(name, rest_start, memo)
|
|
1961
1972
|
if rest
|
|
1962
1973
|
result = [emote_value] + rest
|
data/lib/rage_flip/version.rb
CHANGED
data/lib/rage_flip.rb
CHANGED
data/spec/rage_flip_spec.rb
CHANGED
|
@@ -64,6 +64,29 @@ RSpec.describe RageFlip do
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
describe RageFlip::Confusable do
|
|
68
|
+
describe ".process" do
|
|
69
|
+
it "replaces every letter with a different glyph" do
|
|
70
|
+
input = ("a".."z").to_a.join + ("A".."Z").to_a.join
|
|
71
|
+
result = RageFlip::Confusable.process(input)
|
|
72
|
+
expect(result.each_char.count).to eq(input.length)
|
|
73
|
+
result.each_char.zip(input.each_char).each do |out, orig|
|
|
74
|
+
expect(out).not_to eq(orig)
|
|
75
|
+
expect(RageFlip::Confusable::CONFUSABLES[orig]).to include(out)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "leaves non-letters untouched" do
|
|
80
|
+
expect(RageFlip::Confusable.process("123 !?")).to eq("123 !?")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "has confusables for every ASCII letter" do
|
|
84
|
+
letters = ("a".."z").to_a + ("A".."Z").to_a
|
|
85
|
+
expect(RageFlip::Confusable::CONFUSABLES.keys).to match_array(letters)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
67
90
|
describe RageFlip::Strikethrough do
|
|
68
91
|
describe ".process" do
|
|
69
92
|
it "adds strikethrough characters" do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rage_flip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thomas Powell
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|
|
@@ -45,6 +45,7 @@ email:
|
|
|
45
45
|
executables:
|
|
46
46
|
- chaos
|
|
47
47
|
- chaos_level
|
|
48
|
+
- confusable
|
|
48
49
|
- disapproval
|
|
49
50
|
- doubleunderline
|
|
50
51
|
- emote
|
|
@@ -69,6 +70,7 @@ files:
|
|
|
69
70
|
- bin/version
|
|
70
71
|
- exe/chaos
|
|
71
72
|
- exe/chaos_level
|
|
73
|
+
- exe/confusable
|
|
72
74
|
- exe/disapproval
|
|
73
75
|
- exe/doubleunderline
|
|
74
76
|
- exe/emote
|
|
@@ -83,6 +85,7 @@ files:
|
|
|
83
85
|
- lib/rage_flip.rb
|
|
84
86
|
- lib/rage_flip/chaos.rb
|
|
85
87
|
- lib/rage_flip/clipboard.rb
|
|
88
|
+
- lib/rage_flip/confusable.rb
|
|
86
89
|
- lib/rage_flip/emote.rb
|
|
87
90
|
- lib/rage_flip/flipper.rb
|
|
88
91
|
- lib/rage_flip/frustrated.rb
|