negarmoji 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +6 -0
- data/Rakefile +24 -0
- data/db/dump.rb +69 -0
- data/db/emoji-test-parser.rb +121 -0
- data/db/index.html +113 -0
- data/db/{emoji.json → negarmoji.json} +0 -0
- data/lib/negarmoji.rb +1 -1
- data/lib/{emoji → negarmoji}/character.rb +7 -7
- data/lib/{emoji.rb → negarmoji/emoji.rb} +11 -11
- data/lib/negarmoji/version.rb +5 -0
- data/negarmoji.gemspec +24 -0
- data/script/bootstrap +10 -0
- data/script/console +16 -0
- data/script/release +31 -0
- data/script/test +14 -0
- data/test/documentation_test.rb +46 -0
- data/test/emoji_test.rb +243 -0
- data/test/test_helper.rb +8 -0
- data/vendor/unicode-negarmoji-test.txt +4119 -0
- metadata +31 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e2e1d815a0ef7dd8148b4b72d2ec5e4f20e78a75cd650b769193793bb9a142d
|
4
|
+
data.tar.gz: 49d687a4853b687614cc6f3f9829cefcaa76447f6703fdb6c8c624af390dd319
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a78d55cf033e29c0cd95a8a22d40f3b91f90f5525e8944ee69137dc4f347c2364094351f63a90dbc5fc08fa4370d2066281d786aaefbf284d13fb30fb9c93c49
|
7
|
+
data.tar.gz: 2b064af840975fe10d14c8190def116540da1bddcba0abd0e3a7c8e2cba8d78a9590eb8c65d91c898304973f626410f78ddbdb72174fa49971803dd732589381
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Contributions to this project are released to the public under the [project's open source license](LICENSE).
|
2
|
+
|
3
|
+
Some useful tools in development are:
|
4
|
+
|
5
|
+
```
|
6
|
+
script/bootstrap
|
7
|
+
```
|
8
|
+
|
9
|
+
Sets up the development environment. The prerequisites are:
|
10
|
+
|
11
|
+
* Ruby 1.9+
|
12
|
+
* Bundler
|
13
|
+
|
14
|
+
```
|
15
|
+
script/test
|
16
|
+
```
|
17
|
+
|
18
|
+
Runs the test suite.
|
19
|
+
|
20
|
+
```
|
21
|
+
script/console
|
22
|
+
```
|
23
|
+
|
24
|
+
Opens `irb` console with negarmoji library preloded for experimentation.
|
25
|
+
|
26
|
+
```
|
27
|
+
script/release
|
28
|
+
```
|
29
|
+
|
30
|
+
For maintainers only: after the gemspec has been edited, this commits the
|
31
|
+
change, tags a release, and pushes it to both GitHub and RubyGems.org.
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
task :default => :test
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << "test"
|
7
|
+
t.test_files = FileList["test/*_test.rb"]
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :db do
|
11
|
+
desc %(Generate Emoji data files needed for development)
|
12
|
+
task :generate => [
|
13
|
+
'vendor/unicode-negarmoji-test.txt',
|
14
|
+
]
|
15
|
+
|
16
|
+
desc %(Dump a list of supported Emoji with Unicode descriptions and aliases)
|
17
|
+
task :dump => :generate do
|
18
|
+
system 'ruby', '-Ilib', 'db/dump.rb'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
file 'vendor/unicode-negarmoji-test.txt' do |t|
|
23
|
+
system 'curl', '-fsSL', 'http://unicode.org/Public/negarmoji/12.0/negarmoji-test.txt', '-o', t.name
|
24
|
+
end
|
data/db/dump.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'emoji'
|
4
|
+
require 'json'
|
5
|
+
require_relative './emoji-test-parser'
|
6
|
+
|
7
|
+
items = []
|
8
|
+
|
9
|
+
_, categories = EmojiTestParser.parse(File.expand_path("../../vendor/unicode-negarmoji-test.txt", __FILE__))
|
10
|
+
seen_existing = {}
|
11
|
+
|
12
|
+
categories.each { |category|
|
13
|
+
category[:emoji].each { |sub_category|
|
14
|
+
sub_category[:emoji].each { |emoji_item|
|
15
|
+
unicodes = emoji_item[:sequences].sort_by(&:bytesize)
|
16
|
+
existing_emoji = nil
|
17
|
+
unicodes.detect do |raw|
|
18
|
+
existing_emoji = Emoji.find_by_unicode(raw)
|
19
|
+
end
|
20
|
+
existing_emoji = nil if seen_existing.key?(existing_emoji)
|
21
|
+
output_item = {
|
22
|
+
emoji: unicodes[0],
|
23
|
+
description: emoji_item[:description],
|
24
|
+
category: category[:name],
|
25
|
+
}
|
26
|
+
if existing_emoji
|
27
|
+
eu = existing_emoji.unicode_aliases
|
28
|
+
preferred_raw = eu.size == 2 && eu[0] == "#{eu[1]}\u{fe0f}" ? eu[1] : eu[0]
|
29
|
+
output_item.update(
|
30
|
+
emoji: preferred_raw,
|
31
|
+
aliases: existing_emoji.aliases,
|
32
|
+
tags: existing_emoji.tags,
|
33
|
+
unicode_version: existing_emoji.unicode_version,
|
34
|
+
ios_version: existing_emoji.ios_version,
|
35
|
+
)
|
36
|
+
seen_existing[existing_emoji] = true
|
37
|
+
else
|
38
|
+
output_item.update(
|
39
|
+
aliases: [emoji_item[:description].gsub(/\W+/, '_').downcase],
|
40
|
+
tags: [],
|
41
|
+
unicode_version: "12.0",
|
42
|
+
ios_version: "13.0",
|
43
|
+
)
|
44
|
+
end
|
45
|
+
output_item[:skin_tones] = true if emoji_item[:skin_tones]
|
46
|
+
items << output_item
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
missing_emoji = Emoji.all.reject { |e| e.custom? || seen_existing.key?(e) }
|
52
|
+
if missing_emoji.any?
|
53
|
+
$stderr.puts "Error: these `negarmoji.json` entries were not matched:"
|
54
|
+
$stderr.puts missing_emoji.map { |e| "%s (%s)" % [e.hex_inspect, e.name] }
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
Emoji.all.select(&:custom?).each { |emoji|
|
59
|
+
items << {
|
60
|
+
aliases: emoji.aliases,
|
61
|
+
tags: emoji.tags,
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
trap(:PIPE) { abort }
|
66
|
+
|
67
|
+
puts JSON.pretty_generate(items)
|
68
|
+
.gsub("\n\n", "\n")
|
69
|
+
.gsub(/,\n( +)/) { "\n%s, " % $1[2..-1] }
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EmojiTestParser
|
4
|
+
VARIATION_SELECTOR_16 = "\u{fe0f}"
|
5
|
+
SKIN_TONES = [
|
6
|
+
"\u{1F3FB}", # light skin tone
|
7
|
+
"\u{1F3FC}", # medium-light skin tone
|
8
|
+
"\u{1F3FD}", # medium skin tone
|
9
|
+
"\u{1F3FE}", # medium-dark skin tone
|
10
|
+
"\u{1F3FF}", # dark skin tone
|
11
|
+
]
|
12
|
+
HAIR_MODIFIERS = [
|
13
|
+
"\u{1F9B0}", # red-haired
|
14
|
+
"\u{1F9B1}", # curly-haired
|
15
|
+
"\u{1F9B2}", # bald
|
16
|
+
"\u{1F9B3}", # white-haired
|
17
|
+
]
|
18
|
+
|
19
|
+
module_function
|
20
|
+
|
21
|
+
def parse(filename)
|
22
|
+
File.open(filename, "r:UTF-8") do |file|
|
23
|
+
parse_file(file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_file(io)
|
28
|
+
data = []
|
29
|
+
emoji_map = {}
|
30
|
+
category = nil
|
31
|
+
sub_category = nil
|
32
|
+
|
33
|
+
io.each do |line|
|
34
|
+
begin
|
35
|
+
if line.start_with?("# group: ")
|
36
|
+
_, group_name = line.split(":", 2)
|
37
|
+
category = {
|
38
|
+
name: group_name.strip,
|
39
|
+
emoji: [],
|
40
|
+
}
|
41
|
+
data << category
|
42
|
+
sub_category = nil
|
43
|
+
elsif line.start_with?("# subgroup: ")
|
44
|
+
_, group_name = line.split(":", 2)
|
45
|
+
sub_category = {
|
46
|
+
name: group_name.strip,
|
47
|
+
emoji: [],
|
48
|
+
}
|
49
|
+
category[:emoji] << sub_category
|
50
|
+
elsif line.start_with?("#") || line.strip.empty?
|
51
|
+
next
|
52
|
+
else
|
53
|
+
row, desc = line.split("#", 2)
|
54
|
+
desc = desc.strip.split(" ", 2)[1]
|
55
|
+
codepoints, _ = row.split(";", 2)
|
56
|
+
emoji_raw = codepoints.strip.split.map { |c| c.hex }.pack("U*")
|
57
|
+
next if HAIR_MODIFIERS.include?(emoji_raw)
|
58
|
+
emoji_normalized = emoji_raw
|
59
|
+
.gsub(VARIATION_SELECTOR_16, "")
|
60
|
+
.gsub(/(#{SKIN_TONES.join("|")})/o, "")
|
61
|
+
emoji_item = emoji_map[emoji_normalized]
|
62
|
+
if SKIN_TONES.any? { |s| emoji_raw.include?(s) }
|
63
|
+
emoji_item[:skin_tones] = true if emoji_item
|
64
|
+
next
|
65
|
+
end
|
66
|
+
if emoji_item
|
67
|
+
emoji_item[:sequences] << emoji_raw
|
68
|
+
else
|
69
|
+
emoji_item = {
|
70
|
+
sequences: [emoji_raw],
|
71
|
+
description: desc,
|
72
|
+
}
|
73
|
+
emoji_map[emoji_normalized] = emoji_item
|
74
|
+
sub_category[:emoji] << emoji_item
|
75
|
+
end
|
76
|
+
end
|
77
|
+
rescue
|
78
|
+
warn "line: %p" % line
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
[emoji_map, data]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
if $0 == __FILE__
|
88
|
+
html_output = false
|
89
|
+
if ARGV[0] == "--html"
|
90
|
+
ARGV.shift
|
91
|
+
html_output = true
|
92
|
+
end
|
93
|
+
|
94
|
+
_, categories = EmojiTestParser.parse
|
95
|
+
|
96
|
+
trap(:PIPE) { abort }
|
97
|
+
|
98
|
+
if html_output
|
99
|
+
puts "<!doctype html>"
|
100
|
+
puts "<meta charset=utf-8>"
|
101
|
+
for category in categories
|
102
|
+
puts "<h2>#{category[:name]}</h2>"
|
103
|
+
for sub_category in category[:emoji]
|
104
|
+
puts "<h3>#{sub_category[:name]}</h3>"
|
105
|
+
puts "<ol>"
|
106
|
+
for char in sub_category[:emoji]
|
107
|
+
puts "<li>"
|
108
|
+
for sequence in char[:sequences]
|
109
|
+
codepoints = sequence.unpack("U*").map { |c| c.to_s(16).upcase }.join(" ")
|
110
|
+
printf '<span class=emoji title="%s">%s</span> ', codepoints, sequence
|
111
|
+
end
|
112
|
+
puts "#{char[:description]}</li>"
|
113
|
+
end
|
114
|
+
puts "</ol>"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
else
|
118
|
+
require "json"
|
119
|
+
puts JSON.pretty_generate(categories)
|
120
|
+
end
|
121
|
+
end
|
data/db/index.html
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<title>Emoji preview</title>
|
3
|
+
|
4
|
+
<style type="text/css">
|
5
|
+
body {
|
6
|
+
font: 14px/18px Verdana, Arial, sans-serif;
|
7
|
+
padding: 2em;
|
8
|
+
}
|
9
|
+
ol {
|
10
|
+
list-style: none;
|
11
|
+
padding-left: 0;
|
12
|
+
}
|
13
|
+
li {
|
14
|
+
margin-left: 0;
|
15
|
+
margin-top: 5px;
|
16
|
+
clear: left;
|
17
|
+
}
|
18
|
+
li:first-child { display: none; }
|
19
|
+
li > span {
|
20
|
+
display: block;
|
21
|
+
}
|
22
|
+
.emoji {
|
23
|
+
font-size: 50px;
|
24
|
+
line-height: 50px;
|
25
|
+
float: left;
|
26
|
+
margin-right: 20px;
|
27
|
+
}
|
28
|
+
.description {
|
29
|
+
font-style: italic;
|
30
|
+
color: gray;
|
31
|
+
}
|
32
|
+
.aliases span:before, .aliases span:after {
|
33
|
+
content: ":";
|
34
|
+
color: gray;
|
35
|
+
}
|
36
|
+
.tags {
|
37
|
+
font-size: 11px;
|
38
|
+
}
|
39
|
+
.tags span {
|
40
|
+
display: inline-block;
|
41
|
+
padding: 1px 5px 2px;
|
42
|
+
border-radius: 3px;
|
43
|
+
background: #dadada;
|
44
|
+
line-height: 11px;
|
45
|
+
}
|
46
|
+
</style>
|
47
|
+
|
48
|
+
<p><input type=search placeholder="Search..."></p>
|
49
|
+
|
50
|
+
<ol>
|
51
|
+
<li>
|
52
|
+
<span class="emoji"></span>
|
53
|
+
<span class="description"></span>
|
54
|
+
<span class="aliases"></span>
|
55
|
+
<span class="tags"></span>
|
56
|
+
</li>
|
57
|
+
</ol>
|
58
|
+
|
59
|
+
<script>
|
60
|
+
function renderEmoji(emojis) {
|
61
|
+
var item, els, template = document.querySelector('li')
|
62
|
+
for (var emoji, i=0; i < emojis.length; i++) {
|
63
|
+
emoji = emojis[i]
|
64
|
+
item = template.cloneNode(true)
|
65
|
+
els = item.querySelectorAll('span')
|
66
|
+
if (emoji.emoji) els[0].textContent = emoji.emoji
|
67
|
+
else {
|
68
|
+
var img = document.createElement('img')
|
69
|
+
img.src = "../images/" + emoji.aliases[0] + ".png"
|
70
|
+
els[0].appendChild(img)
|
71
|
+
}
|
72
|
+
els[1].textContent = emoji.description || ''
|
73
|
+
els[2].innerHTML = emoji.aliases.map(function(n){ return '<span>'+n+'</span>' }).join(' ')
|
74
|
+
els[3].innerHTML = emoji.tags.map(function(n){ return '<span>'+n+'</span>' }).join(' ')
|
75
|
+
template.parentNode.appendChild(item)
|
76
|
+
|
77
|
+
item._emojiText = (els[2].textContent + ' ' + els[3].textContent).replace(/_/g, '-')
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
xhr = new XMLHttpRequest
|
82
|
+
xhr.onreadystatechange = function() {
|
83
|
+
if (this.readyState == 4) {
|
84
|
+
json = JSON.parse(this.responseText)
|
85
|
+
renderEmoji(json)
|
86
|
+
}
|
87
|
+
}
|
88
|
+
xhr.open('GET', 'emoji.json', false)
|
89
|
+
xhr.send(null)
|
90
|
+
|
91
|
+
function searchEmoji(query) {
|
92
|
+
var el,
|
93
|
+
re = new RegExp('\\b' + query),
|
94
|
+
els = document.querySelectorAll('li')
|
95
|
+
|
96
|
+
for (var i=1; i < els.length; i++) {
|
97
|
+
el = els[i]
|
98
|
+
if ( !query || re.test(el._emojiText) ) {
|
99
|
+
el.style.display = 'list-item'
|
100
|
+
} else {
|
101
|
+
el.style.display = 'none'
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
var timeout, search = document.querySelector('input[type=search]')
|
107
|
+
search.addEventListener('input', function() {
|
108
|
+
var $this = this
|
109
|
+
if (timeout) clearTimeout(timeout)
|
110
|
+
timeout = setTimeout(function(){ searchEmoji($this.value) }, 200)
|
111
|
+
}, false)
|
112
|
+
search.focus()
|
113
|
+
</script>
|
File without changes
|
data/lib/negarmoji.rb
CHANGED
@@ -5,7 +5,7 @@ module Emoji
|
|
5
5
|
# Inspect individual Unicode characters in a string by dumping its
|
6
6
|
# codepoints in hexadecimal format.
|
7
7
|
def self.hex_inspect(str)
|
8
|
-
str.codepoints.map { |c| c.to_s(16).rjust(4,
|
8
|
+
str.codepoints.map { |c| c.to_s(16).rjust(4, "0") }.join("-")
|
9
9
|
end
|
10
10
|
|
11
11
|
# True if the emoji is not a standard Emoji character.
|
@@ -57,7 +57,7 @@ module Emoji
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def inspect
|
60
|
-
hex =
|
60
|
+
hex = "(#{hex_inspect unless custom?})"
|
61
61
|
%(#<#{self.class.name}:#{name}#{hex}>)
|
62
62
|
end
|
63
63
|
|
@@ -67,22 +67,22 @@ module Emoji
|
|
67
67
|
|
68
68
|
attr_writer :image_filename
|
69
69
|
|
70
|
-
def image_filename
|
70
|
+
def image_filename(extension = "svg")
|
71
71
|
if defined? @image_filename
|
72
72
|
@image_filename
|
73
73
|
else
|
74
|
-
default_image_filename
|
74
|
+
default_image_filename(extension)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
78
|
private
|
79
79
|
|
80
|
-
def default_image_filename
|
80
|
+
def default_image_filename(extension)
|
81
81
|
if custom?
|
82
|
-
|
82
|
+
"#{name}.#{extension}"
|
83
83
|
else
|
84
84
|
hex_name = hex_inspect.gsub(/-(fe0f|200d)\b/, '')
|
85
|
-
|
85
|
+
"#{hex_name}.#{extension}"
|
86
86
|
end
|
87
87
|
end
|
88
88
|
end
|