From 04c4330f9961eec5b78eb2a49cd56af4382a43a4 Mon Sep 17 00:00:00 2001 From: Overwatch Date: Mon, 15 Sep 2014 22:39:24 +0200 Subject: [PATCH] et-prompt rebranded to over-prompt, no other changes --- bash-init | 78 +++++++ data.cpp | 455 ++++++++++++++++++++++++++++++++++++++++ over-prompt-9999.ebuild | 41 ++++ zsh-init | 61 ++++++ 4 files changed, 635 insertions(+) create mode 100755 bash-init create mode 100755 data.cpp create mode 100644 over-prompt-9999.ebuild create mode 100755 zsh-init diff --git a/bash-init b/bash-init new file mode 100755 index 0000000..37e2f47 --- /dev/null +++ b/bash-init @@ -0,0 +1,78 @@ +#! /bin/bash +# *very* dirty, but this inferior excuse for a shell deserves nothing better + +OVER_PROMPT_CFG="/etc/over/prompt.cfg" + +if [ -a "$OVER_PROMPT_CFG" ]; then + source "$OVER_PROMPT_CFG" +fi + +function precmd() +{ + EXITUS=$? + RAW_DATA="$(/usr/share/over-prompt/data $OVER_PROMPT_OPTS)" + + if [ $EXITUS = 0 ]; then + CROSS="|CYAN|\\$|NONE|" + else + CROSS="|RED|${EXITUS}|NONE|:|RED|\\$|NONE|" + fi + if [ $EUID = 0 ]; then + USER_PART="|RED|\\u|NONE|" + else + USER_PART="|GREEN|\\u|NONE|" + fi + + home_len=${#HOME} + if [ "${PWD:0:$home_len}" = "$HOME" ]; then + pwd="~${PWD:$home_len}" + else + pwd="$PWD" + fi + + if [ -n "$RAW_DATA" ]; then + LOGIN_PART="${RAW_DATA%:::*}" + STATS_PART="[ ${RAW_DATA#*:::} " + + pwd_len=${#pwd} + user_len=${#USER} + host_len=${#HOSTNAME} + win_len=${#WINDOW} + STATS_PART_="$(echo ${STATS_PART}|sed 's/|[A-Z]*|//g')" + stats_len=${#STATS_PART_} + + max_pwd_len=$((${COLUMNS}-${user_len}-${host_len}-${win_len}-${stats_len}-5)) + + # 1 kB of spaces... that should be enough. DIRTY, YEAH! + spaces=" " + padding="" + if [ ${pwd_len} -gt ${max_pwd_len} ]; then + pwd="<${pwd:(($pwd_len - $max_pwd_len)):$pwd_len}" + elif [ ${pwd_len} -lt ${max_pwd_len} ]; then + padto=$((${max_pwd_len} - ${pwd_len} + 1)) + padding="${spaces:0:$padto}" + fi + + PS1="${USER_PART}@${LOGIN_PART} ${padding}${STATS_PART}\n$CROSS " + else + PS1="${USER_PART}@|HOST| |CWD| [|RFLASH|data gathering error|NONE|]\n$CROSS " + fi + + PS1="${PS1//|NONE|/\[\033[0m\]}" + PS1="${PS1//|RED|/\[\033[1;31m\]}" + PS1="${PS1//|RFLASH|/\[\033[5;31m\]}" + PS1="${PS1//|GREEN|/\[\033[1;32m\]}" + PS1="${PS1//|DARKGREEN|/\[\033[0;32m\]}" + PS1="${PS1//|BLUE|/\[\033[1;34m\]}" + PS1="${PS1//|YELLOW|/\[\033[1;33m\]}" + PS1="${PS1//|CYAN|/\[\033[1;36m\]}" + PS1="${PS1//|HOST|/$HOSTNAME}" + PS1="${PS1//|TTY|/$WINDOW}" + PS1="${PS1//|CWD|/$pwd}" + + unset -v LOGIN_PART STATS_PART pwd_len user_len host_len win_len STATS_PART_ stats_len max_pwd_len spaces padding pwd padto padding +} + +PROMPT_COMMAND="precmd" + +unset OVER_PROMPT_CFG diff --git a/data.cpp b/data.cpp new file mode 100755 index 0000000..e18747c --- /dev/null +++ b/data.cpp @@ -0,0 +1,455 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Space { + public: + Space(const char *name, float available, float total) { + if (strlen(name) > 15) { + strcpy(this->name, "!!!"); + } else { + strcpy(this->name, name); + } + + this->available = available; + + if (total > 0) { + this->percent = available*100 / total; + this->active = true; + } else { + this->percent = 100; + this->active = false; + } + } + + char name[16]; + float available; + float percent; + bool active; +}; + +typedef struct { + float load; + char proc[16]; + unsigned int color; +} Sysload; + +typedef struct { + unsigned short int m1, m2; + bool long_names; +} Config; + +/// Data gatherers +void get_available_space(std::vector &space); +void get_memswap(std::vector &space); +void get_sysload(Sysload &sysload); + +/// Formatting and parsing +void append(char *output, const char *string, int color=-1); +const char *float_to_str(float value, char *output); +int space_to_color(Space &space); +float grepf(const std::string &data, const char *marker); +std::vector read_lines_from_file(std::string filename); +bool column_contains_substr(std::string line, unsigned int column, std::string substr, int position=0); +std::string get_column(std::string line, unsigned int column); +bool str_in_list(std::string str, std::vector list); + +/// Global +std::map color_map; +Config config; +std::vector ignored; + +enum { + COL_GREEN, + COL_DARKGREEN, + COL_YELLOW, + COL_BLUE, + COL_RED, + COL_RFLASH, + COL_NONE +}; + +int main(int argc, char **argv) { + char output[512]; + char tmp[16]; + + std::vector space; + Sysload sysload; + + output[0] = 0; + + color_map[COL_GREEN] = "|GREEN|"; + color_map[COL_DARKGREEN] = "|DARKGREEN|"; + color_map[COL_YELLOW] = "|YELLOW|"; + color_map[COL_BLUE] = "|BLUE|"; + color_map[COL_RED] = "|RED|"; + color_map[COL_RFLASH] = "|RFLASH|"; + color_map[COL_NONE] = "|NONE|"; + + ignored.push_back("rootfs"); + ignored.push_back("proc"); + ignored.push_back("sysfs"); + ignored.push_back("udev"); + ignored.push_back("devpts"); + ignored.push_back("shm"); + ignored.push_back("usbfs"); + ignored.push_back("binfmt_misc"); + ignored.push_back("fusectl"); + + if (argc == 4) { + config.m1 = atoi(argv[1]); + config.m2 = atoi(argv[2]); + if (strcmp(argv[3], "long") == 0) { + config.long_names = true; + } else { + config.long_names = false; + } + } else { + config.m1 = 20; + config.m2 = 10; + config.long_names = false; + } + + get_available_space(space); + get_memswap(space); + get_sysload(sysload); + + append(output, "|HOST|", sysload.color); + append(output, ":"); + append(output, "|TTY|", COL_DARKGREEN); + append(output, " "); + append(output, "|CWD|", COL_DARKGREEN); + append(output, ":::"); + + for (unsigned int i = 0; i < space.size(); i++) { + append(output, space[i].name); + // TODO Active only + append(output, float_to_str(space[i].available, tmp), space_to_color(space[i])); + append(output, " "); + } + + append(output, "| "); + append(output, float_to_str(sysload.load, tmp), sysload.color); + append(output, " "); + append(output, sysload.proc, sysload.color); + + std::cout << output; + + return 0; +} + +void append(char *output, const char *input, int color) { + unsigned int len_o, len_i, len_c = 0; + + len_o = strlen(output); + len_i = strlen(input); + if (color != -1) len_c = strlen(color_map[color].c_str()); + + if (color != -1) strcpy(output + len_o, color_map[color].c_str()); + strcpy(output + len_o + len_c, input); + if (color != -1) strcpy(output + len_o + len_i + len_c, color_map[COL_NONE].c_str()); +} + +const char *float_to_str(float value, char *output) { + char suffix; + + output[0] = 0; + + if (value > 2*1099511627776.0) { // 2T + value /= 1099511627776.0; + suffix = 'T'; + } else if (value > 2*1073741824.0) { // 2G + value /= 1073741824.0; + suffix = 'G'; + } else if (value > 2*1048576.0) { // 2M + value /= 1048576.0; + suffix = 'M'; + } else if (value > 2*1024.0) { // 2k + value /= 1024.0; + suffix = 'k'; + } else { + suffix = 0; + } + + sprintf(output, "%.2f%c", value, suffix); + + return output; +} + +int space_to_color(Space &space) { + if (space.percent >= config.m1) { + return COL_GREEN; + } else if (space.percent >= config.m2) { + return COL_YELLOW; + } else if (space.percent > (config.m2/32.0)) { + return COL_RED; + } else { + return COL_RFLASH; + } +} + +void get_sysload(Sysload &sysload) { + float dump; + std::ifstream in; + + in.open("/proc/loadavg", std::ifstream::in); + + in >> sysload.load; + in >> dump; + in >> dump; + in >> sysload.proc; + + in.close(); + + if (sysload.load > 12) { + sysload.color = COL_RFLASH; + } else if (sysload.load > 8) { + sysload.color = COL_RED; + } else if (sysload.load > 4) { + sysload.color = COL_YELLOW; + } else { + sysload.color = COL_GREEN; + } +} + +float grepf(const std::string &data, const char *marker) { + unsigned int pos, offset; + char value[16]; + float out; + + offset = 0; + pos = data.find(marker) + strlen(marker); + + // skip through spaces + while (data[pos] == ' ') { + pos++; + } + + // read the number + for (; data[pos] != ' '; pos++, offset++) { + value[offset] = data[pos]; + } + + // terminator + value[offset] = 0; + + out = atof(value); + + return out; +} + +void get_memswap(std::vector &space) { + std::string s; + std::ifstream in; + char c; + + in.open("/proc/meminfo", std::ifstream::in); + + while (true) { + c = in.get(); + + if (in.good()) { + s += c; + } else { + break; + } + } + + in.close(); + + // memory + space.push_back(Space("mem", grepf(s, "MemFree:")*1024 + grepf(s, "Buffers:")*1024 + grepf(s, "Cached:")*1024, grepf(s, "MemTotal:")*1024)); + + // swap + space.push_back(Space("swp", grepf(s, "SwapFree:")*1024.0, grepf(s, "SwapTotal:")*1024.0)); +} + +void get_available_space(std::vector &space) { + // Go through physical RW mounts and query them re: free space + // Ignore /dev and everything below it + // Generate a unique name: mountpoint basename? or maybe shortened to smallest unique part. + + std::vector mounts; + struct statfs stats; + std::string fullname, name, mountpoint; + long free_space, total_space; + int ptr, len; + size_t str_pos; + bool OK; + + mounts = read_lines_from_file("/etc/mtab"); + + for (unsigned int i = 0; i < mounts.size(); i++) { + if (column_contains_substr(mounts[i], 3, "rw") and column_contains_substr(mounts[i], 0, "/dev", -1)) { + + // no CDs - loops have rw set + if (column_contains_substr(mounts[i], 2, "iso9660")) { + continue; + } + + // no binds + if (column_contains_substr(mounts[i], 3, "bind")) { + continue; + } + + + mountpoint = get_column(mounts[i], 1); + + // handle spaces in mount point names - /proc/mounts uses a 4-byte '\\040' sequence to signify a space + while (true) { + str_pos = mountpoint.find("\\040"); + + if (str_pos == std::string::npos) { + break; + } else { + mountpoint.replace(str_pos, 4, " "); + } + } + + // generate name FIXME basenames are unique on systems with obsessive-compulsive roots only + fullname = basename((char*)mountpoint.c_str()); + if (config.long_names or fullname == "/") { + name = fullname; + } else { + ptr = 1; + len = fullname.size(); + name = fullname.substr(0, ptr); + + OK = false; + while (not OK) { + OK = true; + for (unsigned int j = 0; j < space.size(); j++) { + if (name == space[j].name) { + OK = false; + if (ptr == len) { + // too bad, we give up + name += "!"; + OK = true; + } else { + name = fullname.substr(0, ++ptr); + } + + break; + } + } + } + } + + // figure out free and total space + statfs(mountpoint.c_str(), &stats); + free_space = stats.f_bavail * stats.f_bsize; + total_space = stats.f_blocks * stats.f_bsize; + + // pass it on + space.push_back(Space(name.c_str(), free_space, total_space)); + } + } +} + +std::string get_column(std::string line, unsigned int column) { + std::string output; + unsigned int col_counter; + bool eating_chars; // else eating whitespace + char c; + + col_counter = -1; // Dirty? Maybe. It's called 'creative' where I come from. + eating_chars = false; // ignore leading whitespace + + // start eating bytes + for (unsigned int i = 0; i < line.size(); i++) { + c = line[i]; + + if (eating_chars and isspace(c)) { // end of a column + eating_chars = false; + + if (col_counter == column) { + break; + } + + } else if (not (eating_chars or isspace(c))) { // beginning of a column + eating_chars = true; + col_counter++; + } + + if (col_counter == column) { + output += c; + } + } + + return output; +} + +bool column_contains_substr(std::string line, unsigned int column, std::string substr, int position) { + std::string str; + + str = get_column(line, column); + + switch (position) { + case -1: + // beginning + if (str.substr(0, substr.size()) == substr) { + return true; + } else { + return false; + } + + break; + case 0: + // anywhere + if (str.find(substr) == std::string::npos) { + return false; + } else { + return true; + } + + break; + case 1: + // end + if (str.substr(str.size() - substr.size() - 1, substr.size()) == substr) { + return true; + } else { + return false; + } + + break; + }; + + return false; +} + +std::vector read_lines_from_file(std::string filename) { + char buf[512]; + std::ifstream in; + std::vector lines; + + in.open(filename.c_str(), std::ifstream::in); + + while (in.good()) { + in.getline(buf, 512); + + if (in.gcount()) { + lines.push_back(buf); + } + } + + return lines; +} + +bool str_in_list(std::string str, std::vector list) { + for (unsigned int i = 0; i < list.size(); i++) { + if (list[i] == str) { + return true; + } + } + + return false; +} diff --git a/over-prompt-9999.ebuild b/over-prompt-9999.ebuild new file mode 100644 index 0000000..cddec24 --- /dev/null +++ b/over-prompt-9999.ebuild @@ -0,0 +1,41 @@ +# Copyright 1999-2014 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: $ + +EAPI=5 + +inherit git-2 + +DESCRIPTION="A nonintrusive shell prompt that provides: username, hostname, tty name, cwd, return value of the last command, current time, disk space, and system load. Zsh is preferred, although bash is also somewhat supported." + +HOMEPAGE="http://overtech.cz/" +SRC_URI="" +EGIT_REPO_URI="git://overtech.cz/over-prompt.git" + +LICENSE="AOJSL" +SLOT="0" +KEYWORDS="x86 amd64" + +src_compile() { + g++ -ansi -Wall -o data data.cpp +} + +src_install() { + insinto /usr/share/${PN} + insopts -m755 + doins data + doins *-init +} + +pkg_postinst() { + einfo "Source /usr/share/${PN}/bash-init or /usr/share/${PN}/zsh-init" + einfo "(depending on which shell you're running) to enable the prompt." + einfo "If you wish, write the following into /etc/over/prompt.cfg:" + einfo "" + einfo "export OVER_PROMPT_OPTS=\"A B C\"" + einfo "" + einfo "where A is the percentage at which your free space indicator turns" + einfo "yellow, B is the threshold for red color, and C is either 'short' or" + einfo "'long' (without the quotes), indicating whether you want to display" + einfo "short or long mount point names. The default is \"20 10 short\"." +} diff --git a/zsh-init b/zsh-init new file mode 100755 index 0000000..68f7d13 --- /dev/null +++ b/zsh-init @@ -0,0 +1,61 @@ +#! /bin/zsh + +OVER_PROMPT_CFG="/etc/over/prompt.cfg" + +if [[ -a "$OVER_PROMPT_CFG" ]]; then + source "$OVER_PROMPT_CFG" +fi + +function tprompt_filter { + PRT="$1" + PRT="${PRT//|HOST|/%m}" + PRT="${PRT//|TTY|/%l}" + COLORLESS="$(print -P "%n@${PRT}"|sed 's/|[A-Z]*|//g')" + PRT="${PRT//|GREEN|/\%\{\e[1;32m%\}}" + PRT="${PRT//|RED|/\%\{\e[1;31m%\}}" + PRT="${PRT//|RFLASH|/\%\{\e[5;31m%\}}" + PRT="${PRT//|DARKGREEN|/\%\{\e[0;32m%\}}" + PRT="${PRT//|YELLOW|/\%\{\e[1;33m%\}}" +# PRT="${PRT//|BLUE|/\%\{\e[1;34m%\}}" # not used + PRT="${PRT//|CYAN|/\%\{\e[1;36m%\}}" + PRT="${PRT//|NONE|/\%\{\e[0m%\}}" + ((OFFSET = ${COLUMNS} - ${#COLORLESS} + 9)) + PRT="${PRT//|CWD|/%${OFFSET}<\\\\\\\\\\\\\\\<<%~%<<}" # 16x\ protoze to jde exponencialne pres 4 printy (kazdej jich polovinu sezere :) + print "$PRT" +} + +function precmd { + local OVER_OPTS + set -A OVER_OPTS ${(s. .)OVER_PROMPT_OPTS} + + RAW_DATA="$(/usr/share/over-prompt/data $OVER_OPTS[1] $OVER_OPTS[2] $OVER_OPTS[3])" + + if [ -n "$RAW_DATA" ]; then + set -A DATA ${(s.:::.)RAW_DATA} + LOGIN_PART=${DATA[1]} + STATS_PART=${DATA[2]} + + PS1a="%(!.|RED|.|GREEN|)%n|NONE|@$LOGIN_PART" + PS1b="%(?.|CYAN|.|RED|%?|NONE|:|RED|)%(!.#.$)|NONE| " + RPS1="[ |CYAN|%T|NONE| | $STATS_PART" + + # vyrenderovat + PS1a="$(print "$(tprompt_filter "$PS1a")")" + PS1b="$(print "$(tprompt_filter "$PS1b")")" + RPS1="$(print "$(tprompt_filter "$RPS1")")" + + PS1="$(print "$PS1a\n$PS1b")" + RPS1="$(print $RPS1)" + else + PS1a="%(!.|RED|.|GREEN|)%n|NONE|@|HOST|:|DARKGREEN||TTY| |CWD|" + PS1b="%(?.|CYAN|.|RED|%?|NONE|:|RED|)%(!.#.$)|NONE| " + RPS1="[ |CYAN|%T|NONE| | |RFLASH|data gathering error|NONE|" + PS1a="$(print "$(tprompt_filter "$PS1a")")" + PS1b="$(print "$(tprompt_filter "$PS1b")")" + + PS1="$(print "${PS1a}\n${PS1b}")" + RPS1="$(print "$(tprompt_filter "$RPS1")")" + fi +} + +unset OVER_PROMPT_CFG