Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of tinyshell by
tinysh.c
- Committer:
- jasonreiss
- Date:
- 2017-12-27
- Revision:
- 3:d027360b1892
- Parent:
- 2:c57737fee604
File content as of revision 3:d027360b1892:
/*
* tinysh.c
*
* Minimal portable shell
*
* Copyright (C) 2001 Michel Gutierrez <mig@nerim.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "tinysh.h"
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 64
#endif
#ifndef HISTORY_DEPTH
#define HISTORY_DEPTH 3
#endif
#ifndef MAX_ARGS
#define MAX_ARGS 4
#endif
#ifndef PROMPT_SIZE
#define PROMPT_SIZE 4
#endif
#ifndef TOPCHAR
#define TOPCHAR '/'
#endif
typedef unsigned char uchar;
/* redefine some useful and maybe missing utilities to avoid conflicts */
#define strlen tinysh_strlen
#define puts tinysh_puts
#define putchar tinysh_char_out
static void help_fnt(int argc, char **argv);
static tinysh_cmd_t help_cmd={
0,"help","Display help","<cr>",help_fnt,0,0,0 };
static uchar input_buffers[HISTORY_DEPTH][BUFFER_SIZE+1]={0};
static uchar trash_buffer[BUFFER_SIZE+1]={0};
static int cur_buf_index=0;
static uchar context_buffer[BUFFER_SIZE+1]={0};
static int cur_context=0;
static int cur_index=0;
static int echo=1;
static char prompt[PROMPT_SIZE+1]="$ ";
static tinysh_cmd_t *root_cmd=&help_cmd;
static tinysh_cmd_t *cur_cmd_ctx=0;
static void *tinysh_arg=0;
/* few useful utilities that may be missing */
static int strlen(uchar *s)
{
int i;
for(i=0;*s;s++,i++);
return i;
}
static void puts(char *s)
{
while(*s)
putchar(*s++);
}
/* callback for help function
*/
static void help_fnt(int argc, char **argv)
{
puts("? display help on given or available commands\r\n");
puts("<TAB> auto-completion\r\n");
puts("<cr> execute command line\r\n");
puts("CTRL-P recall previous input line\r\n");
puts("CTRL-N recall next input line\r\n");
puts("<any> treat as input character\r\n");
}
/*
*/
enum { NULLMATCH,FULLMATCH,PARTMATCH,UNMATCH,MATCH,AMBIG };
/* verify if the non-spaced part of s2 is included at the begining
* of s1.
* return FULLMATCH if s2 equal to s1, PARTMATCH if s1 starts with s2
* but there are remaining chars in s1, UNMATCH if s1 does not start with
* s2
*/
int strstart(uchar *s1, uchar *s2)
{
while(*s1 && *s1==*s2) { s1++; s2++; }
if(*s2==' ' || *s2==0)
{
if(*s1==0)
return FULLMATCH; /* full match */
else
return PARTMATCH; /* partial match */
}
else
return UNMATCH; /* no match */
}
/*
* check commands at given level with input string.
* _cmd: point to first command at this level, return matched cmd
* _str: point to current unprocessed input, return next unprocessed
*/
static int parse_command(tinysh_cmd_t **_cmd, uchar **_str)
{
uchar *str=*_str;
tinysh_cmd_t *cmd;
int matched_len=0;
tinysh_cmd_t *matched_cmd=0;
/* first eliminate first blanks */
while(*str==' ') str++;
if(!*str)
{
*_str=str;
return NULLMATCH; /* end of input */
}
/* first pass: count matches */
for(cmd=*_cmd;cmd;cmd=cmd->next)
{
int ret=strstart(cmd->name,str);
if(ret==FULLMATCH)
{
/* found full match */
while(*str && *str!=' ') str++;
while(*str==' ') str++;
*_str=str;
*_cmd=cmd;
return MATCH;
}
else if (ret==PARTMATCH)
{
if(matched_cmd)
{
*_cmd=matched_cmd;
return AMBIG;
}
else
{
matched_cmd=cmd;
}
}
else /* UNMATCH */
{
}
}
if(matched_cmd)
{
while(*str && *str!=' ') str++;
while(*str==' ') str++;
*_cmd=matched_cmd;
*_str=str;
return MATCH;
}
else
return UNMATCH;
}
/* create a context from current input line
*/
static void do_context(tinysh_cmd_t *cmd, uchar *str)
{
while(*str)
context_buffer[cur_context++]=*str++;
context_buffer[cur_context]=0;
cur_cmd_ctx=cmd;
}
/* execute the given command by calling callback with appropriate
* arguments
*/
static void exec_command(tinysh_cmd_t *cmd, uchar *str)
{
char *argv[MAX_ARGS];
int argc=0;
int i;
/* copy command line to preserve it for history */
for(i=0;i<BUFFER_SIZE;i++)
trash_buffer[i]=str[i];
str=trash_buffer;
/* cut into arguments */
argv[argc++]=cmd->name;
while(*str && argc<MAX_ARGS)
{
while(*str==' ') str++;
if(*str==0)
break;
argv[argc++]=str;
while(*str!=' ' && *str) str++;
if(!*str) break;
*str++=0;
}
/* call command function if present */
if(cmd->function)
{
tinysh_arg=cmd->arg;
cmd->function(argc,&argv[0]);
}
}
/* try to execute the current command line
*/
static int exec_command_line(tinysh_cmd_t *cmd, uchar *_str)
{
uchar *str=_str;
while(1)
{
int ret;
ret=parse_command(&cmd,&str);
if(ret==MATCH) /* found unique match */
{
if(cmd)
{
if(!cmd->child) /* no sub-command, execute */
{
exec_command(cmd,str);
return 0;
}
else
{
if(*str==0) /* no more input, this is a context */
{
do_context(cmd,_str);
return 0;
}
else /* process next command word */
{
cmd=cmd->child;
}
}
}
else /* cmd == 0 */
{
return 0;
}
}
else if(ret==AMBIG)
{
puts("ambiguity: ");
puts(str);
puts("\r\n");
return 0;
}
else if(ret==UNMATCH) /* UNMATCH */
{
puts("no match: ");
puts(str);
puts("\r\n");
return 0;
}
else { /* NULLMATCH */
puts("\r\n");
return 0;
}
}
}
/* display help for list of commands
*/
static void display_child_help(tinysh_cmd_t *cmd)
{
tinysh_cmd_t *cm;
int len=0;
puts("\r\n");
for(cm=cmd;cm;cm=cm->next)
if(len<strlen(cm->name))
len=strlen(cm->name);
for(cm=cmd;cm;cm=cm->next)
if(cm->help)
{
int i;
puts(cm->name);
for(i=strlen(cm->name);i<len+2;i++)
putchar(' ');
puts(cm->help);
puts("\r\n");
}
}
/* try to display help for current comand line
*/
static int help_command_line(tinysh_cmd_t *cmd, uchar *_str)
{
uchar *str=_str;
while(1)
{
int ret;
ret=parse_command(&cmd,&str);
if(ret==MATCH && *str==0) /* found unique match or empty line */
{
tinysh_cmd_t *cm;
int len=0;
if(cmd->child) /* display sub-commands help */
{
display_child_help(cmd->child);
return 0;
}
else /* no sub-command, show single help */
{
if(*(str-1)!=' ')
putchar(' ');
if(cmd->usage)
puts(cmd->usage);
puts(": ");
if(cmd->help)
puts(cmd->help);
else
puts("no help available");
puts("\r\n");
}
return 0;
}
else if(ret==MATCH && *str)
{ /* continue processing the line */
cmd=cmd->child;
}
else if(ret==AMBIG)
{
puts("\r\nambiguity: ");
puts(str);
puts("\r\n");
return 0;
}
else if(ret==UNMATCH)
{
puts("\r\nno match: ");
puts(str);
puts("\r\n");
return 0;
}
else /* NULLMATCH */
{
if(cur_cmd_ctx)
display_child_help(cur_cmd_ctx->child);
else
display_child_help(root_cmd);
return 0;
}
}
}
/* try to complete current command line
*/
static int complete_command_line(tinysh_cmd_t *cmd, uchar *_str)
{
uchar *str=_str;
while(1)
{
int ret;
int common_len=BUFFER_SIZE;
int _str_len;
int i;
uchar *__str=str;
tinysh_cmd_t *_cmd=cmd;
ret=parse_command(&cmd,&str);
for(_str_len=0;__str[_str_len]&&__str[_str_len]!=' ';_str_len++);
if(ret==MATCH && *str)
{
cmd=cmd->child;
}
else if(ret==AMBIG || ret==MATCH || ret==NULLMATCH)
{
tinysh_cmd_t *cm;
tinysh_cmd_t *matched_cmd=0;
int nb_match=0;
for(cm=cmd;cm;cm=cm->next)
{
int r=strstart(cm->name,__str);
if(r==FULLMATCH)
{
for(i=_str_len;cmd->name[i];i++)
tinysh_char_in(cmd->name[i]);
if(*(str-1)!=' ')
tinysh_char_in(' ');
if(!cmd->child)
{
if(cmd->usage)
{
puts(cmd->usage);
putchar('\n');
return 1;
}
else
return 0;
}
else
{
cmd=cmd->child;
break;
}
}
else if(r==PARTMATCH)
{
nb_match++;
if(!matched_cmd)
{
matched_cmd=cm;
common_len=strlen(cm->name);
}
else
{
for(i=_str_len;cm->name[i] && i<common_len &&
cm->name[i]==matched_cmd->name[i];i++);
if(i<common_len)
common_len=i;
}
}
}
if(cm)
continue;
if(matched_cmd)
{
if(_str_len==common_len)
{
puts("\r\n");
for(cm=cmd;cm;cm=cm->next)
{
int r=strstart(cm->name,__str);
if(r==FULLMATCH || r==PARTMATCH)
{
puts(cm->name);
puts("\r\n");
}
}
return 1;
}
else
{
for(i=_str_len;i<common_len;i++)
tinysh_char_in(matched_cmd->name[i]);
if(nb_match==1)
tinysh_char_in(' ');
}
}
return 0;
}
else /* UNMATCH */
{
return 0;
}
}
}
/* start a new line
*/
static void start_of_line()
{
/* display start of new line */
puts(prompt);
if(cur_context)
{
puts(context_buffer);
puts("> ");
}
cur_index=0;
}
/* character input
*/
static void _tinysh_char_in(uchar c)
{
uchar *line=input_buffers[cur_buf_index];
if(c=='\n' || c=='\r') /* validate command */
{
tinysh_cmd_t *cmd;
int context=0;
/* first, echo the newline */
if(echo)
putchar(c);
while(*line && *line==' ') line++;
if(*line) /* not empty line */
{
cmd=cur_cmd_ctx?cur_cmd_ctx->child:root_cmd;
exec_command_line(cmd,line);
cur_buf_index=(cur_buf_index+1)%HISTORY_DEPTH;
cur_index=0;
input_buffers[cur_buf_index][0]=0;
}
start_of_line();
}
else if(c==TOPCHAR) /* return to top level */
{
if(echo)
putchar(c);
cur_context=0;
cur_cmd_ctx=0;
}
else if(c==8 || c==127) /* backspace */
{
if(cur_index>0)
{
puts("\b \b");
cur_index--;
line[cur_index]=0;
}
}
else if(c==16) /* CTRL-P: back in history */
{
int prevline=(cur_buf_index+HISTORY_DEPTH-1)%HISTORY_DEPTH;
if(input_buffers[prevline][0])
{
line=input_buffers[prevline];
/* fill the rest of the line with spaces */
while(cur_index-->strlen(line))
puts("\b \b");
putchar('\r');
start_of_line();
puts(line);
cur_index=strlen(line);
cur_buf_index=prevline;
}
}
else if(c==14) /* CTRL-N: next in history */
{
int nextline=(cur_buf_index+1)%HISTORY_DEPTH;
if(input_buffers[nextline][0])
{
line=input_buffers[nextline];
/* fill the rest of the line with spaces */
while(cur_index-->strlen(line))
puts("\b \b");
putchar('\r');
start_of_line();
puts(line);
cur_index=strlen(line);
cur_buf_index=nextline;
}
}
else if(c=='?') /* display help */
{
tinysh_cmd_t *cmd;
cmd=cur_cmd_ctx?cur_cmd_ctx->child:root_cmd;
help_command_line(cmd,line);
start_of_line();
puts(line);
cur_index=strlen(line);
}
else if(c==9 || c=='!') /* TAB: autocompletion */
{
tinysh_cmd_t *cmd;
cmd=cur_cmd_ctx?cur_cmd_ctx->child:root_cmd;
if(complete_command_line(cmd,line))
{
start_of_line();
puts(line);
}
cur_index=strlen(line);
}
else /* any input character */
{
if(cur_index<BUFFER_SIZE)
{
if(echo)
putchar(c);
line[cur_index++]=c;
line[cur_index]=0;
}
}
}
/* new character input */
void tinysh_char_in(uchar c)
{
/*
* filter characters here
*/
_tinysh_char_in(c);
}
/* add a new command */
void tinysh_add_command(tinysh_cmd_t *cmd)
{
tinysh_cmd_t *cm;
if(cmd->parent)
{
cm=cmd->parent->child;
if(!cm)
{
cmd->parent->child=cmd;
}
else
{
while(cm->next) cm=cm->next;
cm->next=cmd;
}
}
else if(!root_cmd)
{
root_cmd=cmd;
}
else
{
cm=root_cmd;
while(cm->next) cm=cm->next;
cm->next=cmd;
}
}
/* modify shell prompt
*/
void tinysh_set_prompt(char *str)
{
int i;
for(i=0;str[i] && i<PROMPT_SIZE;i++)
prompt[i]=str[i];
prompt[i]=0;
/* force prompt display by generating empty command */
tinysh_char_in('\r');
}
/* return current command argument
*/
void *tinysh_get_arg()
{
return tinysh_arg;
}
/* string to decimal/hexadecimal conversion
*/
unsigned long tinysh_atoxi(char *s)
{
int ishex=0;
unsigned long res=0;
if(*s==0) return 0;
if(*s=='0' && *(s+1)=='x')
{
ishex=1;
s+=2;
}
while(*s)
{
if(ishex)
res*=16;
else
res*=10;
if(*s>='0' && *s<='9')
res+=*s-'0';
else if(ishex && *s>='a' && *s<='f')
res+=*s+10-'a';
else if(ishex && *s>='A' && *s<='F')
res+=*s+10-'A';
else
break;
s++;
}
return res;
}
