shellmatta.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  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->hereStartIdx = 0u;
  87. inst->hereDelimiterIdx = 0u;
  88. inst->hereLength = 0u;
  89. inst->mode = SHELLMATTA_MODE_INSERT;
  90. inst->cmdList = &helpCmd;
  91. inst->cmdListIsConst = false;
  92. if(NULL != cmdList)
  93. {
  94. helpCmd.next = (shellmatta_cmd_t *) cmdList;
  95. inst->cmdListIsConst = true;
  96. }
  97. inst->magic = SHELLMATTA_MAGIC;
  98. *handle = (shellmatta_handle_t)inst;
  99. /** -# print the first prompt */
  100. utils_terminateInput(inst);
  101. }
  102. return SHELLMATTA_OK;
  103. }
  104. /**
  105. * @brief adds a command to the command list alphabetically ordered
  106. * @param[in] handle shellmatta instance handle
  107. * @param[in] cmd pointer to the command to add type #shellmatta_cmd_t
  108. * @return errorcode #SHELLMATTA_OK
  109. * #SHELLMATTA_USE_FAULT (param err)
  110. * SHELLMATTA_DUPLICATE
  111. */
  112. shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd)
  113. {
  114. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  115. shellmatta_cmd_t *tempCmd;
  116. shellmatta_cmd_t **prevCmd;
  117. bool cmdPlaced = false;
  118. shellmatta_retCode_t ret = SHELLMATTA_OK;
  119. int cmdDiff = 0;
  120. int aliasDiff = 0;
  121. /** -# check parameters for plausibility */
  122. if( (NULL != inst)
  123. && (SHELLMATTA_MAGIC == inst->magic)
  124. && (false == inst->cmdListIsConst))
  125. {
  126. tempCmd = inst->cmdList;
  127. prevCmd = &inst->cmdList;
  128. /** -# register first command as list entry */
  129. if (NULL == tempCmd)
  130. {
  131. inst->cmdList = cmd;
  132. cmd->next = NULL;
  133. }
  134. /** -# append the new command sorted */
  135. else
  136. {
  137. while ((false == cmdPlaced) && (SHELLMATTA_OK == ret))
  138. {
  139. cmdDiff = strcmp(tempCmd->cmd, cmd->cmd);
  140. aliasDiff = strcmp(tempCmd->cmdAlias, cmd->cmdAlias);
  141. /** -# check for a duplicate command */
  142. if((0u == cmdDiff) || (0u == aliasDiff))
  143. {
  144. ret = SHELLMATTA_DUPLICATE;
  145. }
  146. else if(0 < cmdDiff)
  147. {
  148. cmd->next = tempCmd;
  149. *prevCmd = cmd;
  150. cmdPlaced = true;
  151. }
  152. else if(NULL == tempCmd->next)
  153. {
  154. tempCmd->next = cmd;
  155. cmd->next = NULL;
  156. cmdPlaced = true;
  157. }
  158. else
  159. {
  160. /* nothing to do */
  161. }
  162. prevCmd = &(tempCmd->next);
  163. tempCmd = tempCmd->next;
  164. }
  165. }
  166. }
  167. else
  168. {
  169. ret = SHELLMATTA_USE_FAULT;
  170. }
  171. return ret;
  172. }
  173. /**
  174. * @brief processes the passed amount of data
  175. * @param[in] handle shellmatta instance handle
  176. * @param[in] data pointer to input data to process
  177. * @param[in] size length of input data to process
  178. * @return errorcode #SHELLMATTA_OK
  179. * #SHELLMATTA_USE_FAULT
  180. */
  181. shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle,
  182. char *data,
  183. uint32_t size)
  184. {
  185. shellmatta_cmd_t *cmd;
  186. uint8_t cmdExecuted = 0u;
  187. uint32_t cmdLen;
  188. char *tempString;
  189. char *argumentString;
  190. uint32_t argumentLength;
  191. uint32_t byteCounter;
  192. uint32_t idx;
  193. shellmatta_retCode_t ret = SHELLMATTA_OK;
  194. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  195. /** -# check parameters for plausibility */
  196. if( (NULL != inst)
  197. && (SHELLMATTA_MAGIC == inst->magic))
  198. {
  199. /** -# process byte wise */
  200. for (byteCounter = 0u; byteCounter < size; byteCounter++)
  201. {
  202. /** -# handle escape sequences */
  203. if(inst->escapeCounter != 0u)
  204. {
  205. escape_handleSequence(inst, *data);
  206. }
  207. /** -# ignore newline as first character (to be compatible to
  208. * terminals sending newline after return */
  209. else if((0u == inst->inputCount) && ('\n' == *data))
  210. {
  211. /* do nothing */
  212. }
  213. /** -# handle return as start of processing the command */
  214. else if ('\r' == *data)
  215. {
  216. if(0u == inst->hereLength)
  217. {
  218. /**
  219. * \dot
  220. * digraph heredocParser {
  221. * start -> wait [ label="<< in first line - store delimiter" ];
  222. * wait -> wait [ label="delimiter not detected" ];
  223. * wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ];
  224. * }
  225. * \enddot */
  226. /** -# check for heredoc */
  227. tempString = strstr(inst->buffer, "<<");
  228. if(NULL != tempString)
  229. {
  230. /*' -# check if length of heredoc delimiter is valid */
  231. if(inst->inputCount > ((uint32_t)(tempString - inst->buffer) + 2u))
  232. {
  233. inst->hereStartIdx = (uint32_t)(tempString - inst->buffer);
  234. inst->hereDelimiterIdx = inst->hereStartIdx + 2u;
  235. while((inst->hereDelimiterIdx < inst->inputCount)
  236. && ( ('\0' == inst->buffer[inst->hereDelimiterIdx])
  237. || (' ' == inst->buffer[inst->hereDelimiterIdx])))
  238. {
  239. inst->hereDelimiterIdx ++;
  240. }
  241. inst->hereLength = inst->inputCount - inst->hereDelimiterIdx;
  242. inst->dirty = true;
  243. utils_insertChars(inst, data, 1);
  244. inst->lastNewlineIdx = inst->inputCount;
  245. }
  246. else
  247. {
  248. inst->hereLength = 0u;
  249. /** -# store the current command and reset the history buffer */
  250. inst->dirty = true;
  251. history_storeCmd(inst);
  252. history_reset(inst);
  253. }
  254. }
  255. else
  256. {
  257. argumentString = inst->buffer;
  258. argumentLength = inst->inputCount;
  259. /** -# store the current command and reset the history buffer */
  260. inst->dirty = true;
  261. history_storeCmd(inst);
  262. history_reset(inst);
  263. }
  264. }
  265. else
  266. {
  267. tempString = &inst->buffer[inst->lastNewlineIdx];
  268. cmdLen = inst->inputCount - inst->lastNewlineIdx;
  269. /** -# skip newline characters before comparison */
  270. while(('\n' == *tempString) || ('\r' == *tempString))
  271. {
  272. tempString ++;
  273. cmdLen --;
  274. }
  275. if( (inst->hereLength == cmdLen)
  276. && (0 == strncmp( &inst->buffer[inst->hereDelimiterIdx],
  277. tempString,
  278. inst->hereLength)))
  279. {
  280. argumentLength = inst->lastNewlineIdx;
  281. /** -# store the current command and reset the history buffer */
  282. inst->dirty = true;
  283. history_storeCmd(inst);
  284. history_reset(inst);
  285. /* TODO it is difficult to store the complete command in the history buffer if it is restructured before...
  286. * So this should be an extra function that can be called after parsing the command and before calling the command funktion */
  287. for(idx = 1u; idx <= inst->hereStartIdx; idx++)
  288. {
  289. inst->buffer[inst->hereDelimiterIdx + inst->hereLength - idx] = inst->buffer[inst->hereStartIdx - idx];
  290. }
  291. argumentString = &(inst->buffer[inst->hereDelimiterIdx + inst->hereLength - inst->hereStartIdx]);
  292. argumentLength = inst->lastNewlineIdx - ((inst->hereDelimiterIdx + inst->hereLength) - inst->hereStartIdx);
  293. inst->hereLength = 0u;
  294. }
  295. else
  296. {
  297. inst->lastNewlineIdx = inst->inputCount;
  298. utils_insertChars(inst, data, 1);
  299. }
  300. }
  301. if(0u == inst->hereLength)
  302. {
  303. cmd = inst->cmdList;
  304. argumentString[argumentLength] = 0u;
  305. /** -# determine the cmd len (chars until first space or \0 is found */
  306. cmdLen = 0u;
  307. while( (cmdLen < argumentLength)
  308. && (' ' != argumentString[cmdLen])
  309. && ('\r' != argumentString[cmdLen])
  310. && ('\n' != argumentString[cmdLen])
  311. && ('\0' != argumentString[cmdLen]))
  312. {
  313. cmdLen ++;
  314. }
  315. /** -# search for a matching command */
  316. while (NULL != cmd)
  317. {
  318. /** -# compare command string and length */
  319. if ( ((0 == strncmp( argumentString,
  320. cmd->cmd,
  321. cmdLen))
  322. && (cmdLen == strlen(cmd->cmd)))
  323. || ((0 == strncmp( argumentString,
  324. cmd->cmdAlias,
  325. cmdLen))
  326. && (cmdLen == strlen(cmd->cmdAlias))))
  327. {
  328. inst->write("\r\n", 2u);
  329. cmdExecuted = 1u;
  330. cmd->cmdFct(inst, argumentString, argumentLength);
  331. cmd = NULL;
  332. }
  333. else
  334. {
  335. cmd = cmd->next;
  336. }
  337. }
  338. if ((cmdExecuted == 0u) && (inst->inputCount > 0))
  339. {
  340. inst->write("\r\nCommand: ", 11u);
  341. inst->write(argumentString, argumentLength);
  342. inst->write(" not found", 10u);
  343. }
  344. utils_terminateInput(inst);
  345. }
  346. }
  347. /** -# check for tabulator key - auto complete */
  348. else if('\t' == *data)
  349. {
  350. inst->dirty = true;
  351. autocomplete_run(inst);
  352. }
  353. /** -# check for cancel -
  354. * terminate current input and print prompt again */
  355. else if(3 == *data)
  356. {
  357. inst->dirty = false;
  358. history_reset(inst);
  359. utils_terminateInput(inst);
  360. }
  361. /** -# check for backspace */
  362. else if('\b' == *data)
  363. {
  364. inst->dirty = true;
  365. utils_removeChars(inst, 1u, true);
  366. }
  367. /** -# check for delete key */
  368. else if(0x7eu == *data)
  369. {
  370. inst->dirty = true;
  371. utils_removeChars(inst, 1u, false);
  372. }
  373. /** -# check for start of escape sequence */
  374. else if('\x1b' == *data)
  375. {
  376. inst->escapeCounter = 1u;
  377. }
  378. else
  379. {
  380. inst->dirty = true;
  381. utils_insertChars(inst, data, 1);
  382. }
  383. /** -# reset tab counter on not a tab */
  384. if ('\t' != *data)
  385. {
  386. inst->tabCounter = 0u;
  387. }
  388. data ++;
  389. }
  390. }
  391. else
  392. {
  393. ret = SHELLMATTA_USE_FAULT;
  394. }
  395. return ret;
  396. }
  397. /**
  398. * @brief simple write function to write a datastream to the output of an instance
  399. * @param[in] handle shellmatta instance handle
  400. * @param[in] data data to be written
  401. * @param[in] length amount of data to be written
  402. * @return
  403. */
  404. shellmatta_retCode_t shellmatta_write( shellmatta_handle_t handle,
  405. char *data,
  406. uint32_t length)
  407. {
  408. shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT;
  409. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  410. /** -# check parameters for plausibility */
  411. if( (NULL != inst)
  412. && (SHELLMATTA_MAGIC == inst->magic))
  413. {
  414. /** -# pass the data to the write function of the instance */
  415. ret = inst->write(data, length);
  416. }
  417. return ret;
  418. }
  419. #ifndef SHELLMATTA_STRIP_PRINTF
  420. /**
  421. * @brief printf like function to print output to the instances output
  422. * @param[in] handle shellmatta instance handle
  423. * @param[in] fmt format string and parameters
  424. * @return errorcode #SHELLMATTA_OK
  425. * #SHELLMATTA_USE_FAULT (no valid instance)
  426. * #SHELLMATTA_ERROR (buffer overflow or format err)
  427. */
  428. shellmatta_retCode_t shellmatta_printf( shellmatta_handle_t handle,
  429. const char *fmt,
  430. ...)
  431. {
  432. char outputBuffer[SHELLMATTA_OUTPUT_BUFFER_SIZE];
  433. va_list arg;
  434. int length;
  435. shellmatta_retCode_t ret = SHELLMATTA_OK;
  436. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  437. /** -# check parameters for plausibility */
  438. if( (NULL != inst)
  439. && (SHELLMATTA_MAGIC == inst->magic))
  440. {
  441. /** -# assemble output string and write it */
  442. va_start(arg, fmt);
  443. length = vsnprintf(outputBuffer, SHELLMATTA_OUTPUT_BUFFER_SIZE, fmt, arg);
  444. va_end(arg);
  445. if(length < 0)
  446. {
  447. ret = SHELLMATTA_ERROR;
  448. }
  449. else
  450. {
  451. inst->write(outputBuffer, length);
  452. }
  453. }
  454. else
  455. {
  456. ret = SHELLMATTA_USE_FAULT;
  457. }
  458. return ret;
  459. }
  460. #endif
  461. /** @} */