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.
@@ -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