636 lines
15 KiB
Bash
636 lines
15 KiB
Bash
#!/bin/bash
|
||
# xdialog kullanımını kolaylaştıran yordamlar.
|
||
|
||
## milisarge <milisarge@gmail.com>
|
||
## brokenman <brokenman@porteus.org> librokenman betiğinden yararlanılmıştır.
|
||
###################################
|
||
## READ THE COMMENTS CAREFULLY TO
|
||
## KNOW THE ARGUMENTS REQUIRED !!
|
||
###################################
|
||
|
||
## In most cases script returns variable $answ
|
||
## In this case 0=yes 1=no
|
||
## E.g is_64bit ($answ returns 0 then yes it is a 64bit machine)
|
||
|
||
script=${0##*/} ### This is the users script name, not this one
|
||
scriptpath="$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")" ### Full script path
|
||
answ=/tmp/.answ
|
||
log=/var/log/libmilisdialog.log
|
||
|
||
# Generic output for errors
|
||
error(){
|
||
echo $1 >> $log
|
||
}
|
||
|
||
# Find which window desktop session we are in. It returns wm=
|
||
get_desktop_session(){
|
||
# get desktop info
|
||
startx=`ps x | grep -c /usr/bin/startx`
|
||
if [ $startx -eq 2 ]; then
|
||
lxde=`ps x | grep -v grep | grep -c /usr/bin/lxsession`
|
||
trinity=`ps x | grep -v grep | grep tdeinit`
|
||
kde4=`ps x | grep -v grep | grep -c /usr/bin/starttde`
|
||
xfce=`ps x | grep -v grep | grep -c xfdesktop`
|
||
else
|
||
lxde=`ps aux | sed '/X '$(echo $DISPLAY|cut -d. -f1)'/,$ !d' | sed '1,/X / !d' | grep -c /usr/bin/lxsession`
|
||
trinity=`ps aux | sed '/ '$DISPLAY' /,$ !d' | sed '1,/X / !d' | grep tdeinit`
|
||
kde4=`ps aux | sed '/ '$DISPLAY' /,$ !d' | sed '1,/X / !d' | grep -c /usr/bin/startkde`
|
||
xfce=`ps aux | sed '/ '$(echo $DISPLAY|cut -d. -f1)' /,$ !d' | sed '1,/X / !d' | grep -c xfdesktop`
|
||
fi
|
||
|
||
if [ "$lxde" -eq 1 ]; then
|
||
wm=lxde
|
||
elif [ "$trinity" ]; then
|
||
wm=tde
|
||
elif [ "$kde4" -eq 1 -a -z "$trinity" ]; then
|
||
wm=kde4
|
||
elif [ "$xfce" -eq 1 ]; then
|
||
wm=xfce
|
||
fi
|
||
[ -z $wm ] && error "There was a problem finding the desktop session"
|
||
}
|
||
|
||
# milis mimari tipi
|
||
is_64bit(){
|
||
[ `uname -m|egrep "64"` ] && answ=0 || answ=1
|
||
}
|
||
|
||
# Silently kill a named process (number)
|
||
kills(){
|
||
# Check that $1 was given
|
||
if [ $1 ]; then
|
||
kill $1 2>/dev/null &
|
||
wait $1 2>/dev/null
|
||
else
|
||
answ=3
|
||
error "You must supply a process ID to kill!"
|
||
return
|
||
fi
|
||
}
|
||
|
||
# Unpack a compressed file to current dir
|
||
# $1=compressed file
|
||
unpack_file(){
|
||
if [ ! $1 ]; then
|
||
answ=3
|
||
error "You must supply a compressed file"
|
||
cyan "Supported files: .zip .tar.gz .tar.bz2 .xzm"
|
||
return
|
||
fi
|
||
|
||
# Make sure file has valid extension
|
||
local chkf
|
||
local ext
|
||
local nam
|
||
local nam2
|
||
chkf=`echo $1|egrep "*.tar.gz|.tar.bz2|.zip|.xzm|.tar.xz|.txz|.tgz"`
|
||
if [ -z $chkf ]; then
|
||
answ=3
|
||
error "ERROR:" "This type of file is not supported!"
|
||
return
|
||
fi
|
||
|
||
# Get extension
|
||
ext=${chkf##*.}
|
||
nam=${1##*/}
|
||
case $ext in
|
||
gz )
|
||
tar zxvf $1 >/dev/null|| answ=3 && error "ERROR unpacking $1"
|
||
;;
|
||
bz2 )
|
||
tar xvjf $1 >/dev/null|| bzip2 -d $1 || answ=3 && error "ERROR unpacking $1"
|
||
;;
|
||
zip )
|
||
unzip $1 >/dev/null|| answ=3 && error "ERROR unpacking $1"
|
||
;;
|
||
xzm )
|
||
unsquashfs $1 >/dev/null 2>&1 answ=3 && error "ERROR unpacking $1"
|
||
;;
|
||
xz )
|
||
tar -Jxf $1 >/dev/null || answ=3 && error "ERROR unpacking $1"
|
||
;;
|
||
txz )
|
||
mktemp -d ./unpacked.XXXXXX
|
||
local targ
|
||
targ=`ls|grep unpacked`
|
||
installpkg -root $targ $1 >/dev/null 2>&1 || answ=3 && error "ERROR unpacking $1"
|
||
;;
|
||
tgz )
|
||
mktemp -d ./unpacked.XXXXXX || answ=3 && error "ERROR unpacking $1"
|
||
local targ
|
||
targ=`ls|grep unpacked`
|
||
installpkg -root $targ $1 >/dev/null 2>&1 || answ=3 && error "ERROR unpacking $1"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Make a posix test
|
||
# $1=dir
|
||
make_posix_test(){
|
||
f1=$1/._test1
|
||
f2=$1/._test2
|
||
touch $f1
|
||
ln -s $f1 $f2 2>/dev/null && chmod +x $f1 2>/dev/null && [ -x $f1 ] && chmod -x $f1 2>/dev/null && [ ! -x $f1 ] && rm -f $f1 $f2
|
||
if [ $? -ne 0 ]; then
|
||
answ=1
|
||
rm -f $f1 $f2 >/dev/null 2>&1
|
||
else
|
||
answ=0
|
||
fi
|
||
}
|
||
|
||
# Remove duplicates
|
||
# $1=file
|
||
remove_duplicates(){
|
||
if [ ! $1 ]; then
|
||
answ=3 && error "You must supply a valid list"
|
||
return
|
||
elif [ ! -f $1 ]; then
|
||
error "Could not find $1" $FUNCNAME $LINENO
|
||
cyan "Make sure the file exists and is a list"
|
||
exit
|
||
else
|
||
sort -u $1 > /tmp/.hold
|
||
rm $1
|
||
mv /tmp/.hold $1
|
||
fi
|
||
}
|
||
|
||
# A simple Xdialog download gauge
|
||
# $1=title $2=text $3=link $4=rc-file
|
||
download() {
|
||
if [ $# -lt 4 ]; then
|
||
error "You must supply 4 args to: $FUNCNAME" $FUNCNAME $LINENO
|
||
cyan "\$1=title, \$2=text, \$3=link \$4=rc-file"
|
||
cyan "If you don't have an rc-file just put anything"
|
||
exit
|
||
else
|
||
touch /tmp/.dload
|
||
dl=/tmp/.dload
|
||
wget $3 -o $dl &
|
||
(
|
||
while [ "$i" != "98" -a "$f" == "" ]; do
|
||
echo $i
|
||
i=$(cat $dl 2>/dev/null| grep "%" |awk '{print $(NF-2)}'|sed 's/\(.*\)./\1/'|tail -n1)
|
||
f=$(grep "100%" $dl)
|
||
sleep 0.1
|
||
done
|
||
) | Xdialog --title "$1" --rc-file "$4" --icon "$5" --gauge "Downloading $2...." 400x150
|
||
[ -f $dl ] && rm -f $dl
|
||
fi
|
||
[ -f $dl ] && rm $dl
|
||
}
|
||
|
||
# Check that a filename is slackware compatible
|
||
# If name is bad will return badname=1
|
||
# If field has -lt 4 will return $badflds
|
||
# If $PKG_ARCH is -ne i486,x86_64,noarch,fw will return $badarch
|
||
check_slackware_name(){
|
||
if [ $# -eq 0 ]; then
|
||
error "You must supply a slackware file name!" $FUNCNAME $LINENO
|
||
else
|
||
local FILE=${1##*/}
|
||
|
||
# Make sure all is in lower case
|
||
FILE=`echo $FILE|tr "[:upper:]" "[:lower:]"`
|
||
|
||
# Make sure there are no less than 4 fields
|
||
local flds=`awk -F- '{print NF}' $FILE`
|
||
[ $flds -lt 4 ] && { answ=1; return; }
|
||
|
||
# Split package name to individual elements
|
||
eval $(echo $FILE | sed 's/\(.*\)-\([^-]*\)-\([^-]*\)-\([0-9]*\)\(.*\)*/\
|
||
PKG_NAME=\1 PKG_VER=\2 PKG_ARCH=\3 PKG_BUILD=\4 PKG_TAG=\5/')
|
||
|
||
# Check that package arch is legit
|
||
[ ! `echo $PKG_ARCH|egrep "i*86|x86_64|noarch|fw"` ] && { answ=1; return; }
|
||
answ=0
|
||
fi
|
||
}
|
||
|
||
# makes sure that is a directory is chosen, it is not on aufs
|
||
# unless in tmp or home folder (never in root)
|
||
# This function returns nopermit=1 if dir is bad.
|
||
is_on_aufs(){
|
||
if [ $# -ne 1 ]; then
|
||
answ=3
|
||
return
|
||
else
|
||
[ ! -d $1 ] && { answ=3; return; }
|
||
[ "`df -T $1|tail -n1|awk '{print$1}'`" == "aufs" ] && answ=0 || answ=1
|
||
fi
|
||
}
|
||
|
||
# Verify that this is a milis module & identify squashfs version
|
||
# $1=module path
|
||
# Returns: badext=1 bad extension | sq=4 squashfs4 | sq=3 squashfs3 | sq=0 not valid
|
||
is_squash4(){
|
||
if [ ! $1 ]; then
|
||
error "You must supply the path to a valid Milis module" $FUNCNAME $LINENO
|
||
return
|
||
else
|
||
mname=$1
|
||
ftype=`file $mname|egrep -o "endian|data"`
|
||
[ -z $ftype ] && answ=0 || answ=1
|
||
fi
|
||
}
|
||
|
||
# Silently mount a module on loopback and check is module
|
||
# Mount module to /mnt/loop
|
||
mount_module(){
|
||
if [ ! $1 ]; then
|
||
answ=3
|
||
return
|
||
else
|
||
is_squash4
|
||
if [ $answ -eq 1 ]; then
|
||
answ=3
|
||
else
|
||
mloop "$1" >/dev/null 2>&1 && answ=0 || answ=3
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Check if a website is online and working (silently)
|
||
# Returns url=0 if it is avaiable
|
||
silent_check_url(){
|
||
if [ $# -lt 1 ]; then
|
||
answ=3
|
||
error "You must supply a URL to:" $FUNCNAME $LINENO
|
||
exit
|
||
else
|
||
if (wget -q --spider --force-html --inet4-only "$1" >/dev/null 2>&1); then
|
||
answ=0
|
||
else
|
||
answ=1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Check if a website is online and working (silently)
|
||
# Returns url=0 if it is avaiable
|
||
has_internet(){
|
||
if (wget -q --spider --force-html --inet4-only http://www.google.com >/dev/null 2>&1); then
|
||
answ=0
|
||
else
|
||
answ=1
|
||
fi
|
||
}
|
||
|
||
# Check that writable flag is set
|
||
is_writable(){
|
||
[ -w $1 ] && answ=0 || answ=1
|
||
}
|
||
|
||
# Get the last field in a given string with given delimiter
|
||
# $1=delimiter
|
||
get_last_field(){
|
||
awk -F"$1" '{print$NF}'
|
||
}
|
||
|
||
# Get the field count for given string with given delimiter
|
||
get_field_count(){
|
||
awk -F"$1" '{print NF}'
|
||
}
|
||
|
||
# Make a free loop if one is not present
|
||
check_free_loop_device(){
|
||
x=`grep /dev/loop /proc/mounts | tail -n1 | cut -d " " -f1 | sed 's@/dev/loop@@'`; let y=x+1
|
||
loop=/dev/loop$y
|
||
if [ ! -e $loop ]; then mknod $loop b 7 $y; fi
|
||
unset x loop y
|
||
}
|
||
|
||
|
||
# Green colored output
|
||
green() {
|
||
echo -e "\033[01;32m$@\033[00m"
|
||
}
|
||
|
||
# Yellow colored output
|
||
yellow() {
|
||
echo -e "\033[01;33m$@\033[00m"
|
||
}
|
||
|
||
# Red colored output
|
||
function redd() {
|
||
echo -e "\033[01;31m$@\033[00m"
|
||
}
|
||
|
||
# Cyan colored output
|
||
cyan() {
|
||
echo -e "\033[01;36m$@\033[00m"
|
||
}
|
||
|
||
# Blue colored output
|
||
blue() {
|
||
echo -e "\033[01;34m$@\033[00m"
|
||
}
|
||
|
||
# Magenta colored output
|
||
magenta() {
|
||
echo -e "\033[01;35m$@\033[00m"
|
||
}
|
||
|
||
### GTKDIALOG LIBRARIES
|
||
|
||
## Use manual progress update like so:
|
||
## echo "My message goes here" > /tmp/.message
|
||
## manual_progress "My title goes here"
|
||
## sleep 5 (or other code)
|
||
## echo "Second update goes here" > /tmp/.message
|
||
## sleep 3 (or other code)
|
||
## kill_manual_progress
|
||
manual_progress(){
|
||
# Open a window for progress
|
||
export MANUAL_PROGRESS='
|
||
<window title="Milis message" icon-name="cdr" resizable="false" width-request="400" window_position="1">
|
||
<vbox>
|
||
<frame>
|
||
<text use-markup="true" width-request="310">
|
||
<label>"'$1'"</label>
|
||
<variable>PROGRESS_TXT</variable>
|
||
</text>
|
||
<progressbar visible="true">
|
||
<variable>PROGRESS_BAR</variable>
|
||
<label>...</label>
|
||
<input>prog_counter</input>
|
||
</progressbar>
|
||
</frame>
|
||
</vbox>
|
||
</window>
|
||
'
|
||
gtkdialog -c -p MANUAL_PROGRESS 2>/tmp/.progress_trap &
|
||
}; export -f manual_progress
|
||
|
||
## This is called by the manual_progress function.
|
||
prog_counter(){
|
||
case $STATE in
|
||
MANUAL )
|
||
while [ A != B ]; do
|
||
MSG=`cat /tmp/.message`
|
||
echo $MSG
|
||
sleep 0.3
|
||
done
|
||
echo "ALL DONE"
|
||
unset STATE
|
||
;;
|
||
DOWNLOAD )
|
||
while [ A != B ]; do
|
||
if [ -f /tmp/.cdload ]; then M=`cat /tmp/.cdload`; echo "$M"; fi
|
||
sleep 0.3
|
||
[ `grep "100%" /tmp/.message` ] && echo 1
|
||
i=$(grep -o "[^ ]*%[^ ]*" /tmp/.message|sed "s/%//g"|tail -n1)
|
||
[ "$i" == "100" ] && i=1
|
||
[ "$i" != "" ] && echo "$i"
|
||
sleep 0.3
|
||
done
|
||
echo "ALL DONE"
|
||
unset STATE
|
||
;;
|
||
BUILDMODULE )
|
||
while [ A != B ]; do
|
||
if [ ! -f /tmp/.message ]; then echo "Waiting ..."; fi
|
||
[ `grep "100%" /tmp/.message` ] && echo Finished
|
||
i=$(grep -o ...% /tmp/.message|sed -e "s/%//g" -e 's/ //g'|tail -n1)
|
||
[ "$i" != "" ] && echo "$i"
|
||
sleep 0.3
|
||
done
|
||
echo "ALL DONE"
|
||
unset STATE
|
||
;;
|
||
* )
|
||
while [ A != B ]; do
|
||
MSG=`cat /tmp/.message`
|
||
echo $MSG
|
||
sleep 0.3
|
||
done
|
||
echo "ALL DONE"
|
||
unset STATE
|
||
;;
|
||
esac
|
||
[ -f /tmp/.cdload ] && rm /tmp/.cdload 2>&-
|
||
[ -f /tmp/.message ] && rm /tmp/.message 2>&-
|
||
}; export -f prog_counter
|
||
|
||
kill_manual_progress() {
|
||
myself=`ps ax|grep MANUAL_PROGRESS|awk '{print$1}'|head -n1`
|
||
for children in `ps -o pid --ppid $myself|sed '/PID/d'`; do
|
||
kill $children >/dev/null 2>&-
|
||
done
|
||
kill $myself >/dev/null 2>&-
|
||
[ -f /tmp/.message ] && rm /tmp/.message
|
||
[ -f /tmp/.progress_trap ] && rm /tmp/.progress_trap
|
||
}; export -f kill_manual_progress
|
||
|
||
gtk_message(){
|
||
echo '
|
||
<window window_position="1" title="Milis mesaj" icon-name="cdr" allow-shrink="false" width-request="'$2'">
|
||
<vbox>
|
||
<hbox>
|
||
<frame>
|
||
<pixmap icon_size="6">
|
||
<input file stock="'$3'"></input>
|
||
</pixmap>
|
||
</frame>
|
||
<frame>
|
||
<text wrap="true"><label>"'$1'"</label></text>
|
||
</frame>
|
||
</hbox>
|
||
<hbox>
|
||
<button ok></button>
|
||
</hbox>
|
||
</vbox>
|
||
<action signal="hide">exit:abort main window by X</action>
|
||
</window>
|
||
' | gtkdialog -s
|
||
}
|
||
|
||
## $1=message $2=width $3=icon(file)
|
||
gtk_message_custom(){
|
||
echo '
|
||
<window window_position="1" title="Milis mesaj" icon-name="cdr" allow-shrink="false" width-request="'$2'">
|
||
<vbox>
|
||
<hbox>
|
||
<frame>
|
||
<pixmap icon_size="6">
|
||
<input file>'$3'</input>
|
||
</pixmap>
|
||
</frame>
|
||
<frame>
|
||
<text use-markup="true" wrap="true"><label>"'$1'"</label></text>
|
||
</frame>
|
||
</hbox>
|
||
<hbox>
|
||
<button ok></button>
|
||
</hbox>
|
||
</vbox>
|
||
<action signal="hide">exit:abort main window by X</action>
|
||
</window>
|
||
' | gtkdialog -s
|
||
}
|
||
|
||
gtk_choose_file(){
|
||
echo '
|
||
<text label="'$1'"></text>
|
||
<entry editable="false" fs-title="'$2'" fs-action="'$3'" fs-folder="'$4'" height-request="32">
|
||
<variable>'$6'</variable>
|
||
</entry>
|
||
<button>
|
||
<variable>'$5'</variable>
|
||
<input file stock="gtk-find"></input>
|
||
<action>fileselect:'$6'</action>
|
||
</button>
|
||
'
|
||
}
|
||
|
||
start_window(){
|
||
echo '<window window_position="1" title="'$1'" icon-name="'$2'" allow-shrink="false" width-request="'$3'" height-request="'$4'">
|
||
<vbox margin="10">'
|
||
}
|
||
|
||
end_window(){
|
||
echo '</vbox><action signal="hide">exit:Cancel</action></window>'
|
||
}
|
||
|
||
txtmarkup(){
|
||
echo '<text use-markup="true" width-request="'$1'" selectable="true" can-focus="no"><label>"'$2'"</label></text>'
|
||
}
|
||
|
||
txtcolor(){
|
||
echo '<text use-markup="true" width-request="'$1'"><label>"<span fgcolor='"'$2'"' size='"'$3'"' weight='"'$4'"'>'$5'</span>"</label></text>'
|
||
}
|
||
|
||
txtfile(){
|
||
echo '<text width-request="'$1'" selectable="true" can-focus="no"><label>NoTextFound</label><input file>'$2'</input></text>'
|
||
}
|
||
|
||
pixmapstock(){
|
||
echo '<pixmap><input file stock="'$1'"></input></pixmap>'
|
||
}
|
||
|
||
pixmapicon(){
|
||
echo '<pixmap><height>'$1'</height><width>'$2'</width><input file icon="'$3'"></input></pixmap>'
|
||
}
|
||
|
||
pixmapfile(){
|
||
echo '<pixmap><height>'$1'</height><width>'$2'</width><input file>'$3'</input></pixmap>'
|
||
}
|
||
|
||
vsep(){
|
||
echo '<vseparator></vseparator>'
|
||
}
|
||
|
||
hsep(){
|
||
echo '<hseparator></hseparator>'
|
||
}
|
||
|
||
butstock(){
|
||
echo '<button use-underline="true"><variable>'$1'</variable><label>'$2'</label><input file stock="'$3'"></input>'
|
||
}
|
||
|
||
buticon(){
|
||
echo '<button><variable>'$1'</variable><label>'$2'</label><input file icon="'$3'"></input>'
|
||
}
|
||
|
||
butok(){
|
||
echo '<button ok></button>'
|
||
}
|
||
|
||
butcancel(){
|
||
echo '<button cancel></button>'
|
||
}
|
||
|
||
butyes(){
|
||
echo '<button yes></button>'
|
||
}
|
||
|
||
butno(){
|
||
echo '<button no></button>'
|
||
}
|
||
|
||
butreturn(){
|
||
echo '<button tooltip-text="'$1'" use-underline="true"><label>_Return</label><input file stock="gtk-go-back"></input><sensitive>'$2'</sensitive>'
|
||
}
|
||
|
||
butcustom(){
|
||
echo '<button tooltip-text="'$1'" sensitive="'$6'"><label>'$2'</label><variable>'$3'</variable><input file '$4'="'$5'"></input>'
|
||
}
|
||
|
||
rbut(){
|
||
echo '<radiobutton active="'$1'"><variable>'$2'</variable><label>'$3'</label>'
|
||
}
|
||
|
||
chkbox(){
|
||
echo '<checkbox active="'$1'"><label>"'$3'"</label><variable>'$2'</variable>'
|
||
}
|
||
|
||
blankline(){
|
||
echo '<text><label>""</label></text>'
|
||
}
|
||
|
||
entry(){
|
||
echo '<entry sensitive="'$1'" tooltip-text="'$2'"><default>"'$3'"</default><variable>'$4'</variable>'
|
||
}
|
||
|
||
table(){
|
||
echo '<table><variable>'$1'</variable><label>"'$4'"</label><height>'$2'</height><width>'$3'</width>'
|
||
}
|
||
|
||
if [ "$1" == "-h" ]; then
|
||
yellow start_window
|
||
blue "WindowTitle WindowIcon width height"
|
||
yellow "txtmarkup"
|
||
blue "width text"
|
||
yellow "txtcolor"
|
||
blue "width color size weight text"
|
||
yellow txtfile
|
||
blue "width file"
|
||
yellow pixmapstock
|
||
blue "gtk-name"
|
||
yellow pixmapicon
|
||
blue "height width icon"
|
||
yellow pixmapfile
|
||
blue "height width file"
|
||
yellow vsep
|
||
blue "vertical seperator (nothing required)"
|
||
yellow hsep
|
||
blue "horizontal seperator (nothing required)"
|
||
yellow butok
|
||
blue "nothing required"
|
||
yellow butcancel
|
||
blue "nothing required"
|
||
yellow butyes
|
||
blue "nothing required"
|
||
yellow butno
|
||
blue "nothing required"
|
||
yellow butstock
|
||
blue "variable label gtk-icon"
|
||
cyan "must be closed with </button>"
|
||
yellow buticon
|
||
blue "variable label icon"
|
||
cyan "must be closed with </button>"
|
||
yellow butreturn
|
||
blue "tooltip-text sensitive (true/false)"
|
||
cyan "must be closed with </button>"
|
||
yellow butcustom
|
||
blue "tooltip label variable stock/icon gtk-icon/iconname sensitive (true/false)"
|
||
cyan "must be closed with </button>"
|
||
yellow rbut
|
||
blue "active (true/false) variable label"
|
||
cyan "must be closed with </radiobutton>"
|
||
yellow chbox
|
||
blue "active (true/false) variable label"
|
||
cyan "must be closed with </checkbox>"
|
||
yellow blankline
|
||
blue "makes a blank line"
|
||
yellow entry
|
||
blue "sensitive tooltip-text defaulttext variable"
|
||
cyan "must be closed with </entry>"
|
||
yellow table
|
||
blue "variable height width label"
|
||
cyan "enter <item>something</item>"
|
||
cyan "Must be closed with </table>"
|
||
fi
|
||
|