Module: Termclock

Defined in:
lib/termclock.rb,
lib/termclock/start.rb,
lib/termclock/hex2rgb.rb,
lib/termclock/version.rb,
lib/termclock/translate.rb,
lib/termclock/center_puts.rb,
lib/termclock/system_info.rb,
lib/termclock/parse_characters.rb

Defined Under Namespace

Modules: ParseCharacters

Constant Summary collapse

GC_COMPACT_TIME =
7200
CLEAR =
"\e[H\e[2J\e[3J".freeze
ANTIFLICKER =
"\e[J\e[1;1H".freeze
NEWLINE =
?\n.freeze
SPACE =
?\s.freeze
TAB =
?\t.freeze
EMPTY =
''.freeze
EPSILON =
5.0e-07
LANGS =

All languages

%i(
	bn en es fr hi it ru
)
LANG =
_lang.freeze
TRANSLATION_FILES =

Load translations

{}
TRANSLATIONS =
if translation_file && File.readable?(translation_file)
	begin
		JSON.parse(IO.read(translation_file))
	rescue StandardError
		center_puts "Can't Parse Translation File!"
		sleep 0.5
		{}
	end
else
	{}
end
VERSION =
"1.0.2"
@@cpu_usage =
0
@@cpu_usage_t =
Thread.new { }.join
@@current_net_usage =
"\u{1F4CA} #{_tr}:"
@@current_net_usage_t =
Thread.new { }.join

Class Method Summary collapse

Class Method Details

.center_puts(message) ⇒ Object



2
3
4
5
6
7
# File 'lib/termclock/center_puts.rb', line 2

def self.center_puts(message)
	winsize = STDOUT.winsize

	puts "\e[2J\e[H\e[3J\e[#{winsize[0] / 2}H"\
	"#{?\s.*(winsize[1] / 2 - message.bytesize / 2)}#{message}"
end

.hex2rgb(hex) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/termclock/hex2rgb.rb', line 2

def self.hex2rgb(hex)
	colour = hex.dup.to_s
	colour.strip!
	colour.downcase!
	colour[0] = EMPTY if colour[0] == ?#.freeze

	# out of range
	oor = colour.scan(/[^a-f0-9]/)

	unless oor.empty?
		invalids = colour.chars.map { |x|
			oor.include?(x) ? "\e[1;31m#{x}\e[0m" : x
		}.join

		puts "Hex Colour ##{invalids} is Out of Range"
		raise ArgumentError
	end

	clen = colour.length
	if clen == 3
		colour.chars.map { |x| x.<<(x).to_i(16) }
	elsif clen == 6
		colour.chars.each_slice(2).map { |x| x.join.to_i(16) }
	else
		sli = clen > 6 ? 'too long'.freeze : clen < 3 ? 'too short'.freeze : 'invalid'.freeze
		raise ArgumentError, "Invalid Hex Colour ##{colour} (length #{sli})"
	end
end

.start(colour1, colour2, colour3, colour4, textcolour1 = nil, textcolour2 = nil, refresh: 0.1, bold: false, italic: false, print_info: true, print_message: true, print_date: true, time_format: "%H %M %S %2N", date_format: '%a, %d %B %Y', no_logo: false, anti_flicker: false, logo_colour: [Termclock.hex2rgb('ff0'), Termclock.hex2rgb('f55'), Termclock.hex2rgb('55f')]) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/termclock/start.rb', line 2

