ecoji 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.
@@ -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: []