shellmatta.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /*
  2. * Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
  3. *
  4. * This Source Code Form is subject to the terms of the Mozilla Public
  5. * License, v. 2.0. If a copy of the MPL was not distributed with this
  6. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  7. */
  8. /**
  9. * @file shellmatta.c
  10. * @brief Main implementation of the Shellmatta terminal implementation
  11. * @author Stefan Strobel <stefan.strobel@shimatta.net>
  12. */
  13. /**
  14. * @addtogroup shellmatta_private
  15. * @{
  16. */
  17. #include "shellmatta.h"
  18. #include "shellmatta_autocomplete.h"
  19. #include "shellmatta_history.h"
  20. #include "shellmatta_utils.h"
  21. #include "shellmatta_escape.h"
  22. #include <stddef.h>
  23. #include <string.h>
  24. #include <stdarg.h>
  25. #include <stdio.h>
  26. /**
  27. * @}
  28. * @addtogroup shellmatta_api
  29. * @{
  30. */
  31. /**
  32. * @brief initialize the shellmatta terminal and provide all callbacks
  33. * @param[in,out] inst pointer to a shellmatta instance
  34. * @param[out] handle pointer to shellmatta handle -
  35. * has to be used for all furterh api calls
  36. * @param[in] buffer pointer to the input buffer to use
  37. * @param[in] bufferSize size of the provided input buffer
  38. * @param[in] historyBuffer pointer to the history buffer to use
  39. * NULL in case of no history buffer
  40. * @param[in] historyBufferSize size of the history buffer
  41. * 0 in case of no history buffer
  42. * @param[in] prompt pointer to prompt string - is printed
  43. * after each command
  44. * @param[in] cmdList constant command list if no dynamic
  45. * adding of commands is desired
  46. * @param[in] writeFct function pointer to output function
  47. */
  48. shellmatta_retCode_t shellmatta_doInit(
  49. shellmatta_instance_t *inst,
  50. shellmatta_handle_t *handle,
  51. char *buffer,
  52. uint32_t bufferSize,
  53. char *historyBuffer,
  54. uint32_t historyBufferSize,
  55. const char *prompt,
  56. const shellmatta_cmd_t *cmdList,
  57. shellmatta_write_t writeFct)
  58. {
  59. /** -# check parameters for plausibility */
  60. if( (NULL != inst)
  61. && (NULL != handle)
  62. && (NULL != buffer)
  63. && (0u != bufferSize)
  64. && (NULL != prompt)
  65. && (NULL != writeFct)
  66. && ((NULL != historyBuffer) || (0u == historyBufferSize)))
  67. {
  68. /** -# copy all provided buffers into the shellmatta instance */
  69. inst->buffer = buffer;
  70. inst->bufferSize = bufferSize;
  71. inst->inputCount = 0u;
  72. inst->lastNewlineIdx = 0u;
  73. inst->cursor = 0u;
  74. inst->historyBuffer = historyBuffer;
  75. inst->historyBufferSize = historyBufferSize;
  76. inst->historyStart = 0u;
  77. inst->historyEnd = 0u;
  78. inst->historyRead = 0u;
  79. inst->historyReadUp = true;
  80. inst->write = writeFct;
  81. inst->prompt = prompt;
  82. inst->echoEnabled = true;
  83. inst->dirty = false;
  84. inst->tabCounter = 0u;
  85. inst->escapeCounter = 0u;
  86. inst->hereLength = 0u;
  87. inst->mode = SHELLMATTA_MODE_INSERT;
  88. inst->cmdList = &helpCmd;
  89. inst->cmdListIsConst = false;
  90. if(NULL != cmdList)
  91. {
  92. helpCmd.next = (shellmatta_cmd_t *) cmdList;
  93. inst->cmdListIsConst = true;
  94. }
  95. inst->magic = SHELLMATTA_MAGIC;
  96. *handle = (shellmatta_handle_t)inst;
  97. /** -# print the first prompt */
  98. utils_terminateInput(inst);
  99. }
  100. return SHELLMATTA_OK;
  101. }
  102. /**
  103. * @brief adds a command to the command list alphabetically ordered
  104. * @param[in] handle shellmatta instance handle
  105. * @param[in] cmd pointer to the command to add type #shellmatta_cmd_t
  106. * @return errorcode #SHELLMATTA_OK
  107. * #SHELLMATTA_USE_FAULT (param err)
  108. * SHELLMATTA_DUPLICATE
  109. */
  110. shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd)
  111. {
  112. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  113. shellmatta_cmd_t *tempCmd;
  114. shellmatta_cmd_t **prevCmd;
  115. bool cmdPlaced = false;
  116. shellmatta_retCode_t ret = SHELLMATTA_OK;
  117. int cmdDiff = 0;
  118. int aliasDiff = 0;
  119. /** -# check parameters for plausibility */
  120. if( (NULL != inst)
  121. && (SHELLMATTA_MAGIC == inst->magic)
  122. && (false == inst->cmdListIsConst))
  123. {
  124. tempCmd = inst->cmdList;
  125. prevCmd = &inst->cmdList;
  126. /** -# register first command as list entry */
  127. if (NULL == tempCmd)
  128. {
  129. inst->cmdList = cmd;
  130. cmd->next = NULL;
  131. }
  132. /** -# append the new command sorted */
  133. else
  134. {
  135. while ((false == cmdPlaced) && (SHELLMATTA_OK == ret))
  136. {
  137. cmdDiff = strcmp(tempCmd->cmd, cmd->cmd);
  138. aliasDiff = strcmp(tempCmd->cmdAlias, cmd->cmdAlias);
  139. /** -# check for a duplicate command */
  140. if((0u == cmdDiff) || (0u == aliasDiff))
  141. {
  142. ret = SHELLMATTA_DUPLICATE;
  143. }
  144. else if(0 < cmdDiff)
  145. {
  146. cmd->next = tempCmd;
  147. *prevCmd = cmd;
  148. cmdPlaced = true;
  149. }
  150. else if(NULL == tempCmd->next)
  151. {
  152. tempCmd->next = cmd;
  153. cmd->next = NULL;
  154. cmdPlaced = true;
  155. }
  156. else
  157. {
  158. /* nothing to do */
  159. }
  160. prevCmd = &(tempCmd->next);
  161. tempCmd = tempCmd->next;
  162. }
  163. }
  164. }
  165. else
  166. {
  167. ret = SHELLMATTA_USE_FAULT;
  168. }
  169. return ret;
  170. }
  171. /**
  172. * @brief processes the passed amount of data
  173. * @param[in] handle shellmatta instance handle
  174. * @param[in] data pointer to input data to process
  175. * @param[in] size length of input data to process
  176. * @return errorcode #SHELLMATTA_OK
  177. * #SHELLMATTA_USE_FAULT
  178. */
  179. shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle,
  180. char *data,
  181. uint32_t size)
  182. {
  183. shellmatta_cmd_t *cmd;
  184. uint8_t cmdExecuted = 0u;
  185. uint32_t cmdLen;
  186. char *tempString;
  187. shellmatta_retCode_t ret = SHELLMATTA_OK;
  188. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  189. /** -# check parameters for plausibility */
  190. if( (NULL != inst)
  191. && (SHELLMATTA_MAGIC == inst->magic))
  192. {
  193. /** -# process byte wise */
  194. for (uint32_t i = 0u; i < size; i++)
  195. {
  196. /** -# handle escape sequences */
  197. if(inst->escapeCounter != 0u)
  198. {
  199. escape_handleSequence(inst, *data);
  200. }
  201. /** -# ignore newline as first character (to be compatible to
  202. * terminals sending newline after return */
  203. else if((0u == inst->inputCount) && ('\n' == *data))
  204. {
  205. /* do nothing */
  206. }
  207. /** -# handle return as start of processing the command */
  208. else if ('\r' == *data)
  209. {
  210. if(0u == inst->hereLength)
  211. {
  212. /**
  213. * \dot
  214. * digraph heredocParser {
  215. * start -> wait [ label="<< in first line - store delimiter" ];
  216. * wait -> wait [ label="delimiter not detected" ];
  217. * wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ];
  218. * }
  219. * \enddot */
  220. /** -# check for heredoc */
  221. tempString = strstr(inst->buffer, "<<");
  222. if(NULL != tempString)
  223. {
  224. /*' -# check if length of heredoc delimiter is valid */
  225. if(inst->inputCount > ((uint32_t)(tempString - inst->buffer) + 2u))
  226. {
  227. inst->hereLength = inst->inputCount - ((uint32_t)(tempString - inst->buffer) + 2u);
  228. if(sizeof(inst->hereDelimiter) < inst->hereLength)
  229. {
  230. inst->write("\r\nHeredoc delimiter too long\r\n", 30u);
  231. inst->inputCount = 0u;
  232. inst->hereLength = 0u;
  233. }
  234. else
  235. {
  236. /** -# store delimiter and remove it from the input buffer */
  237. strncpy(inst->hereDelimiter, &(tempString[2u]), inst->hereLength);
  238. inst->inputCount -= (inst->hereLength + 2u);
  239. inst->cursor = inst->inputCount;
  240. inst->dirty = true;
  241. utils_insertChars(inst, data, 1);
  242. inst->lastNewlineIdx = inst->inputCount;
  243. }
  244. }
  245. else
  246. {
  247. inst->hereLength = 0u;
  248. }
  249. }
  250. }
  251. else
  252. {
  253. tempString = &inst->buffer[inst->lastNewlineIdx];
  254. cmdLen = inst->inputCount - inst->lastNewlineIdx;
  255. /** -# skip newline characters before comparison */
  256. while(('\n' == *tempString) || ('\r' == *tempString))
  257. {
  258. tempString ++;
  259. cmdLen --;
  260. }
  261. if( (inst->hereLength == cmdLen)
  262. && (0 == strncmp( inst->hereDelimiter,
  263. tempString,
  264. inst->hereLength)))
  265. {
  266. inst->inputCount = inst->lastNewlineIdx;
  267. inst->hereLength = 0u;
  268. }
  269. else
  270. {
  271. inst->lastNewlineIdx = inst->inputCount;
  272. utils_insertChars(inst, data, 1);
  273. }
  274. }
  275. if(0u == inst->hereLength)
  276. {
  277. cmd = inst->cmdList;
  278. inst->buffer[inst->inputCount] = 0u;
  279. /** -# store the current command and reset the history buffer */
  280. inst->dirty = true;
  281. history_storeCmd(inst);
  282. history_reset(inst);
  283. /** -# determine the cmd len (chars until first space or \0 is found */
  284. cmdLen = 0u;
  285. while( (cmdLen < inst->inputCount)
  286. && (' ' != inst->buffer[cmdLen])
  287. && ('\0' != inst->buffer[cmdLen]))
  288. {
  289. cmdLen ++;
  290. }
  291. /** -# search for a matching command */
  292. while (NULL != cmd)
  293. {
  294. /** -# compare command string and length */
  295. if ( ((0 == strncmp( inst->buffer,
  296. cmd->cmd,
  297. cmdLen))
  298. && (cmdLen == strlen(cmd->cmd)))
  299. || ((0 == strncmp( inst->buffer,
  300. cmd->cmdAlias,
  301. cmdLen))
  302. && (cmdLen == strlen(cmd->cmdAlias))))
  303. {
  304. inst->write("\r\n", 2u);
  305. cmdExecuted = 1u;
  306. cmd->cmdFct(inst, inst->buffer, inst->inputCount);
  307. cmd = NULL;
  308. }
  309. else
  310. {
  311. cmd = cmd->next;
  312. }
  313. }
  314. if ((cmdExecuted == 0u) && (inst->inputCount > 0))
  315. {
  316. inst->buffer[inst->inputCount] = '\0';
  317. inst->write("\r\nCommand: ", 11u);
  318. inst->write(inst->buffer, inst->inputCount);
  319. inst->write(" not found", 10u);
  320. }
  321. utils_terminateInput(inst);
  322. }
  323. }
  324. /** -# check for tabulator key - auto complete */
  325. else if('\t' == *data)
  326. {
  327. inst->dirty = true;
  328. autocomplete_run(inst);
  329. }
  330. /** -# check for cancel -
  331. * terminate current input and print prompt again */
  332. else if(3 == *data)
  333. {
  334. inst->dirty = false;
  335. history_reset(inst);
  336. utils_terminateInput(inst);
  337. }
  338. /** -# check for backspace */
  339. else if('\b' == *data)
  340. {
  341. inst->dirty = true;
  342. utils_removeChars(inst, 1u, true);
  343. }
  344. /** -# check for delete key */
  345. else if(0x7eu == *data)
  346. {
  347. inst->dirty = true;
  348. utils_removeChars(inst, 1u, false);
  349. }
  350. /** -# check for start of escape sequence */
  351. else if('\e' == *data)
  352. {
  353. inst->escapeCounter = 1u;
  354. }
  355. else
  356. {
  357. inst->dirty = true;
  358. utils_insertChars(inst, data, 1);
  359. }
  360. /** -# reset tab counter on not a tab */
  361. if ('\t' != *data)
  362. {
  363. inst->tabCounter = 0u;
  364. }
  365. data ++;
  366. }
  367. }
  368. else
  369. {
  370. ret = SHELLMATTA_USE_FAULT;
  371. }
  372. return ret;
  373. }
  374. /**
  375. * @brief simple write function to write a datastream to the output of an instance
  376. * @param[in] handle shellmatta instance handle
  377. * @param[in] data data to be written
  378. * @param[in] length amount of data to be written
  379. * @return
  380. */
  381. shellmatta_retCode_t shellmatta_write( shellmatta_handle_t handle,
  382. char *data,
  383. uint32_t length)
  384. {
  385. shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT;
  386. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  387. /** -# check parameters for plausibility */
  388. if( (NULL != inst)
  389. && (SHELLMATTA_MAGIC == inst->magic))
  390. {
  391. /** -# pass the data to the write function of the instance */
  392. ret = inst->write(data, length);
  393. }
  394. return ret;
  395. }
  396. #ifndef SHELLMATTA_STRIP_PRINTF
  397. /**
  398. * @brief printf like function to print output to the instances output
  399. * @param[in] handle shellmatta instance handle
  400. * @param[in] fmt format string and parameters
  401. * @return errorcode #SHELLMATTA_OK
  402. * #SHELLMATTA_USE_FAULT (no valid instance)
  403. * #SHELLMATTA_ERROR (buffer overflow or format err)
  404. */
  405. shellmatta_retCode_t shellmatta_printf( shellmatta_handle_t handle,
  406. const char *fmt,
  407. ...)
  408. {
  409. char outputBuffer[SHELLMATTA_OUTPUT_BUFFER_SIZE];
  410. va_list arg;
  411. int length;
  412. shellmatta_retCode_t ret = SHELLMATTA_OK;
  413. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  414. /** -# check parameters for plausibility */
  415. if( (NULL != inst)
  416. && (SHELLMATTA_MAGIC == inst->magic))
  417. {
  418. /** -# assemble output string and write it */
  419. va_start(arg, fmt);
  420. length = vsnprintf(outputBuffer, SHELLMATTA_OUTPUT_BUFFER_SIZE, fmt, arg);
  421. va_end(arg);
  422. if(length < 0)
  423. {
  424. ret = SHELLMATTA_ERROR;
  425. }
  426. else
  427. {
  428. inst->write(outputBuffer, length);
  429. }
  430. }
  431. else
  432. {
  433. ret = SHELLMATTA_USE_FAULT;
  434. }
  435. return ret;
  436. }
  437. #endif
  438. /** @} */