def self.start(colour1, colour2, colour3, colour4,
	textcolour1 = nil, textcolour2 = nil,
	refresh: 0.1,
	bold: false,
	italic: false,
	print_info: true, print_message: true,
	print_date: true,
	time_format: "%H %M %S %2N",
	date_format: '%a, %d %B %Y',
	no_logo: false,
	anti_flicker: false,
	logo_colour: [Termclock.hex2rgb('ff0'), Termclock.hex2rgb('f55'), Termclock.hex2rgb('55f')]
	)

	clear_character = anti_flicker ? ANTIFLICKER : CLEAR

	generate = proc do |start, stop, n = 5|
		r_op = r_val = nil
		ary = []

		if start > stop
			r_op, r_val = :-, start.-(stop).fdiv(n - 1)
		elsif start < stop
			r_op, r_val = :+, stop.-(start).fdiv(n - 1)
		end

		_r = r_op ? start.send(r_op, r_val * -1) : start
		n.times {
			_r = _r.send(r_op, r_val) if r_op
			ary << _r.clamp(0, 255).to_i
		}

		ary
	end

	gc_compact, gc_compacted = GC.respond_to?(:compact), Time.now.to_i + GC_COMPACT_TIME

	r1, g1, b1 = *colour1
	r2, g2, b2 = *colour2
	r3, g3, b3 = *colour3
	r4, g4, b4 = *colour4

	# colour1 -> colour3
	rs1 = generate.(r1, r3)
	gs1 = generate.(g1, g3)
	bs1 = generate.(b1, b3)

	# colour2 -> colour4
	rs2 = generate.(r2, r4)
	gs2 = generate.(g2, g4)
	bs2 = generate.(b2, b4)

	# All Gradient Colours
	colours = [rs1, gs1, bs1].transpose.zip([rs2, gs2, bs2].transpose)
	colours.unshift(colours[0])
	colours.push(colours[-1])

	# Text colours
	tc1 = textcolour1 ? hex2rgb(textcolour1) : hex2rgb('5555ff')
	tc2 = textcolour2 ? hex2rgb(textcolour2) : hex2rgb('3ce3b5')

	message_counter = -1
	message = ''
	message_final = ''
	message_align = 0
	message_temp = ''

	date, info = '', ''

	clock_emoji = ?\u{1F550}.ord.-(1).chr('utf-8')
	clock_emoji = 12.times.map { clock_emoji.next!.dup }

	braille = %W(\u2821 \u2811 \u2812 \u280A \u280C)

	get_message = proc {
		braille.rotate!
		braille_0 = braille[0]

		case Time.now.hour
		when 5...12
			_m = translate('Good Morning')
			"\u{1F304} #{braille_0} #{_m} #{braille_0} \u{1F304}"
		when 12...16
			_m = translate('Good Afternoon')
			"\u26C5 #{braille_0} #{_m} #{braille_0} \u26C5"
		when 16...18
			_m = translate('Good Evening')
			"\u{1F307} #{braille_0} #{_m} #{braille_0} \u{1F307}"
		when 18...20
			_m = translate('Good Evening')
			"\u{1F31F} #{braille_0} #{_m} #{braille_0} \u{1F31F}"
		when 20...24
			_m = translate('Good Night')
			"\u{1F303} #{braille_0} #{_m} #{braille_0} \u{1F303}"
		else
			_m = translate('Good Night')
			"\u{2728} #{braille_0} #{_m} #{braille_0} \u{2728}"
		end
	}

	version = "Termclock v#{Termclock::VERSION}"

	v_col1 = logo_colour[0]
	v_col2 = logo_colour[1]
	v_col3 = logo_colour[2]

	vl_2 = version.length / 2
	_term_clock_v = version[0...vl_2].gradient(v_col1, v_col2, underline: true, bold: bold, italic: italic) <<
		version[vl_2..-1].gradient(v_col2, v_col3, underline: true, bold: bold, italic: italic)

	term_clock_v = ''

	chop_message = 0
	deviation = 0

	time_seperators = [?:, ?$]
	time_seperator = time_seperators[0]
	point_five_tick = -0.5

	height, width = *STDOUT.winsize
	height2, width2 = *STDOUT.winsize

	print CLEAR

	while true
		monotonic_time_1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
		time_now = Time.now
		height, width = *STDOUT.winsize

		if time_now.to_f > point_five_tick
			point_five_tick = time_now.to_f + 0.5
			time_seperators.rotate!
			clock_emoji.rotate!
			time_seperator = time_seperators[0]
		end

		unless 
			term_clock_v = "\e[#{height}H #{clock_emoji[0]} #{_term_clock_v} \e[0m"
		end

		if print_message
			message_temp = get_message.call
			message_counter += 1
			message_length = message.length

			if (width - message_counter % width < 8)
				unless chop_message == message_temp.length
					chop_message += 1
					message_counter -= 1
					message_align -= 1

					_chopped = message_temp[chop_message..-1]
					message.replace(_chopped) if _chopped
				end
			else
				chop_message = 0 unless chop_message == 0
				message.clear if width - message_counter % width == width
				message_align = width - message_counter % width + message_length - 4

				if message_temp != message
					if message_length < message_temp.length
						message.replace(message_temp[0..message_length])
					else
						message.replace(message_temp)
					end
				end
			end

			message_final = message.rjust(message_align).gradient(
				tc1, tc2, exclude_spaces: true, bold: bold, italic: italic
			)
		end

		info = system_info(width, tc1, tc2, bold, italic) if print_info

		if print_date
			_date = time_now.strftime(date_format)

			unless LANG == :en
				_date = _date.split(/(\W)/).map { |x|
					translate(
						x,
						breakword: !x[/[^0-9]/]
					)
				}.join
			end

			date = _date.center(width)
				.gradient(tc1, tc2, bold: bold, italic: italic, exclude_spaces: true)
		end

		time = time_now.strftime(time_format).split.join(time_seperator)
		art = Termclock::ParseCharacters.display(time).lines

		art_aligned = art.each_with_index do |x, i|
			chomped = x.chomp(EMPTY).+(NEWLINE)
			gr = chomped.gradient(*colours[i], bold: bold, italic: italic)

			w = width./(2.0).-(chomped.length / 2.0) + 2
			w = 0 if w < 0

			x.replace(SPACE.*(w) + gr)
		end.join

		vertical_gap = "\e[#{height./(2.0).-(art.length / 2.0).to_i + 1}H"
		final_output = "#{info}#{vertical_gap}#{art_aligned}\n#{date}\n\n\e[K#{message_final}#{term_clock_v}"

		if anti_flicker && (height != height2 || width != width2)
			height2, width2 = *STDOUT.winsize
			print CLEAR
		end

		print "#{clear_character}#{final_output}"

		if gc_compact && time_now.to_i > gc_compacted
			GC.compact
			gc_compacted = time_now.to_i + GC_COMPACT_TIME
		end

		monotonic_time_2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
		time_diff = monotonic_time_2 - monotonic_time_1
		sleep_time = refresh.-(time_diff + deviation)
		sleep_time = 0 if sleep_time < 0

		sleep(sleep_time)
		deviation = Process.clock_gettime(Process::CLOCK_MONOTONIC).-(monotonic_time_2 + sleep_time + EPSILON)
	end
