arTTY 0.9.48

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"
@@ -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
@@ -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
@@ -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,6 @@
1
+ class ArTTY::Error::ArtNotFound < ArTTY::Error
2
+ def initialize(art = nil)
3
+ super("Art not found: #{art}") if (art && !art.empty?)
4
+ super("Art not found") if (art.nil?)
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class ArTTY::Error::FailedToDownload < ArTTY::Error
2
+ def initialize
3
+ super("Failed to download arTTY_images.tgz")
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class ArTTY::Error::ImageMagickNotFound < ArTTY::Error
2
+ def initialize
3
+ super("ImageMagick not found")
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class ArTTY::Error::ImageNamedImproperly < ArTTY::Error
2
+ def initialize(fn = nil)
3
+ super("Image named wrong: #{fn}") if (fn && !fn.empty?)
4
+ super("Image named wrong") if (fn.nil?)
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class ArTTY::Error::ImageNotFound < ArTTY::Error
2
+ def initialize(image = nil)
3
+ super("Image not found: #{image}") if (image && !image.empty?)
4
+ super("Image not found") if (image.nil?)
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class ArTTY::Error::NoPixelDataFound < ArTTY::Error
2
+ def initialize
3
+ super("No pixel data was found")
4
+ end
5
+ end
@@ -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