An interactive shell for generating Markdown documents, with
support for embedding executable commands in your markdown!
Usage: mdshell <path/to/file.md>
Write the file line by line.
Hit ENTER 3 times to exit and save the file.
Demo:
More info:
I wanted a way to create Markdown documents in the terminal, and to
embed shell commands in the Markdown, so I could generate the final
file dynamically - the results of the embedded shell commands are
saved in the final file.
The script:
Code: Select all
#!/bin/bash
# sc0ttman
# Based on an example script by Stéphane Chazelas.
if [ ! "$1" ] || [ "$1" = "-h" ] || [ "$1" = "-help" ] || [ "$1" = "--help" ];then
cat << HELP_MSG
An interactive shell for writing Markdown documents, with
support for embedding bash sub-shells \$() in your Markdown!
Usage: mdshell <path-to-file> # creates the file if it doesn't exist
Note backtick subshells (\`\`) not supported, only \$()
Example: Add \$(uname) in your Markdown and the saved
file will contain your system info.
HELP_MSG
exit
fi
# define default settings
# define run-time vars used by this program
prev_line=none
line_is_bash=false
line_was_bash=false
multi_line_string=false
was_multi_line_string=false
command=''
command_line_count=0
if [ ! -f "$1" ];then
mkdir -p "$(dirname "$1")"
touch "$1"
fi
while :
do
# the user just hit ENTER, so read the line they input
# -e use readline
# -r dont escape backslashes include them as literals chars
read -er line
# if line starts with ``` we know it's a markdown line, not part of a bash sub-shell
[ "$(echo "$line" | grep '^```')" != "" ] && line_is_bash=false
# if the line contains $( then the user is starting a bash sub-shell on this line
[ "$(echo "$line" | grep -m1 '$(')" != "" ] && line_is_bash=true
# if the line is not bash, then it's also not a multi line string
#if [ "$line_is_bash" = false ];then
# multi_line_string=false
#fi
# if the previous last (one before last entered) was part of a multi-line string,
# then this line probably is too, and so it's part of a bash command
[ "$was_multi_line_string" = true ] && line_was_bash=true
# the the line given was bash, not markdown, we need to interpret it
if [ "$line_is_bash" = true ];then
# count the bumber of double quotes, and chck if that number is even
quote_count=$(echo "$line" | tr -cd '"' | wc -c)
quote_count_is_even=$(( ${quote_count} %2 ))
# if $line has quotes, and an odd number of them, we moved in/out of a string
if [ $quote_count -gt 0 ] && [ $quote_count_is_even -ne 0 ];then
# toggle whether in a string or not
if [ "$multi_line_string" = true ];then
multi_line_string=false # toggle it
else
multi_line_string=true # toggle it
fi
fi
# while we are in a bash sub-shell, lets save each line in the $command var
if [ "$command" = "" ];then
command="$line"
else
command="$command\n$line"
fi
command_line_count=$(($command_line_count + 1))
# check if the command has a closing parenthesis ) - cos then we might be ending the sub-shell
subshell_has_ended="$(echo "$command" | grep -Eq ')' && echo true || echo false)"
# if line is part of a multi string, it's been saved into $command, so skip
if [ "$multi_line_string" = true ];then
line_is_bash=true
# else if we detected the end of a sub-shell, lets evaluate it, get its output
# and then save that to our markdown file, instead of the bash commands themselves
elif [ "$subshell_has_ended" = true ];then
# strip any leading chars up to the sub-shell invocation '$(' and
# strip any chars after the sub-shell, and
# keep only the command
pre_text="$(echo "$command" | sed -e 's/$(.*//' -e 's/)$//')"
post_text="$(echo "$command" | sed 's/.*$(.*)//g')"
if [ "$pre_text" != "" ] || [ "$post_text" != "" ];then
command="$(echo "$command" | sed -e "s/^$pre_text//g" -e "s/$post_text//")"
[ "$post_text" = "$command" ] && post_text=""
fi
# if previous line was not part of a string, then it each was a separate command
if [ "${was_multi_line_string}" = false ];then
# each line is a separate command, so replace newlines with semi-colons
result="$(eval $(echo -e "${command//\\/\\\\}" | sed s'/\\n/;/g' | tr -d '`' | sed -e 's/$(//g' -e 's/)$//g') 2>/dev/null)"
retval=$?
else
result="$(eval $(echo -e "${command//\\/\\\\}" | tr -d '`' | sed -e 's/$(//g' -e 's/)$//g') 2>/dev/null)"
retval=$?
fi
[ "$was_multi_line_string" = false ] && line_is_bash=false
[ "$multi_line_string" = false ] && line_is_bash=false
[ $retval -eq 0 ] && text="$text\n$pre_text$result$post_text" && line_is_bash=false
fi
elif [ "$line_is_bash" = false ];then
command_line_count=0
command=""
text="$text\n$line"
multi_line_string=false
[ -z "${prev_line}" ] && [ -z "$line" ] && break
fi
###### done working out what was in $line #######
# xmessage "
# was multi-line string: $was_multi_line_string
# multi-line string: $multi_line_string
# line_was_bash: $line_was_bash
# line_is_bash: $line_is_bash
# command line count: '${command_line_count}'
# command: '${command//\\/\\\\}'
# result: '${result}'
# "
was_multi_line_string=${multi_line_string}
line_was_bash=${line_is_bash}
prev_line="$line"
[ ${retval:-1} -eq 0 ] && result=''
done
echo -e "$text" > "$1"
echo
echo '------------------------------'
echo
echo "File $1 created:"
echo
less -X "$1"
unset prev_line
unset line_is_bash
unset multi_line_string
unset quote_count_is_even
unset file
unset text
unset command
unset command_line_count
unset result
unset retval
exit 0