end

.system_info(width, tc1, tc2, bold, italic) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/termclock/system_info.rb', line 18

def system_info(width, tc1, tc2, bold, italic)
	unless @@cpu_usage_t.alive?
		@@cpu_usage_t = Thread.new {
			_cpu_usage = LS::CPU.usage(0.25)
			@@cpu_usage = _cpu_usage ? "%0.2f".freeze % _cpu_usage : nil
		}
	end

	unless @@current_net_usage_t.alive?
		@@current_net_usage_t = Thread.new do
			_m = LS::Net.current_usage(0.25)

			_dl = _m[:received]
			_ul = _m[:transmitted]

			@@current_net_usage = if _dl && _ul
				_tr = translate('Curr. DL/UL')
				dl = LS::PrettifyBytes.convert_short_decimal(_dl)
				ul = LS::PrettifyBytes.convert_short_decimal(_ul)

				"\u{1F4CA} #{_tr}: #{t!("%-9s" % dl)} | #{t!("%9s" % ul)}"
			else
				EMPTY
			end
		end
	end

	cpu = if @@cpu_usage
		_tr = translate('CPU')
		 "\u{1F9E0} #{_tr}: #{t!("%6s" % @@cpu_usage)}% (#{t!(LS::CPU.count_online)}/#{t!(LS::CPU.count)})"
	else
		EMPTY
	end

	battery = if LS::Battery.present?
		stat = LS::Battery.stat
		charge = stat[:charge].to_i

		emoji, plug = "\u{1F50B}".freeze, EMPTY

		if LS::Battery.charging?
			emoji, plug = "\u{1F4A1}".freeze, "\u{1F50C} ".freeze
		end

		lives = "\u2665 ".freeze.*(charge.fdiv(20).ceil).chop

		_tr = translate('Battery')
		"#{emoji} #{_tr}: #{t!(charge)}% #{lives} (#{plug}#{translate(stat[:status])})"
	else
		EMPTY
	end

	_tr = translate('User')
	user = "\u{1F481} #{_tr}: #{LS::User.get_current_user.capitalize}"

	_tr = translate('Hostname')
	hostname = "\u{1F4BB} #{_tr}: #{LS::OS.hostname}"

	_tr = translate('IP Addr')
	_m = LS::Net.total_bytes
	ip = "\u{1F30F} #{_tr}: #{translate(LS::Net.ipv4_private, b: true)}"

	_received = _m[:received]
	_transmitted = _m[:transmitted]

	_tr = translate('Totl. DL/UL')
	tot_received = _received ? "\u{1F4C8} #{_tr}: #{t!('%-9s'.freeze % LS::PrettifyBytes.convert_short_decimal(_m[:received]))}" : nil
	tot_transmitted = _transmitted ? " | #{t!('%9s'.freeze % LS::PrettifyBytes.convert_short_decimal(_transmitted))}" : nil

	net_usage = if tot_received && tot_transmitted
		tot_received + tot_transmitted
	else
		EMPTY
	end

	_m = LS::Memory.stat
	_m.default = 0

	_tr = translate('Mem')
	memory = "\u{1F3B0} #{_tr}: #{t!(LS::PrettifyBytes.convert_short_decimal(_m[:used] * 1000))}"\
	" / #{t!(LS::PrettifyBytes.convert_short_decimal(_m[:total] * 1000))}"\
	" (#{t!("%.2f" % _m[:percent_used])}%)"

	_m = LS::Swap.stat
	_m.default = 0

	_tr = translate('Swap')
	swap = "\u{1F300} #{_tr}: #{t!(LS::PrettifyBytes.convert_short_decimal(_m[:used] * 1000))}"\
	" / #{t!(LS::PrettifyBytes.convert_short_decimal(_m[:total] * 1000))}"\
	" (#{t!("%.2f" % _m[:percent_used])}%)"

	_m = LS::Filesystem.stat(FILESYSTEM)
	_m.default = 0

	_tr = translate('FS')
	fs = "\u{1F4BD} #{_tr} (#{FILESYSTEM_LABEL}): #{t!(LS::PrettifyBytes.convert_short_decimal(_m[:used]))}"\
	" / #{t!(LS::PrettifyBytes.convert_short_decimal(_m[:total]))}"\
	" (#{t!("%.2f" % _m[:used].*(100).fdiv(_m[:total]).round(2))}%)"

	pt = LS::Process.types.values

	process = if pt.length > 0
		_tr = translate('Process')

		"\u{1F3ED} #{_tr}: T:#{t!("%4s" % pt.length)}|"\
		"R:#{"%3s" % t!(pt.count(:running))}|"\
		"S:#{"%3s" % t!(pt.count(:sleeping))}|"\
		"I:#{"%3s" % t!(pt.count(:idle))}"
	else
		EMPTY
	end

	@@os_v ||= unless LS::OS.version.empty?
		" (#{LS::OS.version})"
	else
		EMPTY
	end

	_tr = translate('Distrib')
	@@os ||= "\u{1F427} #{_tr}: #{LS::OS.distribution} #{LS::OS.machine}#{@@os_v}"

	_temp_uptime = LS::OS.uptime

	_uptime = unless _temp_uptime.empty?
		_temp_uptime
	else
		_u = LS::OS.uptime_i
		{
			hour: _u / 3600,
			minute: _u % 3600 / 60,
			second: _u % 3600 % 60,
			jiffy: 0
		}
	end

	_second = _uptime[:second]
	_second_i = _second.to_i

	hour = "%02d" % _uptime[:hour]
	minute = "%02d" % _uptime[:minute]
	second = "%02d" % _uptime[:second]
	jiffy = "%02d" % _uptime[:jiffy]

	_tr = translate('Uptime')
	uptime = "\u{1F3A1} #{_tr}: #{t! hour}:#{t! minute}:#{t! second}:#{t! jiffy} (#{t! LS::OS.uptime_i}s)"

	_tr = translate('LoadAvg')
	_loadavg = LS::Sysinfo.loads.map! { |x| "%.2f" % x }
	loadavg = "\u{1F525} #{_tr}: 1m #{translate(_loadavg[0], b: true)}|5m #{translate(_loadavg[1], b: true)}|15m #{translate(_loadavg[2], b: true)}"

	all_info = []
	max_l = 0
	i = -1

	[
		user, hostname,
		@@os, battery,
		cpu, ip,
		memory, @@current_net_usage,
		swap, net_usage,
		fs, process,
		uptime, loadavg
	].each { |x|
		unless x.empty?
			all_info << x
			i += 1

			if i.odd?
				_x_len = x.length
				max_l = _x_len if max_l < _x_len
			end
		end
	}

	max_l += 4

	all_info.each_slice(2).map { |x, y|
		_diff = width.-(x.length + max_l)
		_diff = 0 if _diff < 1
		y_to_s = y.to_s

		padding = "#{SPACE * _diff}"
		str = SPACE + x + padding + y_to_s

		grads = SPACE + x.gradient(tc1, tc2, bold: bold, italic: italic) +
			padding +
			y_to_s.gradient(tc1, tc2, bold: bold, italic: italic)

		len = str.grapheme_clusters.map { |x|
			_x = x.bytesize./(2)
			_x == 0 ? 1 : _x
		}.sum

		w = width - 2

		len < w ? grads.+(SPACE.*(w - len)) : grads
	}.join(NEWLINE)
