-
Notifications
You must be signed in to change notification settings - Fork 5
/
eshell-info-banner.el
923 lines (801 loc) · 40.7 KB
/
eshell-info-banner.el
1
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
;;; eshell-info-banner.el --- System information as your Eshell banner -*- lexical-binding: t -*-
;; Author: Lucien Cartier-Tilet <lucien@phundrak.com>
;; Maintainer: Lucien Cartier-Tilet <lucien@phundrak.com>
;; Version: 0.8.8
;; Package-Requires: ((emacs "25.1") (s "1"))
;; Homepage: https://github.com/Phundrak/eshell-info-banner.el
;; This file is not part of GNU Emacs
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; `eshell-info-banner' is a utility for creating an informative
;; banner akin to fish_greeting if fish shell but for Eshell. It can
;; provide information on:
;; - the OS’ name
;; - the OS’ kernel
;; - the hostname
;; - the uptime
;; - the system’s memory usage (RAM, swap, disk)
;; - the battery status
;; It can be TRAMP-aware or not, depending on the user’s preferences.
;;; Code:
(require 'cl-lib)
(require 's)
(require 'em-banner)
(require 'json)
(require 'seq)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Group ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defgroup eshell-info-banner ()
"System information as your Eshell banner."
:group 'eshell
:prefix "eshell-info-banner-"
:link '(url-link :tag "Gitea" "https://labs.phundrak.com/phundrak/eshell-info-banner.el")
:link '(url-link :tag "Github" "https://github.com/Phundrak/eshell-info-banner.el"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Constants ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defconst eshell-info-banner-path-separator
(substring-no-properties (file-relative-name (expand-file-name "x" "y")) 1 2)
"File separator used by the current operating system.")
(defconst eshell-info-banner--min-length-left 8
"Minimum length of text on the left hand side of the banner.")
(eval-when-compile
(defconst eshell-info-banner--macos-versions
'(("^10\\.0\\." . "Mac OS X Cheetah")
("^10\\.1\\." . "Mac OS X Puma")
("^10\\.2\\." . "Mac OS X Jaguar")
("^10\\.3\\." . "Mac OS X Panther")
("^10\\.4\\." . "Mac OS X Tiger")
("^10\\.5\\." . "Mac OS X Leopard")
("^10\\.6\\." . "Mac OS X Snow Leopard")
("^10\\.7\\." . "Mac OS X Lion")
("^10\\.8\\." . "OS X Mountain Lion")
("^10\\.9\\." . "OS X Mavericks")
("^10\\.10\\." . "OS X Yosemite")
("^10\\.11\\." . "OS X El Capitan")
("^10\\.12\\." . "macOS Sierra")
("^10\\.13\\." . "macOS High Sierra")
("^10\\.14\\." . "macOS Mojave")
("^10\\.15\\." . "macOS Catalina")
("^10\\.16\\." . "macOS Big Sur")
("^11\\." . "macOS Big Sur")
("^12\\." . "macOS Monterey"))
"Versions of OSX and macOS and their name."))
(defconst eshell-info-banner--posix-shells '("bash" "zsh" "sh")
"List of POSIX-compliant shells to run external commands through.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Custom variables ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defcustom eshell-info-banner-tramp-aware t
"Make `eshell-info-banner' TRAMP aware."
:group 'eshell-info-banner
:type 'boolean
:safe #'booleanp)
(defcustom eshell-info-banner-shorten-path-from 7
"From which length should a path be shortened?"
:group 'eshell-info-banner
:type 'integer
:safe #'integer-or-marker-p)
(defcustom eshell-info-banner-width 80
"Width of the info banner to be shown in Eshell."
:group 'eshell-info-banner
:type 'integer
:safe #'integer-or-marker-p)
(defcustom eshell-info-banner-progress-bar-char "="
"Character to fill the progress bars with."
:group 'eshell-info-banner
:type 'string
:safe #'stringp)
(defcustom eshell-info-banner-warning-percentage 75
"When to warn about a percentage."
:group 'eshell-info-banner
:type 'float
:safe #'floatp)
(defcustom eshell-info-banner-critical-percentage 90
"When a percentage becomes critical."
:group 'eshell-info-banner
:type 'float
:safe #'floatp)
(defcustom eshell-info-banner-partition-prefixes '("/dev")
"List of prefixes for detecting which partitions to display."
:group 'eshell-info-banner
:type 'list)
(defcustom eshell-info-banner-filter-duplicate-partitions nil
"Whether to filter duplicate partitions.
Two partitions are considered duplicate if they have the same
size and amount of space used."
:group 'eshell-info-banner
:type 'boolean)
(defcustom eshell-info-banner-exclude-partitions nil
"List of patterns to exclude from the partition list.
Patterns are matched against the partition name with
`string-match-p'."
:group 'eshell-info-banner
:type '(repeat string))
(defmacro eshell-info-banner--executable-find (program)
"Find PROGRAM executable, possibly on a remote machine.
This is a wrapper around `executable-find' in order to avoid
issues with older versions of the functions only accepting one
argument. `executable-find'’s remote argument has the value of
`eshell-info-banner-tramp-aware'."
(if (version< emacs-version "27.1")
`(let ((default-directory (if eshell-info-banner-tramp-aware
default-directory
"~")))
(executable-find ,program))
`(executable-find ,program eshell-info-banner-tramp-aware)))
(defcustom eshell-info-banner-duf-executable "duf"
"Path to the `duf' executable."
:group 'eshell-info-banner
:type 'string
:safe #'stringp)
(defcustom eshell-info-banner-use-duf
(if (eshell-info-banner--executable-find eshell-info-banner-duf-executable)
t
nil)
"If non-nil, use `duf' instead of `df'."
:group 'eshell-info-banner
:type 'boolean
:safe #'booleanp)
(defcustom eshell-info-banner-file-size-flavor nil
"Display sizes with IEC prefixes."
:group 'eshell-info-banner
:type '(radio (const :tag "Default" nil)
(const :tag "SI" si)
(const :tag "IEC" iec)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Faces ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defface eshell-info-banner-background-face
'((t :inherit font-lock-comment-face))
"Face for \"empty\" part of progress bars."
:group 'eshell-info-banner)
(defface eshell-info-banner-normal-face
'((t :inherit font-lock-string-face))
"Face for `eshell-info-banner' progress bars displaying acceptable levels."
:group 'eshell-info-banner)
(defface eshell-info-banner-warning-face
'((t :inherit warning))
"Face for `eshell-info-banner' progress bars displaying high levels."
:group 'eshell-info-banner)
(defface eshell-info-banner-critical-face
'((t :inherit error))
"Face for `eshell-info-banner' progress bars displaying critical levels."
:group 'eshell-info-banner)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Macros and Utilities ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro eshell-info-banner--with-face (str &rest properties)
"Helper macro for applying face PROPERTIES to STR."
`(propertize ,str 'face (list ,@properties)))
(defun eshell-info-banner--shell-command-to-string (command)
"Execute shell command COMMAND and return its output as a string.
Ensures the command is ran with LANG=C."
(let ((shell (or (seq-find (lambda (shell)
(eshell-info-banner--executable-find shell))
eshell-info-banner--posix-shells)
"sh")))
(with-temp-buffer
(let ((default-directory (if eshell-info-banner-tramp-aware default-directory "~")))
(process-file shell nil t nil "-c" (concat "LANG=C " command))
(buffer-string)))))
(defun eshell-info-banner--progress-bar-without-prefix (bar-length used total &optional newline)
"Display a progress bar without its prefix.
Display a progress bar of BAR-LENGTH length, followed by an
indication of how full the memory is with a human readable USED
and TOTAL size.
Optional argument NEWLINE: Whether to output a newline at the end
of the progress bar."
(let ((percentage (if (= used 0)
0
(/ (* 100 used) total))))
(concat (eshell-info-banner--progress-bar bar-length percentage)
(format (if (equal eshell-info-banner-file-size-flavor 'iec)
" %8s / %-8s (%3s%%)%s"
" %6s / %-6s (%3s%%)%s")
(file-size-human-readable used eshell-info-banner-file-size-flavor)
(file-size-human-readable total eshell-info-banner-file-size-flavor)
(eshell-info-banner--with-face
(number-to-string percentage)
:inherit (eshell-info-banner--get-color-percentage percentage))
(if newline "\n" "")))))
(defun eshell-info-banner--string-repeat (str times)
"Repeat STR for TIMES times."
(declare (pure t) (side-effect-free t))
(let (result)
(cl-dotimes (_ times)
(setq result (cons str result)))
(apply #'concat result)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Internal functions ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Misc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun eshell-info-banner--get-uptime ()
"Get uptime of machine if `uptime' is available.
If the executable `uptime' is not found, return nil."
(when (eshell-info-banner--executable-find "uptime")
(let ((uptime-str (eshell-info-banner--shell-command-to-string "uptime -p")))
(if (not (seq-some (lambda (keyword)
(string-match-p keyword uptime-str))
'("invalid" "illegal" "unknown")))
(s-chop-prefix "up " (s-trim uptime-str))
(let ((uptime-str (eshell-info-banner--shell-command-to-string "uptime")))
(save-match-data
(string-match "[^,]+up *\\([^,]+\\)," uptime-str)
(s-trim (substring-no-properties uptime-str
(match-beginning 1)
(match-end 1)))))))))
; Partitions ;;;;;;;;;;;;;;;;;;;;;;;;;;
(cl-defstruct eshell-info-banner--mounted-partitions
"Object representing a mounted partition found in the system."
path size used percent)
(defun eshell-info-banner--get-longest-path (partitions)
"Return the length of the longest partition path in PARTITIONS.
The returned value is in any case greater than
`eshell-info-banner--min-length-left'."
(let ((length eshell-info-banner--min-length-left))
(dolist (partition partitions length)
(setf length (max length
(length (eshell-info-banner--mounted-partitions-path partition)))))))
(defun eshell-info-banner--abbr-path (path &optional abbr)
"Remove `$HOME' from PATH, abbreviate parent dirs if ABBR non nil.
Abbreviate PATH by removing the value of HOME if it is present in
the former, and if ABBR is t then all parent directories of the
current PATH are abbreviated to only one character. If an
abbreviated directory starts with a dot, then include it before
the abbreviated name of the directory, e.g. \".config\" ->
\".c\".
For public use, PATH should be a string representing a UNIX path.
For internal use, PATH can also be a list. If PATH is neither of
these, an error will be thrown by the function."
(cond
((stringp path)
(let ((abbr-path (abbreviate-file-name path)))
(if abbr
(abbreviate-file-name
(eshell-info-banner--abbr-path
(split-string abbr-path eshell-info-banner-path-separator t)))
abbr-path)))
((null path) "")
((listp path)
(let ((file (eshell-info-banner--abbr-path (cdr path)))
(directory (if (= (length path) 1)
(car path)
(let* ((dir (car path))
(first-char (substring dir 0 1)))
(if (string= "." first-char)
(substring dir 0 2)
first-char)))))
(if (string= "" file)
directory
(let ((relative-p (not (file-name-absolute-p directory)))
(new-dir (expand-file-name file directory)))
(if relative-p
(file-relative-name new-dir)
new-dir)))))
(t (error "Invalid argument %s, neither stringp or listp" path))))
(defun eshell-info-banner--get-mounted-partitions-duf ()
"Detect mounted partitions on systems supporting `duf'.
Return detected partitions as a list of structs. See
`eshell-info-banner-partition-prefixes' to see how partitions are
chosen. Relies on the `duf' command."
(let* ((partitions (json-read-from-string (with-temp-buffer
(call-process "duf" nil t nil "-json")
(buffer-string))))
(partitions (cl-remove-if-not (lambda (partition)
(let ((device (format "%s" (cdr (assoc 'device partition)))))
(seq-some (lambda (prefix)
(string-prefix-p prefix device t))
eshell-info-banner-partition-prefixes)))
(seq-into-sequence partitions))))
(mapcar (lambda (partition)
(let* ((mount-point (format "%s" (cdr (assoc 'mount_point partition))))
(total (cdr (assoc 'total partition)))
(used (cdr (assoc 'used partition)))
(percent (/ (* 100 used) total)))
(make-eshell-info-banner--mounted-partitions
:path (if (> (length mount-point) eshell-info-banner-shorten-path-from)
(eshell-info-banner--abbr-path mount-point t)
mount-point)
:size total
:used used
:percent percent)))
partitions)))
(defun eshell-info-banner--get-mounted-partitions-df (mount-position)
"Get mounted partitions through df.
Common function between
`eshell-info-banner--get-mounted-partitions-gnu' and
`eshell-info-banner--get-mounted-partitions-darwin' which would
otherwise differ solely on the position of the mount point in the
partition list. Its position is given by the argument
MOUNT-POSITION."
(let ((partitions (cdr (split-string (eshell-info-banner--shell-command-to-string "df -l -k")
(regexp-quote "\n")
t))))
(cl-remove-if #'null
(mapcar (lambda (partition)
(let* ((partition (split-string partition " " t))
(filesystem (nth 0 partition))
(size (* (string-to-number (nth 1 partition)) 1024))
(used (* (string-to-number (nth 2 partition)) 1024))
(percent (nth 4 partition))
(mount (nth mount-position partition)))
(when (seq-some (lambda (prefix)
(string-prefix-p prefix filesystem t))
eshell-info-banner-partition-prefixes)
(make-eshell-info-banner--mounted-partitions
:path (if (> (length mount) eshell-info-banner-shorten-path-from)
(eshell-info-banner--abbr-path mount t)
mount)
:size size
:used used
:percent (string-to-number
(s-chop-suffix "%" percent))))))
partitions))))
(defun eshell-info-banner--get-mounted-partitions-gnu ()
"Detect mounted partitions on a Linux system.
Return detected partitions as a list of structs. See
`eshell-info-banner-partition-prefixes' to see how partitions are
chosen. Relies on the `df' command."
(eshell-info-banner--get-mounted-partitions-df 5))
(defun eshell-info-banner--get-mounted-partitions-windows ()
"Detect mounted partitions on a Windows system.
Return detected partitions as a list of structs. See
`eshell-info-banner-partition-prefixes' to see how partitions are
chosen."
(progn
(warn "Partition detection for Windows and DOS not yet supported.")
nil))
(defun eshell-info-banner--get-mounted-partitions-darwin ()
"Detect mounted partitions on a Darwin/macOS system.
Return detected partitions as a list of structs. See
`eshell-info-banner-partition-prefixes' to see how partitions are
chosen. Relies on the `df' command."
(eshell-info-banner--get-mounted-partitions-df 8))
(defun eshell-info-banner--get-mounted-partitions-1 ()
"Detect mounted partitions on the system.
Return detected partitions as a list of structs."
(if eshell-info-banner-use-duf
(eshell-info-banner--get-mounted-partitions-duf)
(pcase system-type
((or 'gnu 'gnu/linux 'gnu/kfreebsd 'berkeley-unix)
(eshell-info-banner--get-mounted-partitions-gnu))
((or 'ms-dos 'windows-nt 'cygwin)
(eshell-info-banner--get-mounted-partitions-windows))
('darwin
(eshell-info-banner--get-mounted-partitions-darwin))
(other
(progn
(warn "Partition detection for %s not yet supported." other)
nil)))))
(defun eshell-info-banner--get-mounted-partitions ()
"Detect mounted partitions on the system.
Take `eshell-info-banner-filter-duplicate-partitions' and
`eshell-info-banner-exclude-partitions' into account."
(let ((partitions (eshell-info-banner--get-mounted-partitions-1)))
(when eshell-info-banner-filter-duplicate-partitions
(setq partitions
(cl-loop for partition in partitions
with used = nil
for signature =
(format "%d-%d"
(eshell-info-banner--mounted-partitions-size partition)
(eshell-info-banner--mounted-partitions-used partition))
unless (member signature used)
collect partition and do (push signature used))))
(when eshell-info-banner-exclude-partitions
(setq partitions
(seq-filter (lambda (partition)
(let ((path (eshell-info-banner--mounted-partitions-path
partition)))
(not (seq-some
(lambda (pattern)
(string-match-p pattern path))
eshell-info-banner-exclude-partitions))))
partitions)))
partitions))
(defun eshell-info-banner--partition-to-string (partition text-padding bar-length)
"Display a progress bar showing how full a PARTITION is.
For TEXT-PADDING and BAR-LENGTH, see the documentation of
`eshell-info-banner--display-memory'."
(concat (s-pad-right text-padding
"."
(eshell-info-banner--with-face
(eshell-info-banner--mounted-partitions-path partition)
:weight 'bold))
": "
(eshell-info-banner--progress-bar-without-prefix
bar-length
(eshell-info-banner--mounted-partitions-used partition)
(eshell-info-banner--mounted-partitions-size partition))))
(defun eshell-info-banner--display-partitions (text-padding bar-length)
"Display the detected mounted partitions of the system.
For TEXT-PADDING and BAR-LENGTH, see the documentation of
`eshell-info-banner--display-memory'."
(mapconcat (lambda (partition)
(eshell-info-banner--partition-to-string partition text-padding bar-length))
(eshell-info-banner--get-mounted-partitions)
"\n"))
; Memory ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun eshell-info-banner--get-memory-gnu ()
"Get memory usage for GNU/Linux and Hurd."
(mapcar (lambda (line)
(let* ((line (split-string line " " t)))
(list (s-chop-suffix ":" (nth 0 line)) ; name
(string-to-number (nth 1 line)) ; total
(string-to-number (nth 2 line))))) ; used
(split-string (eshell-info-banner--shell-command-to-string "free -b | tail -2")
"\n"
t)))
(defun eshell-info-banner--get-memory-unix-command-to-mem (command)
"Get the output of COMMAND corresponding to memory information.
This function is to be only used on platforms which support sysctl."
(string-to-number
(s-trim
(car (last
(split-string (eshell-info-banner--shell-command-to-string command)
" "
t))))))
(defun eshell-info-banner--get-memory-netbsd ()
"Get memory usage for NetBSD systems.
See `eshell-info-banner--get-memory'."
(let* ((total (eshell-info-banner--get-memory-unix-command-to-mem "sysctl hw.physmem64"))
(used (- total
(* 1024 (string-to-number
(s-trim
(with-temp-buffer
(insert-file-contents-literally "/proc/meminfo")
(save-match-data
(string-match (rx bol
"MemFree:"
(* blank)
(group (+ digit))
(* blank)
"kB")
(buffer-string))
(substring-no-properties (buffer-string)
(match-beginning 1)
(match-end 1))))))))))
`(("RAM" ,total ,used))))
(defun eshell-info-banner--get-memory-darwin ()
"Get memory usage for Darwin systems.
See `eshell-info-banner--get-memory'."
(let* ((total (eshell-info-banner--get-memory-unix-command-to-mem "sysctl -n hw.memsize"))
(vmstat (with-temp-buffer
(call-process "vm_stat" nil t nil)
(buffer-string)))
(wired (save-match-data
(string-match (rx " wired" (* (not digit)) (+ blank) (group (+ digit)) ".")
vmstat)
(* 1024 4
(string-to-number (substring-no-properties vmstat
(match-beginning 1)
(match-end 1))))))
(active (save-match-data
(string-match (rx " active" (* (not digit)) (+ blank) (group (+ digit)) ".")
vmstat)
(* 1024 4
(string-to-number (substring-no-properties vmstat
(match-beginning 1)
(match-end 1))))))
(compressed (save-match-data
(if (string-match (rx " occupied" (* (not digit)) (+ blank) (group (+ digit)) ".")
vmstat)
(* 1024 4
(string-to-number (substring-no-properties vmstat
(match-beginning 1)
(match-end 1))))
0))))
`(("RAM" ,total ,(+ wired active compressed)))))
(defun eshell-info-banner--get-memory-unix ()
"Get memory usage for UNIX systems."
(cond ((and (equal system-type 'berkeley-unix)
(string-match-p "NetBSD" (eshell-info-banner--shell-command-to-string "uname")))
(eshell-info-banner--get-memory-netbsd))
((equal system-type 'darwin)
(eshell-info-banner--get-memory-darwin))
(t
(let* ((total (eshell-info-banner--get-memory-unix-command-to-mem "sysctl hw.physmem"))
(used (eshell-info-banner--get-memory-unix-command-to-mem "sysctl hw.usermem")))
`(("RAM" ,total ,used))))))
(defun eshell-info-banner--get-memory-windows ()
"Get memory usage for Window."
(warn "Memory usage not yet implemented for Windows and DOS")
nil)
(defun eshell-info-banner--get-memory ()
"Get memory usage of current operating system.
Return a list of either one or two elements. The first element
represents the RAM, the second represents the swap. Both are
lists and contain three elements: the name of the memory, the
total amount of memory available, and the amount of used memory,
in bytes."
(pcase system-type
((or 'gnu 'gnu/linux)
(eshell-info-banner--get-memory-gnu))
((or 'darwin 'berkeley-unix 'gnu/kfreebsd)
(eshell-info-banner--get-memory-unix))
((or 'ms-dos 'windows-nt 'cygwin)
(eshell-info-banner--get-memory-windows))
(os (warn "Memory usage not yet implemented for %s" os)
nil)))
(defun eshell-info-banner--memory-to-string (type total used text-padding bar-length)
"Display a memory’s usage with a progress bar.
The TYPE of memory will be the text on the far left, while USED
and TOTAL will be displayed on the right of the progress bar.
From them, a percentage will be computed which will be used to
display a colored percentage of the progress bar and it will be
displayed on the far right.
TEXT-PADDING will determine how many dots are necessary between
TYPE and the colon.
BAR-LENGTH determines the length of the progress bar to be
displayed."
(concat (s-pad-right text-padding "." type)
": "
(eshell-info-banner--progress-bar-without-prefix bar-length used total t)))
(defun eshell-info-banner--display-memory (text-padding bar-length)
"Display memories detected on your system.
This function will create a string used by `eshell-info-banner'
in order to display memories detected by the package, generally
the Ram at least, sometimes the swap too. Displayed progress
bars will have this appearance:
TYPE......: [=========] XXG / XXG (XX%)
TEXT-PADDING: the space allocated to the text at the left of the
progress bar.
BAR-LENGTH: the length of the progress bar."
(mapconcat (lambda (mem)
(eshell-info-banner--memory-to-string (nth 0 mem) (nth 1 mem)
(nth 2 mem) text-padding
bar-length))
(eshell-info-banner--get-memory)
""))
; Display information ;;;;;;;;;;;;;;;;;
(defun eshell-info-banner--get-color-percentage (percentage)
"Display a PERCENTAGE with its according face."
(let ((percentage (if (stringp percentage)
(string-to-number percentage)
percentage)))
(cond
((>= percentage eshell-info-banner-critical-percentage)
'eshell-info-banner-critical-face)
((>= percentage eshell-info-banner-warning-percentage)
'eshell-info-banner-warning-face)
(t 'eshell-info-banner-normal-face))))
(defun eshell-info-banner--progress-bar (length percentage &optional invert)
"Display a progress bar LENGTH long and PERCENTAGE full.
The full path will be displayed filled with the character
specified by `eshell-info-banner-progress-bar-char' up to
PERCENTAGE percents. The rest will be empty.
If INVERT is t, then consider the percentage to approach
critical levels close to 0 rather than 100."
(let* ((length-filled (if (= 0 percentage)
0
(/ (* length percentage) 100)))
(length-empty (- length length-filled))
(percentage-level (if invert
(- 100 percentage)
percentage)))
(concat
(eshell-info-banner--with-face "[" :weight 'bold)
(eshell-info-banner--with-face (eshell-info-banner--string-repeat eshell-info-banner-progress-bar-char
length-filled)
:weight 'bold
:inherit (eshell-info-banner--get-color-percentage percentage-level))
(eshell-info-banner--with-face (eshell-info-banner--string-repeat eshell-info-banner-progress-bar-char
length-empty)
:weight 'bold
:inherit 'eshell-info-banner-background-face)
(eshell-info-banner--with-face "]" :weight 'bold))))
(defun eshell-info-banner--display-battery (text-padding bar-length)
"If the computer has a battery, display its level.
Pad the left text with dots by TEXT-PADDING characters.
BAR-LENGTH indicates the length in characters of the progress
bar.
The usage of `eshell-info-banner-warning-percentage' and
`eshell-info-banner-critical-percentage' is reversed, and can be
thought of as the “percentage of discharge” of the computer.
Thus, setting the warning at 75% will be translated as showing
the warning face with a battery level of 25% or less."
(let ((battery-level (unless (and (equal system-type 'gnu/linux)
(not (file-readable-p "/sys/")))
(battery))))
(if (or (null battery-level)
(string= battery-level "Battery status not available")
(string-match-p (regexp-quote "N/A") battery-level))
""
(let ((percentage (save-match-data
(string-match "\\([0-9]+\\)\\(\\.[0-9]\\)?%" battery-level)
(string-to-number (substring battery-level
(match-beginning 1)
(match-end 1))))))
(concat (s-pad-right text-padding "." "Battery")
": "
(eshell-info-banner--progress-bar bar-length
percentage
t)
(eshell-info-banner--string-repeat
" "
(if (equal eshell-info-banner-file-size-flavor 'iec) 21 17))
(format "(%3s%%)\n"
(eshell-info-banner--with-face
(number-to-string percentage)
:inherit (eshell-info-banner--get-color-percentage (- 100.0 percentage)))))))))
; Operating system identification ;;;;;;;;;;;;;;;;;;
(defun eshell-info-banner--get-os-information-from-release-file (&optional release-file)
"Read the operating system from the given RELEASE-FILE.
If RELEASE-FILE is nil, use '/etc/os-release'."
(let ((prefix (if eshell-info-banner-tramp-aware (file-remote-p default-directory) "")))
(with-temp-buffer
(insert-file-contents (concat prefix (or release-file "/etc/os-release")))
(goto-char (point-min))
(re-search-forward "PRETTY_NAME=\"\\(.*\\)\"")
(match-string 1))))
(defun eshell-info-banner--get-os-information-from-hostnamectl ()
"Read the operating system via hostnamectl."
(let ((default-directory (if eshell-info-banner-tramp-aware default-directory "~")))
(with-temp-buffer
(process-file "hostnamectl" nil t nil)
(re-search-backward "Operating System: \\(.*\\)")
(match-string 1))))
(defun eshell-info-banner--get-os-information-from-lsb-release ()
"Read the operating system information from lsb_release."
(eshell-info-banner--shell-command-to-string "lsb_release -d -s"))
(defun eshell-info-banner--get-os-information-from-registry ()
"Read the operating system information from the Windows registry."
(let ((win32-name "Windows")
(win32-build "Unknown"))
(with-temp-buffer
(call-process "reg" nil t nil "query" "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion")
(goto-char (point-min))
(while (re-search-forward "\\([^[:blank:]]+\\) *\\(REG_[^[:blank:]]+\\) *\\(.+\\)" nil t)
(cond
((string= "ProductName" (match-string 1)) (setq win32-name (match-string 3)))
((string= "BuildLab" (match-string 1)) (setq win32-build (match-string 3)))))
(format "%s (%s)" win32-name win32-build))))
(defun eshell-info-banner--get-os-information-windows ()
"See `eshell-info-banner--get-os-information'."
(let ((os (eshell-info-banner--get-os-information-from-registry)))
(save-match-data
(string-match "\\([^()]+\\) *(\\([^()]+\\))" os)
`(,(s-trim (substring-no-properties os
(match-beginning 1)
(match-end 1)))
.
,(substring-no-properties os
(match-beginning 2)
(match-end 2))))))
(defun eshell-info-banner--get-os-information-gnu ()
"See `eshell-info-banner--get-os-information'."
(let ((prefix (if eshell-info-banner-tramp-aware (file-remote-p default-directory) "")))
`(,(cond
;; Bedrock Linux
((file-exists-p (concat prefix "/bedrock/etc/bedrock-release"))
(s-trim (with-temp-buffer
(insert-file-contents (concat prefix "/bedrock/etc/bedrock-release"))
(buffer-string))))
;; Proxmox
((eshell-info-banner--executable-find "pveversion")
(let ((distro (eshell-info-banner--shell-command-to-string "pveversion")))
(save-match-data
(string-match "/\\([^/]+\\)/" distro)
(concat "Proxmox "
(substring-no-properties distro
(match-beginning 1)
(match-end 1))))))
((eshell-info-banner--executable-find "hostnamectl")
(eshell-info-banner--get-os-information-from-hostnamectl))
((eshell-info-banner--executable-find "lsb_release")
(eshell-info-banner--get-os-information-from-lsb-release))
((file-exists-p (concat prefix "/etc/os-release"))
(eshell-info-banner--get-os-information-from-release-file))
((eshell-info-banner--executable-find "shepherd")
(let ((distro (car (s-lines (eshell-info-banner--shell-command-to-string "guix -V")))))
(save-match-data
(string-match "\\([0-9\\.]+\\)" distro)
(concat "Guix System "
(substring-no-properties distro
(match-beginning 1)
(match-end 1))))))
((equal system-type 'gnu/kfreebsd)
(let* ((default-directory (if eshell-info-banner-tramp-aware default-directory "~")))
(s-trim (with-temp-buffer
(process-file "uname" nil t nil "-s")
(buffer-string)))))
((and (file-exists-p (concat prefix "/system/app"))
(file-exists-p (concat prefix "/system/priv-app")))
(concat "Android "
(s-trim (eshell-info-banner--shell-command-to-string "getprop ro.build.version.release"))))
(t "Unknown"))
.
,(s-trim (eshell-info-banner--shell-command-to-string "uname -rs")))))
(defmacro eshell-info-banner--get-macos-name (version)
"Get the name of the current macOS or OSX system based on its VERSION."
`(cond
,@(mapcar (lambda (major)
`((string-match-p ,(car major)
,version)
,(cdr major)))
eshell-info-banner--macos-versions)
(t "unknown version")))
(defun eshell-info-banner--get-os-information-darwin ()
"See `eshell-info-banner--get-os-information'."
`(,(eshell-info-banner--get-macos-name
(s-trim
(eshell-info-banner--shell-command-to-string "sw_vers -productVersion")))
.
,(s-trim (eshell-info-banner--shell-command-to-string "uname -rs"))))
(defun eshell-info-banner--get-os-information ()
"Get operating system identifying information.
Return a pair containing first the name of the operating system
and second its kernel name and version (or in Windows’ case its
build number)."
(pcase system-type
((or 'ms-dos 'windows-nt 'cygwin)
(eshell-info-banner--get-os-information-windows))
((or 'gnu 'gnu/linux 'gnu/kfreebsd 'berkeley-unix)
(eshell-info-banner--get-os-information-gnu))
('darwin
(eshell-info-banner--get-os-information-darwin))
(os (warn "Operating system information retrieving not yet supported for %s" os)
'((format "%s") . "Unknown"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Public functions ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;###autoload
(defun eshell-info-banner ()
"Banner for Eshell displaying system information."
(let* ((default-directory (if eshell-info-banner-tramp-aware default-directory "~"))
(system-info (eshell-info-banner--get-os-information))
(os (car system-info))
(kernel (cdr system-info))
(hostname (if eshell-info-banner-tramp-aware
(or (file-remote-p default-directory 'host) (system-name))
(system-name)))
(uptime (eshell-info-banner--get-uptime))
(partitions (eshell-info-banner--get-mounted-partitions))
(left-padding (eshell-info-banner--get-longest-path partitions))
(left-text (max (length os)
(length hostname)))
(left-length (+ left-padding 2 left-text)) ; + ": "
(right-text (+ (length "Kernel: ")
(max (length uptime)
(length kernel))))
(tot-width (max (+ left-length right-text 3)
eshell-info-banner-width))
(middle-padding (- tot-width right-text left-padding 4))
(bar-length (- tot-width left-padding 4 23))
(bar-length (if (equal eshell-info-banner-file-size-flavor 'iec)
(- bar-length 4)
bar-length)))
(concat (format "%s\n" (eshell-info-banner--string-repeat eshell-info-banner-progress-bar-char
tot-width))
(format "%s: %s Kernel.: %s\n"
(s-pad-right left-padding
"."
"OS")
(s-pad-right middle-padding " " (eshell-info-banner--with-face os :weight 'bold))
kernel)
(format "%s: %s Uptime.: %s\n"
(s-pad-right left-padding "." "Hostname")
(s-pad-right middle-padding " " (eshell-info-banner--with-face hostname :weight 'bold))
uptime)
(eshell-info-banner--display-battery left-padding bar-length)
(eshell-info-banner--display-memory left-padding bar-length)
(eshell-info-banner--display-partitions left-padding bar-length)
(format "\n%s\n" (eshell-info-banner--string-repeat eshell-info-banner-progress-bar-char
tot-width)))))
;;;###autoload
(defun eshell-info-banner-update-banner ()
"Update the Eshell banner."
(setq eshell-banner-message (eshell-info-banner)))
(provide 'eshell-info-banner)
;;; eshell-info-banner.el ends here