ecoji 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ecoji
4
+ VERSION = '0.1.0'
5
+ end
data/lib/ecoji.rb ADDED
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ecoji
4
+ end
5
+
6
+ require 'ecoji/emojis'
7
+ require 'ecoji/version'
8
+
9
+ module Ecoji
10
+ class Error < StandardError; end
11
+
12
+ PADDING = 0x2615
13
+ PADDING_LAST_V1 = [0x269C, 0x1F3CD, 0x1F4D1, 0x1F64B].freeze
14
+ PADDING_LAST_V2 = [0x1F977, 0x1F6FC, 0x1F4D1, 0x1F64B].freeze
15
+
16
+ REV_EMOJIS = begin
17
+ map = {}
18
+
19
+ Emojis::V1.each_with_index do |n, ordinal|
20
+ map[n] = { ordinal:, version: 1, padding: :none }
21
+ end
22
+
23
+ Emojis::V2.each_with_index do |n, ordinal|
24
+ if map[n]
25
+ map[n][:version] = 3
26
+ else
27
+ map[n] = { ordinal:, version: 2, padding: :none }
28
+ end
29
+ end
30
+
31
+ map[PADDING] = { ordinal: 0, version: 3, padding: :fill }
32
+ map[PADDING_LAST_V1[0]] = { ordinal: 0, version: 1, padding: :last }
33
+ map[PADDING_LAST_V1[1]] = { ordinal: 1 << 8, version: 1, padding: :last }
34
+ map[PADDING_LAST_V1[2]] = { ordinal: 2 << 8, version: 3, padding: :last }
35
+ map[PADDING_LAST_V1[3]] = { ordinal: 3 << 8, version: 3, padding: :last }
36
+ map[PADDING_LAST_V2[0]] = { ordinal: 0, version: 2, padding: :last }
37
+ map[PADDING_LAST_V2[1]] = { ordinal: 1 << 8, version: 2, padding: :last }
38
+
39
+ map
40
+ end
41
+
42
+ def self.encode(data, version: 2)
43
+ case version
44
+ when 1
45
+ emojis = Emojis::V1
46
+ padding_last = PADDING_LAST_V1
47
+ trim = false
48
+ when 2
49
+ emojis = Emojis::V2
50
+ padding_last = PADDING_LAST_V2
51
+ trim = true
52
+ else
53
+ raise Error, 'Version must be either 1 or 2'
54
+ end
55
+
56
+ result = []
57
+ data.bytes.each_slice(5) do |s|
58
+ encode_five(s, emojis, padding_last, trim) { |n| result << n }
59
+ end
60
+ result.pack('U*')
61
+ end
62
+
63
+ def self.decode(data, encoding: Encoding::UTF_8)
64
+ expected_version = 3
65
+ chars = data.chars
66
+ result = []
67
+
68
+ loop do
69
+ emojis, expected_version = read_four(chars, expected_version)
70
+ break if emojis.empty?
71
+
72
+ bits = (emojis[0][:ordinal] << 30) |
73
+ (emojis[1][:ordinal] << 20) |
74
+ (emojis[2][:ordinal] << 10) |
75
+ emojis[3][:ordinal]
76
+ out = [
77
+ bits >> 32,
78
+ 0xff & (bits >> 24),
79
+ 0xff & (bits >> 16),
80
+ 0xff & (bits >> 8),
81
+ 0xff & bits
82
+ ]
83
+
84
+ if emojis[1][:padding] == :fill
85
+ out = out[0...1]
86
+ elsif emojis[2][:padding] == :fill
87
+ out = out[0...2]
88
+ elsif emojis[3][:padding] == :fill
89
+ out = out[0...3]
90
+ elsif emojis[3][:padding] == :last
91
+ out = out[0...4]
92
+ end
93
+
94
+ result.concat(out)
95
+ end
96
+
97
+ result.pack('C*').force_encoding(encoding)
98
+ end
99
+
100
+ private_class_method def self.encode_five(s, emojis, padding_last, trim)
101
+ case s.size
102
+ when 1
103
+ yield emojis[s[0] << 2]
104
+ yield PADDING
105
+ unless trim
106
+ yield PADDING
107
+ yield PADDING
108
+ end
109
+ when 2
110
+ bits = (s[0] << 32) | (s[1] << 24)
111
+ yield emojis[bits >> 30]
112
+ yield emojis[0x3ff & (bits >> 20)]
113
+ yield PADDING
114
+ yield PADDING unless trim
115
+ when 3
116
+ bits = (s[0] << 32) | (s[1] << 24) | (s[2] << 16)
117
+ yield emojis[bits >> 30]
118
+ yield emojis[0x3ff & (bits >> 20)]
119
+ yield emojis[0x3ff & (bits >> 10)]
120
+ yield PADDING
121
+ when 4
122
+ bits = (s[0] << 32) | (s[1] << 24) | (s[2] << 16) | (s[3] << 8)
123
+ yield emojis[bits >> 30]
124
+ yield emojis[0x3ff & (bits >> 20)]
125
+ yield emojis[0x3ff & (bits >> 10)]
126
+ yield padding_last[0x3 & (bits >> 8)]
127
+ when 5
128
+ bits = (s[0] << 32) | (s[1] << 24) | (s[2] << 16) | (s[3] << 8) | s[4]
129
+ yield emojis[bits >> 30]
130
+ yield emojis[0x3ff & (bits >> 20)]
131
+ yield emojis[0x3ff & (bits >> 10)]
132
+ yield emojis[0x3ff & bits]
133
+ else
134
+ raise "BUG: unexpected length: #{s.size}"
135
+ end
136
+ end
137
+
138
+ private_class_method def self.read_four(chars, expected_version)
139
+ index = 0
140
+ saw_padding = false
141
+ emojis = [REV_EMOJIS[PADDING], REV_EMOJIS[PADDING], REV_EMOJIS[PADDING], REV_EMOJIS[PADDING]]
142
+
143
+ while index < 4
144
+ if chars.empty?
145
+ return [], expected_version if index.zero?
146
+ return emojis, expected_version if saw_padding && [3, 2].include?(expected_version)
147
+
148
+ raise Error, 'Unexpected end of data, input data size not multiple of 4'
149
+
150
+ end
151
+
152
+ c = chars.shift
153
+ next if ["\n", "\r"].include?(c)
154
+
155
+ einfo = REV_EMOJIS[c.ord]
156
+ raise Error, "Non Ecoji character seen: #{c.inspect}" unless einfo
157
+
158
+ if einfo[:version] != 3
159
+ if expected_version == 3
160
+ expected_version = einfo[:version]
161
+ elsif expected_version != einfo[:version]
162
+ raise Error, "Emojis from different ecoji versions seen: #{c.inspect}"
163
+ end
164
+ end
165
+
166
+ case einfo[:padding]
167
+ when :none
168
+ if saw_padding
169
+ raise Error, 'Unexpectedly saw non-padding after padding' unless [3, 2].include?(expected_version)
170
+
171
+ chars.unshift(c)
172
+ # NOTE(makenowjust): I think here we should set `expected_version` to `2`,
173
+ # but the original implementation does not.
174
+ # For the compatibility, `expected_version` is kept.
175
+ return emojis, expected_version
176
+
177
+ end
178
+ when :fill
179
+ raise Error, 'Padding unexpectedly seen in first position' if index.zero?
180
+
181
+ saw_padding = true
182
+ when :last
183
+ raise Error, 'Last padding seen in unexpected position' if index != 3
184
+ end
185
+
186
+ emojis[index] = einfo
187
+ index += 1
188
+ end
189
+
190
+ [emojis, expected_version]
191
+ end
192
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ecoji
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - TSUYUSATO Kitsune
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.17'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.17'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.43'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.43'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.25'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.25'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.6'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.6'
97
+ description: Ecoji is a data-to-emoji encoding scheme. This library provides the implementation
98
+ of Ecoji in Ruby.
99
+ email:
100
+ - make.just.on@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".gitmodules"
107
+ - ".rubocop.yml"
108
+ - ".rubocop_todo.yml"
109
+ - CODE_OF_CONDUCT.md
110
+ - Gemfile
111
+ - Gemfile.lock
112
+ - LICENSE
113
+ - LICENSE.ecoji
114
+ - README.md
115
+ - Rakefile
116
+ - bin/console
117
+ - bin/rubocop
118
+ - bin/setup
119
+ - ecoji.gemspec
120
+ - lib/ecoji.rb
121
+ - lib/ecoji/emojis.rb
122
+ - lib/ecoji/version.rb
123
+ homepage: https://github.com/makenowjust/ecoji.rb/
124
+ licenses:
125
+ - MIT
126
+ metadata:
127
+ homepage_uri: https://github.com/makenowjust/ecoji.rb/
128
+ source_code_uri: https://github.com/makenowjust/ecoji.rb/
129
+ changelog_uri: https://github.com/makenowjust/ecoji.rb/blob/main/CHANGELOG.md
130
+ rubygems_mfa_required: 'true'
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - '='
138
+ - !ruby/object:Gem::Version
139
+ version: '3.2'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.4.1
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Ecoji implementation in Ruby
150
+ test_files: []