arTTY 0.9.48
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/bin/arTTY +486 -0
- data/lib/arTTY.rb +78 -0
- data/lib/arTTY/art.rb +111 -0
- data/lib/arTTY/cache.rb +117 -0
- data/lib/arTTY/error.rb +9 -0
- data/lib/arTTY/error/art_not_found.rb +6 -0
- data/lib/arTTY/error/failed_to_download.rb +5 -0
- data/lib/arTTY/error/image_magick_not_found.rb +5 -0
- data/lib/arTTY/error/image_named_improperly.rb +6 -0
- data/lib/arTTY/error/image_not_found.rb +6 -0
- data/lib/arTTY/error/no_pixel_data_found.rb +5 -0
- data/lib/arTTY/generator.rb +156 -0
- data/lib/arTTY/system_info.rb +212 -0
- metadata +198 -0
data/lib/arTTY.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
class ArTTY
|
2
|
+
attr_reader :art
|
3
|
+
|
4
|
+
def cache(download = false)
|
5
|
+
@cache.refresh(download)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.current_version
|
9
|
+
__FILE__.match(/arTTY-(\d+\.\d+\.\d+)/) do |m|
|
10
|
+
return m[1]
|
11
|
+
end
|
12
|
+
return "error" # Shouldn't happen
|
13
|
+
end
|
14
|
+
|
15
|
+
def exclude(pattern)
|
16
|
+
@art.delete_if do |name|
|
17
|
+
name.match(/#{pattern}/)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def fits(width, height)
|
22
|
+
if ((height <= 0) || (width <= 0))
|
23
|
+
@art.clear
|
24
|
+
else
|
25
|
+
@art.keep_if do |name|
|
26
|
+
(@cache.get_height_for(name) <= height) &&
|
27
|
+
(@cache.get_width_for(name) <= width)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def get(name, sysinfo = nil)
|
33
|
+
case name
|
34
|
+
when "none"
|
35
|
+
img = ArTTY::Art.new
|
36
|
+
else
|
37
|
+
if (!@@all_art.include?(name))
|
38
|
+
raise ArTTY::Error::ArtNotFound.new(name)
|
39
|
+
end
|
40
|
+
file = @cache.get_file_for(name)
|
41
|
+
file = nil if (!@art.include?(name))
|
42
|
+
img = ArTTY::Art.new(file)
|
43
|
+
end
|
44
|
+
img.sysinfo = sysinfo
|
45
|
+
return img
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@cache = ArTTY::Cache.new
|
50
|
+
|
51
|
+
@@all_art ||= Array.new
|
52
|
+
@cache.art.each do |name|
|
53
|
+
@@all_art.push(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
@@all_art.sort! do |a, b|
|
57
|
+
a.downcase <=> b.downcase
|
58
|
+
end
|
59
|
+
|
60
|
+
@art = @@all_art.clone
|
61
|
+
end
|
62
|
+
|
63
|
+
def match(pattern)
|
64
|
+
@art.keep_if do |name|
|
65
|
+
name.match(/#{pattern}/)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def random
|
70
|
+
return @art.shuffle[0]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
require "arTTY/art"
|
75
|
+
require "arTTY/cache"
|
76
|
+
require "arTTY/error"
|
77
|
+
require "arTTY/generator"
|
78
|
+
require "arTTY/system_info"
|
data/lib/arTTY/art.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "hilighter"
|
4
|
+
|
5
|
+
class ArTTY::Art
|
6
|
+
attr_reader :pixels
|
7
|
+
attr_accessor :legend
|
8
|
+
attr_accessor :name
|
9
|
+
attr_accessor :sysinfo
|
10
|
+
|
11
|
+
def draw
|
12
|
+
offset = nil
|
13
|
+
out = ""
|
14
|
+
pixels = @pixels
|
15
|
+
sysinfo = Array.new
|
16
|
+
if (@sysinfo)
|
17
|
+
offset = @pixels[0] ? @pixels[0].length + 2 : 0
|
18
|
+
sysinfo = @sysinfo.info
|
19
|
+
end
|
20
|
+
|
21
|
+
if ((pixels.length % 2) != 0)
|
22
|
+
filler = " " * pixels[0].length
|
23
|
+
pixels.insert(0, filler)
|
24
|
+
end
|
25
|
+
|
26
|
+
while (!pixels.empty?)
|
27
|
+
out += " "
|
28
|
+
top = pixels.delete_at(0)
|
29
|
+
bottom = pixels.delete_at(0)
|
30
|
+
|
31
|
+
top += " " while (top.length < bottom.length)
|
32
|
+
bottom += " " while (top.length > bottom.length)
|
33
|
+
|
34
|
+
top.chars.zip(bottom.chars).each_with_index do |map, i|
|
35
|
+
t = map[0].strip
|
36
|
+
b = map[1].strip
|
37
|
+
|
38
|
+
if (
|
39
|
+
(t.empty? && b.empty?) ||
|
40
|
+
(!t.empty? && !@legend.include?(t)) ||
|
41
|
+
(!b.empty? && !@legend.include?(b))
|
42
|
+
)
|
43
|
+
out += " "
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
if (t.empty?)
|
48
|
+
c = "▄".send(@legend[b])
|
49
|
+
elsif (b.empty?)
|
50
|
+
c = "▄".send(@legend[t]).swap
|
51
|
+
else
|
52
|
+
c = "▄".send(@legend[b]).send("on_#{@legend[t]}")
|
53
|
+
end
|
54
|
+
|
55
|
+
out += c
|
56
|
+
end
|
57
|
+
|
58
|
+
if (offset)
|
59
|
+
info = sysinfo.delete_at(0)
|
60
|
+
if (info)
|
61
|
+
filler = offset - top.length
|
62
|
+
out += " " * filler if (filler > 0)
|
63
|
+
out += info
|
64
|
+
else
|
65
|
+
offset = nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
out += "\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
if (offset)
|
73
|
+
loop do
|
74
|
+
info = sysinfo.delete_at(0)
|
75
|
+
break if (info.nil?)
|
76
|
+
out += " " * (offset + 1)
|
77
|
+
out += "#{info}\n"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
return out
|
82
|
+
end
|
83
|
+
|
84
|
+
def height
|
85
|
+
return @json["height"]
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(file = nil)
|
89
|
+
if (!file.nil?)
|
90
|
+
@file = Pathname.new(file).expand_path
|
91
|
+
@json = JSON.parse(File.read(@file))
|
92
|
+
else
|
93
|
+
@file = nil
|
94
|
+
@json = Hash.new
|
95
|
+
end
|
96
|
+
|
97
|
+
@debug = false
|
98
|
+
@legend = @json["legend"] || Hash.new
|
99
|
+
@name = @json["name"] || "none"
|
100
|
+
@pixels = @json["pixels"] || Array.new
|
101
|
+
@sysinfo = nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
return draw
|
106
|
+
end
|
107
|
+
|
108
|
+
def width
|
109
|
+
return @json["width"]
|
110
|
+
end
|
111
|
+
end
|
data/lib/arTTY/cache.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "json"
|
3
|
+
require "jsoncfg"
|
4
|
+
require "minitar"
|
5
|
+
require "pathname"
|
6
|
+
require "stringio"
|
7
|
+
require "typhoeus"
|
8
|
+
require "zlib"
|
9
|
+
|
10
|
+
class ArTTY::Cache < JSONConfig
|
11
|
+
extend JSONConfig::Keys
|
12
|
+
|
13
|
+
add_key("art")
|
14
|
+
add_key("version")
|
15
|
+
|
16
|
+
def art
|
17
|
+
return get_art.keys.sort
|
18
|
+
end
|
19
|
+
|
20
|
+
def download_and_extract
|
21
|
+
# Temporary working directory
|
22
|
+
new = Pathname.new(
|
23
|
+
"#{@cachedir}/#{@imagesdir}.new"
|
24
|
+
).expand_path
|
25
|
+
FileUtils.rm_rf(new) if (new && new.exist?)
|
26
|
+
|
27
|
+
# Download newest tarball
|
28
|
+
request = Typhoeus::Request.new(
|
29
|
+
[
|
30
|
+
"https://gitlab.com/mjwhitta/arTTY_images/-",
|
31
|
+
"archive/master/arTTY_images.tgz"
|
32
|
+
].join("/"),
|
33
|
+
timeout: 10
|
34
|
+
)
|
35
|
+
request.on_headers do |response|
|
36
|
+
if (response.code != 200)
|
37
|
+
raise ArTTY::Error::FailedToDownload.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
tgz = StringIO.new(request.run.body)
|
41
|
+
raise ArTTY::Error::FailedToDownload.new if (tgz.eof?)
|
42
|
+
|
43
|
+
# Extract new art
|
44
|
+
tar = Minitar::Input.new(Zlib::GzipReader.new(tgz))
|
45
|
+
tar.each do |entry|
|
46
|
+
case entry.name
|
47
|
+
when /.+\.json\s*$/
|
48
|
+
entry.name.gsub!(/^.*generated\//, "")
|
49
|
+
entry.prefix.gsub!(/^.*generated\//, "")
|
50
|
+
tar.extract_entry(new, entry)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Move new art to final location
|
55
|
+
old = Pathname.new("#{@cachedir}/#{@imagesdir}").expand_path
|
56
|
+
if (new.exist?)
|
57
|
+
FileUtils.rm_rf(old) if (old.exist?)
|
58
|
+
FileUtils.mv(new, old)
|
59
|
+
end
|
60
|
+
rescue Interrupt
|
61
|
+
raise
|
62
|
+
ensure
|
63
|
+
# Cleanup
|
64
|
+
FileUtils.rm_rf(new) if (new && new.exist?)
|
65
|
+
end
|
66
|
+
private :download_and_extract
|
67
|
+
|
68
|
+
def get_file_for(name)
|
69
|
+
return get_art[name]["file"]
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_height_for(name)
|
73
|
+
return get_art[name]["height"]
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_width_for(name)
|
77
|
+
return get_art[name]["width"]
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize(file = nil)
|
81
|
+
file ||= "#{ENV["HOME"]}/.cache/arTTY/art.json"
|
82
|
+
@defaults = {
|
83
|
+
"art" => Hash.new,
|
84
|
+
"version" => ArTTY.current_version
|
85
|
+
}
|
86
|
+
|
87
|
+
@cachefile = Pathname.new(file).expand_path
|
88
|
+
@cachedir = @cachefile.dirname
|
89
|
+
@imagesdir = "arTTY_images"
|
90
|
+
super(@cachefile, false)
|
91
|
+
|
92
|
+
refresh if (get_version != ArTTY.current_version)
|
93
|
+
end
|
94
|
+
|
95
|
+
def refresh(download = false)
|
96
|
+
download_and_extract if (download)
|
97
|
+
|
98
|
+
set_art(Hash.new)
|
99
|
+
set_version(ArTTY.current_version)
|
100
|
+
|
101
|
+
[
|
102
|
+
"#{@cachedir}/#{@imagesdir}",
|
103
|
+
"#{ENV["HOME"]}/.config/arTTY/#{@imagesdir}"
|
104
|
+
].each do |dir|
|
105
|
+
Dir["#{dir}/**/*.json"].each do |file|
|
106
|
+
img = ArTTY::Art.new(file)
|
107
|
+
get_art[img.name] = {
|
108
|
+
"file" => file,
|
109
|
+
"height" => img.height,
|
110
|
+
"width" => img.width
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
save
|
116
|
+
end
|
117
|
+
end
|
data/lib/arTTY/error.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
class ArTTY::Error < RuntimeError
|
2
|
+
end
|
3
|
+
|
4
|
+
require "arTTY/error/art_not_found"
|
5
|
+
require "arTTY/error/failed_to_download"
|
6
|
+
require "arTTY/error/image_magick_not_found"
|
7
|
+
require "arTTY/error/image_named_improperly"
|
8
|
+
require "arTTY/error/image_not_found"
|
9
|
+
require "arTTY/error/no_pixel_data_found"
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require "scoobydoo"
|
2
|
+
|
3
|
+
class ArTTY::Generator
|
4
|
+
def generate(image, name = nil)
|
5
|
+
file = Pathname.new(image).expand_path
|
6
|
+
pixels = nil
|
7
|
+
|
8
|
+
if (!file.exist?)
|
9
|
+
raise ArTTY::Error::ImageNotFound.new(file.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
file.to_s.match(%r{([^/]+?)(_(\d+)x(\d+))?\.}) do |m|
|
13
|
+
name = m[1] if (name.nil? || name.empty?)
|
14
|
+
width = m[3].nil? ? nil : m[3].to_i
|
15
|
+
height = m[4].nil? ? nil : m[4].to_i
|
16
|
+
pixels = get_pixel_info(file, width, height)
|
17
|
+
end
|
18
|
+
|
19
|
+
raise ArTTY::Error::NoPixelDataFound.new if (pixels.empty?)
|
20
|
+
|
21
|
+
legend = generate_color_map(pixels)
|
22
|
+
return generate_json(name, pixels, legend)
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_color_map(pixels)
|
26
|
+
legend = {"" => " "}
|
27
|
+
|
28
|
+
colors = pixels.flatten.uniq.sort.delete_if(&:empty?)
|
29
|
+
if (colors.length > @keys.length)
|
30
|
+
raise Exception.new(
|
31
|
+
"Too many colors: #{colors.length} > #{@keys.length}"
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
colors.zip(@keys) do |map|
|
36
|
+
legend[map[0]] = map[1]
|
37
|
+
end
|
38
|
+
|
39
|
+
return legend
|
40
|
+
end
|
41
|
+
private :generate_color_map
|
42
|
+
|
43
|
+
def generate_json(name, pixels, legend)
|
44
|
+
ret = [
|
45
|
+
"{",
|
46
|
+
" \"height\": #{(pixels.length + 1) / 2},",
|
47
|
+
" \"legend\": {",
|
48
|
+
]
|
49
|
+
|
50
|
+
stop = legend.length - 1
|
51
|
+
legend.each_with_index do |map, i|
|
52
|
+
next if (i == 0)
|
53
|
+
clr = map[0]
|
54
|
+
key = map[1]
|
55
|
+
ret.push(" \"#{key}\": \"#{clr}\"#{"," if (i < stop)}")
|
56
|
+
end
|
57
|
+
|
58
|
+
ret.concat([
|
59
|
+
" },",
|
60
|
+
" \"name\": \"#{name}\",",
|
61
|
+
" \"pixels\": ["
|
62
|
+
])
|
63
|
+
|
64
|
+
stop = pixels.length - 1
|
65
|
+
pixels.each_with_index do |row, i|
|
66
|
+
line = row.map do |color|
|
67
|
+
legend[color]
|
68
|
+
end.join
|
69
|
+
ret.push(" \"#{line}\"#{"," if (i < stop)}")
|
70
|
+
end
|
71
|
+
|
72
|
+
ret.concat([
|
73
|
+
" ],",
|
74
|
+
" \"width\": #{pixels.map(&:length).max}",
|
75
|
+
"}"
|
76
|
+
])
|
77
|
+
|
78
|
+
return ret
|
79
|
+
end
|
80
|
+
private :generate_json
|
81
|
+
|
82
|
+
def get_pixel_info(file, width = nil, height = nil)
|
83
|
+
h_increment = w_increment = 1
|
84
|
+
h_total = w_total = 0
|
85
|
+
offset = 0
|
86
|
+
pixels = Array.new
|
87
|
+
|
88
|
+
%x(convert #{file} txt:- 2>/dev/null).each_line do |line|
|
89
|
+
case line
|
90
|
+
when /^#\s*ImageMagick.+/
|
91
|
+
if (height && width)
|
92
|
+
line.match(/:\s+([0-9]+),([0-9]+),/) do |m|
|
93
|
+
h_total = m[2].to_i
|
94
|
+
h_increment = h_total.to_f / height.to_f
|
95
|
+
offset = (h_increment / 2).to_i
|
96
|
+
w_total = m[1].to_i
|
97
|
+
w_increment = w_total.to_f / width.to_f
|
98
|
+
end
|
99
|
+
end
|
100
|
+
else
|
101
|
+
line.match(/^([0-9]+),([0-9]+):\s+\S+\s+(\S+)/) do |m|
|
102
|
+
if (
|
103
|
+
((m[2].to_i % h_increment).to_i == offset) &&
|
104
|
+
((m[1].to_i % w_increment).to_i == offset)
|
105
|
+
)
|
106
|
+
c = (m[1].to_i / w_increment).to_i
|
107
|
+
r = (m[2].to_i / h_increment).to_i
|
108
|
+
|
109
|
+
clr = ""
|
110
|
+
code = "[A-Fa-f0-9]{6}"
|
111
|
+
vis = "[A-Fa-f4-9][A-Fa-f0-9]"
|
112
|
+
|
113
|
+
m[3].match(/^#?(#{code})(#{vis})?$/) do |hex|
|
114
|
+
clr = Hilighter.hex_to_x256(hex[1])
|
115
|
+
end
|
116
|
+
|
117
|
+
pixels.push(Array.new) if (pixels.length <= r)
|
118
|
+
pixels[r].push(clr) if (pixels[r].length <= c)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
return pixels
|
125
|
+
end
|
126
|
+
private :get_pixel_info
|
127
|
+
|
128
|
+
def initialize
|
129
|
+
if (ScoobyDoo.where_are_you("convert").nil?)
|
130
|
+
raise ArTTY::Error::ImageMagickNotFound.new
|
131
|
+
end
|
132
|
+
|
133
|
+
@keys = Array.new
|
134
|
+
key = "!"
|
135
|
+
while key != "~" do
|
136
|
+
@keys.push(key.clone)
|
137
|
+
case key
|
138
|
+
when "!"
|
139
|
+
key = "#"
|
140
|
+
when "9"
|
141
|
+
key = ":"
|
142
|
+
when "Z"
|
143
|
+
key = "["
|
144
|
+
when "["
|
145
|
+
key = "]"
|
146
|
+
when "_"
|
147
|
+
key = "a"
|
148
|
+
when "z"
|
149
|
+
key = "{"
|
150
|
+
else
|
151
|
+
key.next!
|
152
|
+
end
|
153
|
+
end
|
154
|
+
@keys.push("~")
|
155
|
+
end
|
156
|
+
end
|