-
-
Save Max-E/5298687 to your computer and use it in GitHub Desktop.
#! /usr/bin/env bash | |
# DESCRIPTION | |
# concalc_wrap.sh - Turns Rainer Strobel's batch-mode concalc(1) utility into | |
# a useful interactive scientific and programming calculator. | |
# The script is also its own documentation. | |
# LICENSE | |
# Copyright (c) 2013 Max Eliaser | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a | |
# copy of this software and/or associated documentation files (the | |
# "Materials"), to deal in the Materials without restriction, including | |
# without limitation the rights to use, copy, modify, merge, publish, | |
# distribute, sublicense, and/or sell copies of the Materials, and to | |
# permit persons to whom the Materials are furnished to do so, subject to | |
# the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included | |
# in all copies or substantial portions of the Materials. | |
# | |
# THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
# MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. | |
# INTRODUCTION | |
# I've been a loyal gcalctool user for many years now, even to the point of | |
# manually installing obsolete versions in order to keep the scientific mode. | |
# But recently I came to the conclusion that having a buttons for each number | |
# is completely stupid when I'm going to use the keyboard anyway, and that | |
# there's no reason I shouldn't get a multi-line display and command history. | |
# I set out looking for another calculator app that would allow me to use | |
# pretty much the same keystrokes and features I'd gotten used to from | |
# gcalctool, while taking up much less screen space and showing multiple lines | |
# and command history. bc was close, but wouldn't automatically remember | |
# previous answers and had some other minor differences I didn't want to get | |
# used to. I briefly considered forking gcalctool before I came to my senses. | |
# Finally, I found concalc, which was *almost* what I needed. And all its | |
# shortcomings were easily addressed with a little bit of shell-script | |
# plumbing. | |
# FEATURES | |
# * Permanently stored command line history and command line editing, provided | |
# by rlwrap(1) | |
# * Permanent storage for concalc's settings (mode, base, angle, complex, | |
# etc.) | |
# * Commands to set those settings | |
# * Automatically prepends "ans" if the first operand of a binary operator is | |
# missing (NOTE: use parentheses or a space at the beginning to input a | |
# literal -3 instead of ans-3) | |
# * Automatically juggles concalc's variables file so that multiple instances | |
# of this script may be run at once without interfering with each other; the | |
# last to exit will have its command history, variables, and settings stored | |
# permanently | |
# * Additional commands for features not provided by Concalc itself (currently | |
# just "factor.") | |
# * Commands for running a single line in a different number system (for | |
# example, "hexrun F+F" will always display 1E as a result, regardless of | |
# current mode.) | |
# * Commands for inputting a single line in a different number system (for | |
# example, "hexin A+3" will display 13 as a result when in decimal mode.) | |
# * Commands for displaying the result of a single line in a different number | |
# system (for example, "hexout 5+5" will display A when in decimal mode.) | |
# * User-friendly customization system (by editing this script) | |
# OTHER NOTES | |
# I recommend you create an XDG launcher (i.e. a .desktop file) for this | |
# script using whatever utility your desktop environment provides, and have it | |
# launch in a graphical terminal emulator window. No need to invoke it | |
# manually in a shell. | |
# DEPENDENCIES | |
# * GNU BASH | |
# * rlwrap(1) | |
# * concalc(1) | |
# * factor(1) | |
# * fold(1) | |
# * seq(1) | |
# * terminal emulator | |
# CHANGELOG | |
# * 1.00 - 04/02/13, 9:35 PM GMT-8 - Initial version | |
# * 1.01 - 04/02/13, 9:45 PM GMT-8 - Fix some comments | |
# * 1.02 - 04/06/13, 1:54 PM GMT-8 - Add a "factor" command | |
# * 1.03 - 04/06/13, 3:18 PM GMT-8 - Added "showargs" command to show current | |
# mode. | |
# - Unbreak angle setting. | |
# * 1.04 - 04/06/13, 3:35 PM GMT-8 - Have this script invoke itself, one | |
# process for rlwrap and one for | |
# everything else. I think that'll fix all | |
# these timing issues. | |
# - Retroactively rewrite the changelog to | |
# get rid of all the failed past fixes, | |
# since none of them are in the code now. | |
# * 1.05 - 04/07/13, 1:16 PM GMT-8 - Add binrun octrun, decrun, and hexrun. | |
# - Add binin, octin, decin, and hexin. | |
# - Add binout, octout, decout, and hexout. | |
# - Validate user input | |
# - Cleanup code and comments. | |
# CUSTOMIZABLE CONSTANTS | |
# Window size in characters | |
CALC_COLUMNS=30 | |
CALC_LINES=10 | |
# These two lines might mess things up if not run in a graphical terminal | |
# emulator. You should comment them out entirely if you intend to run them in | |
# a non-resizable terminal (such as a text-mode VT.) | |
resize -s $CALC_LINES $CALC_COLUMNS 2>/dev/null 1>/dev/null # Set window size | |
echo -n $'\e'"]0;calculator"$'\a' # Set tindow title | |
# Text output gets piped through this | |
TEXT_FORMAT_CMD="fold -s -w $CALC_COLUMNS" | |
# Files used by this script | |
histfile=~/.concalc_wrap_history # Command history | |
cfgfile=~/.concalc_wrap_config # Configuration | |
# If the user inputs a command that starts with one of these characters, add | |
# ans before it | |
BINARY_OPERATORS='-+*/^%&|' | |
# This sed script is applied to every command before it is passed to concalc. | |
# It substitutes "ans" as the first argument if there is a binary operator | |
# with no first argument specified | |
SEDSCRIPT='s/^['$BINARY_OPERATORS']/ans&/' | |
# END customizable constants | |
#Settings -- they correspond to concalc's command line options | |
OUTPUT="18" | |
OUTPUT_VALID_VALS="`seq 1 18`" | |
MODE="std" | |
MODE_VALID_VALS="std base" | |
BASE="dec" | |
BASE_VALID_VALS="bin oct dec hex" | |
ANGLE="rad" | |
ANGLE_VALID_VALS="deg rad gra" | |
COMPLEX="off" | |
COMPLEX_VALID_VALS="off on" | |
# This is where concalc keeps its variables. Don't change this. | |
varfile=~/.concalcvariables | |
# This is the line number in $varfile where concalc keeps the "ans" variable | |
CONCALC_ANS_LINE=53 | |
# This script invokes itself, creating two processes, which is how rlwrap | |
# likes to work. If you pipe rlwrap's output through another filter, that | |
# seems to confuse it and lines come out in the wrong order. Also, give rlwrap | |
# a bit longer than default before it tries to output a prompt. | |
if [ "x$1" = "x" ] ; then | |
exec rlwrap -w 100 rlwrap -S "> " -H $histfile $0 innerloop | |
fi | |
# "INNER" PROCESS | |
# Create temporary files | |
tmpvarfile=`mktemp` | |
tmpcfgfile=`mktemp` | |
# Would be cleaner to write the cfg file directly in the trap without using a | |
# temporary file, but that doesn't seem to work. Despite every reference I've | |
# found suggesting that as long as I avoid quotes, the trap command should | |
# pick up on changed global variables, it doesn't actually seem to do so. | |
ARGS="" | |
function gen_concalc_args { | |
# write to the temporary config file | |
echo output $OUTPUT > $tmpcfgfile | |
echo mode $MODE >> $tmpcfgfile | |
echo base $BASE >> $tmpcfgfile | |
echo angle $ANGLE >> $tmpcfgfile | |
echo complex $COMPLEX >> $tmpcfgfile | |
# build concalc's arguments | |
ARGS="-m $MODE -o $OUTPUT -a $ANGLE" | |
if [ "$MODE" = "base" ] ; then | |
ARGS=$ARGS" -b $BASE" | |
fi | |
if [ "$complex" = "on" ] ; then | |
ARGS=$ARGS" -c" | |
fi | |
} | |
# Run a command temporarily in another number system | |
function tmp_base_cmd { | |
old_mode=$MODE | |
old_base=$BASE | |
MODE=base | |
BASE=$1 | |
gen_concalc_args | |
eval "$2" | |
BASE=$old_base | |
MODE=$old_mode | |
gen_concalc_args | |
} | |
# For validating whether a setting can be set to a certain value. | |
function validate_setting { | |
new_value=$1 | |
valid_values="$2" | |
found_value=`echo $valid_values | grep -w $new_value` | |
if [ "$found_value" ] ; then | |
return 0 | |
fi | |
return 1 | |
} | |
# For controlling one of concalc's settings. | |
function set_concalc_setting { | |
setting_name=$1 | |
setting_var_name=`echo $1 | tr [a-z] [A-Z]` | |
setting_cur_val=`eval "echo $"$setting_var_name` | |
setting_valid_vals="`eval "echo $"$setting_var_name"_VALID_VALS"`" | |
if [ "x$2" = "x" ] ; then | |
echo "Current $setting_name is $setting_cur_val" | $TEXT_FORMAT_CMD | |
elif validate_setting $2 "$setting_valid_vals" ; then | |
eval "$setting_var_name=$2" | |
gen_concalc_args | |
else | |
valid_vals_fmt="`echo $setting_valid_vals | sed "s/ /, /g"`" | |
echo "Invalid $setting_name $2! Valid values are: $valid_vals_fmt." | | |
$TEXT_FORMAT_CMD | |
echo "Current $setting_name is $setting_cur_val" | $TEXT_FORMAT_CMD | |
fi | |
} | |
# In addition to running concalc commands, this adds a few extra commands to | |
# control settings. | |
function run_command { | |
case "x$1" in | |
xoutput | xmode | xbase | xangle | xcomplex) | |
set_concalc_setting $@ | |
;; | |
xshowargs) | |
echo $ARGS | |
;; | |
xbinrun | xoctrun | xdecrun | xhexrun ) | |
# Run the specified command temporarily in another number system | |
tmp_base=`echo $1 | sed "s/run$//"` | |
shift | |
tmp_base_cmd $tmp_base "run_command $@" | |
;; | |
xbinin | xoctin | xdecin | xhexin ) | |
# Run a command temporarily in another number system, but display | |
# its result in the currently selected number system | |
tmp_base=`echo $1 | sed "s/in$//"` | |
shift | |
tmp_base_cmd $tmp_base "run_command $@ 2>/dev/null >/dev/null" | |
run_command | |
;; | |
xbinout | xoctout | xdecout | xhexout ) | |
# Run a command in the currently selected number system, but | |
# display its result another number system | |
tmp_base=`echo $1 | sed "s/out$//"` | |
shift | |
run_command $@ 2>/dev/null >/dev/null | |
tmp_base_cmd $tmp_base "run_command" | |
;; | |
xfactor) | |
# not provided by concalc but still handy | |
if [ "$2" = "ans" ] ; then | |
factor `sed -n "$CONCALC_ANS_LINE"p $varfile` | |
elif [ "x$2" = "x" ] ; then | |
factor `sed -n "$CONCALC_ANS_LINE"p $varfile` | |
else | |
factor $2 | |
fi | |
;; | |
x) | |
concalc $ARGS ans | |
;; | |
*) | |
concalc $ARGS "$@" | |
;; | |
esac | |
} | |
# runs all commands piped to it | |
function loop { | |
while read -r line; do | |
cp $tmpvarfile $varfile #in case another instance is running | |
run_command $line | |
cp $varfile $tmpvarfile #save any changes that might have occurred | |
done | |
} | |
# saves the variable file and config file; the history file is saved already | |
# by rlwrap | |
function cleanup { | |
mv $tmpvarfile $varfile | |
mv $tmpcfgfile $cfgfile | |
} | |
trap cleanup EXIT INT TERM | |
touch $varfile #in case concalc has never been run before | |
cp $varfile $tmpvarfile #save copy of concalc's variable file | |
touch $cfgfile #in case this script has never been run before | |
loop < $cfgfile # read commands from the config file | |
gen_concalc_args # generate initial arguments string for concalc | |
sed -u "$SEDSCRIPT" | loop # read commands from stdin |
Sorry for the late reply, I didn't log into github for a while.
innerloop
is for the purpose of working around a limitation of rlwrap
.
rlwrap
likes to invoke whatever program is being wrapped, and has no ability to wrap a program that is already running. It would have been possible to write two scripts, a main script with all the functionality, and a second script to invoke rlwrap
with the main script. But in order to keep this all in one file, I instead had this script start rlwrap
and have rlwrap
start the script again. In BASH, $0
is a way for a script to refer to itself, so rlwrap $0
means "have rlwrap
run the current script."
I used the first argument $1
to distinguish between when concalc_wrap.sh
is first started by the end-user, and when it is started again by rlwrap
. if [ "x$1" = "x" ]
means "if I was run with no arguments" and $0 innerloop
means "run myself with the argument innerloop
".
If the concalc_wrap.sh
is failing on line 171, there could be a few things causing that:
rlwrap
is not installed on your systemrlwrap
is unable to runconcalc_wrap.sh
for whatever reason, for instance because the execute permissions are not enabled on itconcalc_wrap.sh
is failing, but only when run with an argument. You can force it to run in inner-loop mode without rlwrap by manually runningconcalc_wrap.sh innerloop
from the command line, and this may help you do some further troubleshooting on what might be going wrong.
Hope that helps!
Hello,
The line 171 seems to pose problem. What is
innerloop
intended for ?