port http://sourceforge.net/projects/tinysh to mbed enviroment

Dependents:   kl25z-tinyshell-demo HelloWorld_IHM02A1

This library is a port of tiny shell library to mbed enviroment.

Features

  • Autocomplete
  • Command history
  • Linux like

Tiny Shell minimal example

#include "mbed.h"
#include "tinysh.h"

//serial port to use 
Serial pc(USBTX, USBRX);

//custom function
void foo_fnt(int argc, char **argv)
{
    printf("foo command called\r\n");
    for(int i=0; i<argc; i++) {
        printf("argv[%d]=\"%s\"\r\n",i,argv[i]);
    }
}
//custom command
tinysh_cmd_t myfoocmd= {0,"foo","foo command","[args]",foo_fnt,0,0,0};



//mandatory tiny shell output function
void tinysh_char_out(unsigned char c)
{
    pc.putc(c);
}


void main(void){

   //configure serial baudrate
    pc.baud(115200);

   //print build date
    pc.printf("tiny shell build %s %s\r\n",__DATE__,__TIME__);

    //set prompt
    tinysh_set_prompt("$ ");

    //add custom commands here
    tinysh_add_command(&myfoocmd);

   //run command parser loop foverer 
    while(true) {
        tinysh_char_in( pc.getc() );
    }
}

tinysh.c

Committer:
murilopontes
Date:
2014-03-11
Revision:
0:78b46c0d5246
Child:
1:71580bf962fe

File content as of revision 0:78b46c0d5246:

/*
 * 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 */
        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)
                {
                  putchar('\n');
                  for(cm=cmd;cm;cm=cm->next)
                    {
                      int r=strstart(cm->name,__str);
                      if(r==FULLMATCH || r==PARTMATCH)
                        {
                          puts(cm->name);
                          putchar('\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;
}