end

.t!(keyword, breakword: true, b: true) ⇒ Object



57
58
59
60
# File 'lib/termclock/translate.rb', line 57

def self.t!(keyword, breakword: true, b: true)
	return keyword if LANG == :en
	translate(keyword.to_s, breakword: true)
end

.translate(keyword, breakword: nil, b: nil) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/termclock/translate.rb', line 2

def self.translate(keyword, breakword: nil, b: nil)
	return keyword if LANG == :en
	characters = keyword.grapheme_clusters

	b = breakword if breakword && !b
	breakword = b if b && !breakword

	upcased = characters.all? { |x| x.ord.between?(65, 90) }
	downcased = upcased ? false : characters.all? { |x| x.ord.between?(97, 122) }
	capitalized = if (upcased || downcased)
		false
	else
		characters[0].ord.between?(65, 90) &&
			characters.drop(1).all? { |x| x.ord.between?(97, 122) }
	end

	camelized = if (upcased || downcased || capitalized)
		false
	else
		keyword.split(?\s.freeze).all? { |x|
			x[0].ord.between?(65, 90) && x.chars.drop(1).all? { |y| y.ord.between?(97, 122) }
		}
	end

	if breakword
		return characters.map { |x|
			tr = TRANSLATIONS[x]

			if !tr
				tr = TRANSLATIONS[x.downcase]
			end

			tr.upcase! if tr && upcased
			tr.downcase! if tr && downcased
			tr.capitalize! if tr && capitalized
			tr.camelize! if tr && camelized

			tr ? tr : x
		}.join
	end

	tr = TRANSLATIONS[keyword]

	if !tr
		tr = TRANSLATIONS[keyword.downcase]

		tr.upcase! if tr && upcased
		tr.downcase! if tr && downcased
		tr.capitalize! if tr && capitalized
		tr.camelize! if tr && camelized
	end

	tr ? tr : keyword
end