arTTY 0.9.48
Sign up to get free protection for your applications and to get access to all the features.
- 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
|