shellmatta_opt.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. /*
  2. * Copyright (c) 2019 - 2024 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_opt.c
  10. * @brief option parser implementation of the shellmatta
  11. * @author Stefan Strobel <stefan.strobel@shimatta.net>
  12. */
  13. /**
  14. * @addtogroup shellmatta_opt
  15. * @{
  16. */
  17. #include "shellmatta_opt.h"
  18. #include "shellmatta_utils.h"
  19. #include "shellmatta.h"
  20. #include <string.h>
  21. /**
  22. * @brief finds the next parsable hunk of data in the input
  23. * @param[in] inst shellmatta instance
  24. * @return errorcode #SHELLMATTA_OK - new hunk found
  25. * #SHELLMATTA_ERROR - error parsing or end of input
  26. */
  27. static shellmatta_retCode_t findNextHunk(shellmatta_instance_t *inst)
  28. {
  29. shellmatta_retCode_t ret = SHELLMATTA_ERROR;
  30. uint32_t newOffset = inst->optionParser.nextOffset;
  31. uint32_t exeptionOffset = 0u;
  32. char quotation = '\0'; /* holds the current quotation mark if any */
  33. /** -# find beginning of next hunk */
  34. while( (newOffset < inst->inputCount)
  35. && ((' ' == inst->buffer[newOffset])
  36. || ('\0' == inst->buffer[newOffset])))
  37. {
  38. newOffset ++;
  39. }
  40. inst->optionParser.offset = newOffset;
  41. /** -# determine length */
  42. while((newOffset < inst->inputCount)
  43. && (((' ' != inst->buffer[newOffset]) && ('\0' != inst->buffer[newOffset])) || '\0' != quotation))
  44. {
  45. /** -# check for new quotation */
  46. if((('\'' == inst->buffer[newOffset]) || ('"' == inst->buffer[newOffset])) && ('\0' == quotation))
  47. {
  48. quotation = inst->buffer[newOffset];
  49. exeptionOffset ++;
  50. }
  51. /** -# check if quotation has ended */
  52. else if(quotation == inst->buffer[newOffset])
  53. {
  54. exeptionOffset ++;
  55. /** -# check if quotation is excaped */
  56. if('\\' != inst->buffer[newOffset - 1u])
  57. {
  58. quotation = '\0';
  59. }
  60. else
  61. {
  62. inst->buffer[newOffset - exeptionOffset] = inst->buffer[newOffset];
  63. }
  64. }
  65. else
  66. {
  67. /** -# shift back chars */
  68. if(0u != exeptionOffset)
  69. {
  70. inst->buffer[newOffset - exeptionOffset] = inst->buffer[newOffset];
  71. }
  72. }
  73. newOffset ++;
  74. }
  75. inst->optionParser.nextOffset = newOffset;
  76. inst->optionParser.len = newOffset - inst->optionParser.offset - exeptionOffset;
  77. /** -# add terminating 0 */
  78. inst->buffer[inst->optionParser.offset + inst->optionParser.len] = '\0';
  79. if((inst->optionParser.offset < inst->inputCount) && (0u != inst->optionParser.len) && ('\0' == quotation))
  80. {
  81. ret = SHELLMATTA_OK;
  82. }
  83. return ret;
  84. }
  85. /**
  86. * @brief peeks the first char of the next hunk
  87. * @param[in] inst shellmatta instance
  88. * @return char first char of next hunk \0 if not existing
  89. */
  90. static char peekNextHunk(shellmatta_instance_t *inst)
  91. {
  92. uint32_t newOffset = inst->optionParser.nextOffset;
  93. /** -# find beginning of next hunk */
  94. while( (newOffset < inst->inputCount)
  95. && ((' ' == inst->buffer[newOffset])
  96. || ('\0' == inst->buffer[newOffset])))
  97. {
  98. newOffset ++;
  99. }
  100. return inst->buffer[newOffset];
  101. }
  102. /**
  103. * @brief tries to parse the current input hunk and check if this is a configured option
  104. * @param[in] inst pointer to shellmatta instance
  105. * @param[in] optionString option string e.g. "cd:e::"
  106. * @param[out] option pointer to store the detected option to
  107. * @param[out] argtype pointer to var of type #shellmatta_opt_argtype_t != NULL
  108. * @return errorcode #SHELLMATTA_OK - option parsable and found in option String
  109. * #SHELLMATTA_ERROR - format error or option unknown
  110. */
  111. static shellmatta_retCode_t parseShortOpt( shellmatta_instance_t *inst,
  112. const char *optionString,
  113. char *option,
  114. shellmatta_opt_argtype_t *argtype)
  115. {
  116. shellmatta_retCode_t ret = SHELLMATTA_ERROR;
  117. const char *buffer = &inst->buffer[inst->optionParser.offset];
  118. uint32_t i;
  119. /** -# check for correct syntax */
  120. if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' != buffer[1u]) && ('\0' != buffer[1u]))
  121. {
  122. *option = '\0';
  123. /** -# search for option character in option string */
  124. for(i = 0u; ('\0' != optionString[i]) && ('\0' == *option); i ++)
  125. {
  126. if(buffer[1u] == optionString[i])
  127. {
  128. ret = SHELLMATTA_OK;
  129. /** -# return found option character */
  130. *option = buffer[1u];
  131. /** -# check if an argument is required or optional */
  132. if(':' == optionString[i + 1u])
  133. {
  134. *argtype = SHELLMATTA_OPT_ARG_REQUIRED;
  135. if(':' == optionString[i + 2u])
  136. {
  137. *argtype = SHELLMATTA_OPT_ARG_OPTIONAL;
  138. }
  139. }
  140. else
  141. {
  142. *argtype = SHELLMATTA_OPT_ARG_NONE;
  143. }
  144. }
  145. }
  146. }
  147. /** -# skip "--" */
  148. else if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' == buffer[1u]))
  149. {
  150. ret = SHELLMATTA_CONTINUE;
  151. }
  152. else
  153. {
  154. *option = '\0';
  155. }
  156. return ret;
  157. }
  158. /**
  159. * @brief tries to parse the current input hunk and check if this is a configured option
  160. * @param[in] inst pointer to shellmatta instance
  161. * @param[in] longOptions option structure - pointer to array of type #shellmatta_opt_long_t
  162. * @param[out] option pointer to store the detected option to
  163. * @param[out] argtype pointer to var of type #shellmatta_opt_argtype_t != NULL
  164. * @return errorcode #SHELLMATTA_OK - option parsable and found in option String
  165. * #SHELLMATTA_ERROR - format error or option unknown
  166. */
  167. static shellmatta_retCode_t parseLongOpt( shellmatta_instance_t *inst,
  168. const shellmatta_opt_long_t *longOptions,
  169. char *option,
  170. shellmatta_opt_argtype_t *argtype)
  171. {
  172. shellmatta_retCode_t ret = SHELLMATTA_ERROR;
  173. const char *buffer = &inst->buffer[inst->optionParser.offset];
  174. uint32_t i;
  175. /** -# check for correct syntax for short options */
  176. if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' != buffer[1u]) && ('\0' != buffer[1u]))
  177. {
  178. /** -# search for option character in option list */
  179. for(i = 0u; ('\0' != longOptions[i].paramShort) && ('\0' == *option); i ++)
  180. {
  181. if(buffer[1u] == longOptions[i].paramShort)
  182. {
  183. ret = SHELLMATTA_OK;
  184. /** -# return found option character */
  185. *option = longOptions[i].paramShort;
  186. *argtype = longOptions[i].argtype;
  187. }
  188. }
  189. }
  190. /** -# check for correct syntax for long options */
  191. else if((inst->optionParser.len >= 3u) && ('-' == buffer[0u]) && ('-' == buffer[1u]))
  192. {
  193. /** -# search for long option in option list */
  194. for(i = 0u; ('\0' != longOptions[i].paramShort) && ('\0' == *option); i ++)
  195. {
  196. if(0 == strcmp(&buffer[2u], longOptions[i].paramLong))
  197. {
  198. ret = SHELLMATTA_OK;
  199. /** -# return found option character */
  200. *option = longOptions[i].paramShort;
  201. *argtype = longOptions[i].argtype;
  202. }
  203. }
  204. }
  205. /** -# ignore "--" */
  206. else if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' == buffer[1u]))
  207. {
  208. *option = '\0';
  209. ret = SHELLMATTA_CONTINUE;
  210. }
  211. else
  212. {
  213. *option = '\0';
  214. }
  215. return ret;
  216. }
  217. /**
  218. * @brief Scans the current input and parses options in getopt style - pass either optionString or longOptions
  219. * This is an internal funtion to handle both getopt styles and remove duplicated code...
  220. * The standard functions are just wrapper around this one.
  221. * @param[in] handle shellmatta handle
  222. * @param[in] optionString option string e.g. "cd:e::"
  223. * @param[in] longOptions option structure - pointer to array of type #shellmatta_opt_long_t
  224. * @param[out] option pointer to store the detected option to
  225. * @param[out] argument pointer to store the argument string to (can be NULL)
  226. * @param[out] argLen pointer to store the argument lengh to (can be NULL)
  227. * @return errorcode #SHELLMATTA_OK - no error - keep on calling
  228. * #SHELLMATTA_ERROR - error occured - e.g. argument missing
  229. */
  230. static shellmatta_retCode_t shellmatta_opt_int( shellmatta_handle_t handle,
  231. const char *optionString,
  232. const shellmatta_opt_long_t *longOptions,
  233. char *option,
  234. char **argument,
  235. uint32_t *argLen)
  236. {
  237. shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT;
  238. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  239. shellmatta_opt_argtype_t argtype = SHELLMATTA_OPT_ARG_NONE;
  240. /** -# check parameters for plausibility */
  241. if( (NULL != inst)
  242. && (SHELLMATTA_MAGIC == inst->magic)
  243. && (NULL != option))
  244. {
  245. *option = '\0';
  246. if(NULL != argument)
  247. {
  248. *argument = NULL;
  249. }
  250. if(NULL != argLen)
  251. {
  252. *argLen = 0u;
  253. }
  254. /** -# do this until we find a not skipable argument */
  255. do
  256. {
  257. ret = findNextHunk(inst);
  258. if(SHELLMATTA_OK == ret)
  259. {
  260. /** -# call the matching parse function */
  261. if(NULL != optionString)
  262. {
  263. ret = parseShortOpt(inst, optionString, option, &argtype);
  264. }
  265. else if(NULL != longOptions)
  266. {
  267. ret = parseLongOpt(inst, longOptions, option, &argtype);
  268. }
  269. else
  270. {
  271. ret = SHELLMATTA_USE_FAULT;
  272. }
  273. /** -# when no option is found return this as raw argument */
  274. if(SHELLMATTA_ERROR == ret)
  275. {
  276. if(NULL != argument)
  277. {
  278. *argument = &(inst->buffer[inst->optionParser.offset]);
  279. }
  280. if(NULL != argLen)
  281. {
  282. *argLen = inst->optionParser.len;
  283. }
  284. ret = SHELLMATTA_OK;
  285. }
  286. else if(SHELLMATTA_USE_FAULT == ret)
  287. {
  288. /** -# nothing to do - just return errorcode */
  289. }
  290. else
  291. {
  292. switch(argtype)
  293. {
  294. case SHELLMATTA_OPT_ARG_REQUIRED:
  295. ret = findNextHunk(inst);
  296. if((NULL == argument) || (NULL == argLen))
  297. {
  298. ret = SHELLMATTA_USE_FAULT;
  299. }
  300. if(SHELLMATTA_OK == ret)
  301. {
  302. *argument = &(inst->buffer[inst->optionParser.offset]);
  303. *argLen = inst->optionParser.len;
  304. }
  305. break;
  306. case SHELLMATTA_OPT_ARG_OPTIONAL:
  307. /** -# treat anything not starting with '-' as argument */
  308. if('-' != peekNextHunk(inst))
  309. {
  310. ret = findNextHunk(inst);
  311. if((NULL == argument) || (NULL == argLen))
  312. {
  313. ret = SHELLMATTA_USE_FAULT;
  314. }
  315. if(SHELLMATTA_OK == ret)
  316. {
  317. *argument = &(inst->buffer[inst->optionParser.offset]);
  318. *argLen = inst->optionParser.len;
  319. }
  320. }
  321. break;
  322. default:
  323. /* nothing to do */
  324. break;
  325. }
  326. }
  327. }
  328. } while(SHELLMATTA_CONTINUE == ret);
  329. }
  330. return ret;
  331. }
  332. /**
  333. * @brief scans the current input and parses options in getopt style
  334. * @param[in] handle shellmatta handle
  335. * @param[in] optionString option string e.g. "cd:e::"
  336. * @param[out] option pointer to store the detected option to
  337. * @param[out] argument pointer to store the argument string to (can be NULL)
  338. * @param[out] argLen pointer to store the argument lengh to (can be NULL)
  339. * @return errorcode #SHELLMATTA_OK - no error - keep on calling
  340. * #SHELLMATTA_ERROR - error occured - e.g. argument missing
  341. */
  342. shellmatta_retCode_t shellmatta_opt( shellmatta_handle_t handle,
  343. const char *optionString,
  344. char *option,
  345. char **argument,
  346. uint32_t *argLen)
  347. {
  348. return shellmatta_opt_int( handle,
  349. optionString,
  350. NULL,
  351. option,
  352. argument,
  353. argLen);
  354. }
  355. /**
  356. * @brief scans the current input and parses options in getopt_long style
  357. * @param[in] handle shellmatta handle
  358. * @param[in] longOptions option structure - pointer to array of type #shellmatta_opt_long_t
  359. * @param[out] option pointer to store the detected option to
  360. * @param[out] argument pointer to store the argument string to (can be NULL)
  361. * @param[out] argLen pointer to store the argument lengh to (can be NULL)
  362. */
  363. shellmatta_retCode_t shellmatta_opt_long( shellmatta_handle_t handle,
  364. const shellmatta_opt_long_t *longOptions,
  365. char *option,
  366. char **argument,
  367. uint32_t *argLen)
  368. {
  369. return shellmatta_opt_int( handle,
  370. NULL,
  371. longOptions,
  372. option,
  373. argument,
  374. argLen);
  375. }
  376. /**
  377. * @brief initializes the option parser instance
  378. * @param[in, out] inst pointer to a shellmatta instance
  379. * @param[in] argStart start offset of the arguments (after command name/alias)
  380. */
  381. void shellmatta_opt_init(shellmatta_instance_t *inst, uint32_t argStart)
  382. {
  383. /** -# initialize all relevant option parser variables */
  384. inst->optionParser.argStart = argStart;
  385. inst->optionParser.nextOffset = argStart;
  386. }
  387. /**
  388. * @brief re-initializes the option parser instance using the data from the last init
  389. * @param[in, out] inst pointer to a shellmatta instance
  390. */
  391. void shellmatta_opt_reInit(shellmatta_instance_t *inst)
  392. {
  393. /** -# initialize all relevant option parser variables */
  394. inst->optionParser.nextOffset = inst->optionParser.argStart;
  395. }
  396. /**
  397. * @}
  398. */