load.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. /** @file load.c
  2. * @brief Common part for XML parsing.
  3. *
  4. * Different XML parsing engines can provide a functions in a form of
  5. * standardized API. This code uses this API and parses XML to kscheme.
  6. */
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <assert.h>
  10. #include <errno.h>
  11. #include <sys/types.h>
  12. #include <dirent.h>
  13. #include <faux/faux.h>
  14. #include <faux/str.h>
  15. #include <faux/error.h>
  16. #include <klish/kscheme.h>
  17. #include <klish/ischeme.h>
  18. #include <klish/kxml.h>
  19. #define TAG "XML"
  20. typedef bool_t (kxml_process_fn)(const kxml_node_t *element,
  21. void *parent, faux_error_t *error);
  22. static kxml_process_fn
  23. process_action,
  24. process_param,
  25. process_command,
  26. process_view,
  27. process_ptype,
  28. process_plugin,
  29. process_klish,
  30. process_entry,
  31. process_hotkey;
  32. // Different TAGs types
  33. typedef enum {
  34. KTAG_NONE,
  35. KTAG_ACTION,
  36. KTAG_PARAM,
  37. KTAG_SWITCH, // PARAM alias
  38. KTAG_SEQ, // PARAM alias
  39. KTAG_COMMAND,
  40. KTAG_FILTER,
  41. KTAG_VIEW,
  42. KTAG_PTYPE,
  43. KTAG_PLUGIN,
  44. KTAG_KLISH,
  45. KTAG_ENTRY,
  46. KTAG_COND,
  47. KTAG_COMPL,
  48. KTAG_HELP,
  49. KTAG_PROMPT,
  50. KTAG_HOTKEY,
  51. KTAG_MAX,
  52. } ktags_e;
  53. static const char * const kxml_tags[] = {
  54. NULL,
  55. "ACTION",
  56. "PARAM",
  57. "SWITCH",
  58. "SEQ",
  59. "COMMAND",
  60. "FILTER",
  61. "VIEW",
  62. "PTYPE",
  63. "PLUGIN",
  64. "KLISH",
  65. "ENTRY",
  66. "COND",
  67. "COMPL",
  68. "HELP",
  69. "PROMPT",
  70. "HOTKEY",
  71. };
  72. static kxml_process_fn *kxml_handlers[] = {
  73. NULL,
  74. process_action,
  75. process_param,
  76. process_param,
  77. process_param,
  78. process_command,
  79. process_command,
  80. process_view,
  81. process_ptype,
  82. process_plugin,
  83. process_klish,
  84. process_entry,
  85. process_command,
  86. process_command,
  87. process_command,
  88. process_command,
  89. process_hotkey,
  90. };
  91. static const char *kxml_tag_name(ktags_e tag)
  92. {
  93. if ((KTAG_NONE == tag) || (tag >= KTAG_MAX))
  94. return "NONE";
  95. return kxml_tags[tag];
  96. }
  97. static ktags_e kxml_node_tag(const kxml_node_t *node)
  98. {
  99. ktags_e tag = KTAG_NONE;
  100. char *name = NULL;
  101. if (!node)
  102. return KTAG_NONE;
  103. if (kxml_node_type(node) != KXML_NODE_ELM)
  104. return KTAG_NONE;
  105. name = kxml_node_name(node);
  106. if (!name)
  107. return KTAG_NONE; // Strange case
  108. for (tag = (KTAG_NONE + 1); tag < KTAG_MAX; tag++) {
  109. if (faux_str_casecmp(name, kxml_tags[tag]) == 0)
  110. break;
  111. }
  112. kxml_node_name_free(name);
  113. if (tag >= KTAG_MAX)
  114. return KTAG_NONE;
  115. return tag;
  116. }
  117. static kxml_process_fn *kxml_node_handler(const kxml_node_t *node)
  118. {
  119. return kxml_handlers[kxml_node_tag(node)];
  120. }
  121. /** @brief Reads an element from the XML stream and processes it.
  122. */
  123. static bool_t process_node(const kxml_node_t *node, void *parent, faux_error_t *error)
  124. {
  125. kxml_process_fn *handler = NULL;
  126. // Process only KXML_NODE_ELM. Don't process other types like:
  127. // KXML_NODE_DOC,
  128. // KXML_NODE_TEXT,
  129. // KXML_NODE_ATTR,
  130. // KXML_NODE_COMMENT,
  131. // KXML_NODE_PI,
  132. // KXML_NODE_DECL,
  133. // KXML_NODE_UNKNOWN,
  134. if (kxml_node_type(node) != KXML_NODE_ELM)
  135. return BOOL_TRUE;
  136. handler = kxml_node_handler(node);
  137. if (!handler) { // Unknown element
  138. faux_error_sprintf(error,
  139. TAG": Unknown tag \"%s\"", kxml_node_name(node));
  140. return BOOL_FALSE;
  141. }
  142. #ifdef KXML_DEBUG
  143. printf("kxml: Tag \"%s\"\n", kxml_node_name(node));
  144. #endif
  145. return handler(node, parent, error);
  146. }
  147. static bool_t kxml_load_file(kscheme_t *scheme, const char *filename,
  148. faux_error_t *error)
  149. {
  150. kxml_doc_t *doc = NULL;
  151. kxml_node_t *root = NULL;
  152. bool_t r = BOOL_FALSE;
  153. if (!scheme)
  154. return BOOL_FALSE;
  155. if (!filename)
  156. return BOOL_FALSE;
  157. #ifdef KXML_DEBUG
  158. printf("kxml: Processing XML file \"%s\"\n", filename);
  159. #endif
  160. doc = kxml_doc_read(filename);
  161. if (!kxml_doc_is_valid(doc)) {
  162. /* int errcaps = kxml_doc_error_caps(doc);
  163. printf("Unable to open file '%s'", filename);
  164. if ((errcaps & kxml_ERR_LINE) == kxml_ERR_LINE)
  165. printf(", at line %d", kxml_doc_err_line(doc));
  166. if ((errcaps & kxml_ERR_COL) == kxml_ERR_COL)
  167. printf(", at column %d", kxml_doc_err_col(doc));
  168. if ((errcaps & kxml_ERR_DESC) == kxml_ERR_DESC)
  169. printf(", message is %s", kxml_doc_err_msg(doc));
  170. printf("\n");
  171. */ kxml_doc_release(doc);
  172. return BOOL_FALSE;
  173. }
  174. root = kxml_doc_root(doc);
  175. r = process_node(root, scheme, error);
  176. kxml_doc_release(doc);
  177. if (!r) {
  178. faux_error_sprintf(error, TAG": Illegal file %s", filename);
  179. return BOOL_FALSE;
  180. }
  181. return BOOL_TRUE;
  182. }
  183. /** @brief Default path to get XML files from.
  184. */
  185. static const char *default_path = "/etc/klish;~/.klish";
  186. static const char *path_separators = ":;";
  187. bool_t kxml_load_scheme(kscheme_t *scheme, const char *xml_path,
  188. faux_error_t *error)
  189. {
  190. char *path = NULL;
  191. char *fn = NULL;
  192. char *saveptr = NULL;
  193. bool_t ret = BOOL_TRUE;
  194. assert(scheme);
  195. if (!scheme)
  196. return BOOL_FALSE;
  197. // Use the default path if xml path is not specified.
  198. // Dup is needed because sring will be tokenized but
  199. // the xml_path is must be const.
  200. if (!xml_path)
  201. path = faux_str_dup(default_path);
  202. else
  203. path = faux_str_dup(xml_path);
  204. #ifdef KXML_DEBUG
  205. printf("kxml: Loading scheme \"%s\"\n", path);
  206. #endif
  207. // Loop through each directory
  208. for (fn = strtok_r(path, path_separators, &saveptr);
  209. fn; fn = strtok_r(NULL, path_separators, &saveptr)) {
  210. DIR *dir = NULL;
  211. struct dirent *entry = NULL;
  212. char *realpath = NULL;
  213. // Expand tilde. Tilde must be the first symbol.
  214. realpath = faux_expand_tilde(fn);
  215. // Regular file
  216. if (faux_isfile(realpath)) {
  217. if (!kxml_load_file(scheme, realpath, error))
  218. ret = BOOL_FALSE;
  219. faux_str_free(realpath);
  220. continue;
  221. }
  222. // Search this directory for any XML files
  223. #ifdef KXML_DEBUG
  224. printf("kxml: Processing XML dir \"%s\"\n", realpath);
  225. #endif
  226. dir = opendir(realpath);
  227. if (!dir) {
  228. faux_str_free(realpath);
  229. continue;
  230. }
  231. for (entry = readdir(dir); entry; entry = readdir(dir)) {
  232. const char *extension = strrchr(entry->d_name, '.');
  233. char *filename = NULL;
  234. // Check the filename
  235. if (!extension || strcmp(".xml", extension))
  236. continue;
  237. filename = faux_str_sprintf("%s/%s", realpath, entry->d_name);
  238. if (!kxml_load_file(scheme, filename, error))
  239. ret = BOOL_FALSE;
  240. faux_str_free(filename);
  241. }
  242. closedir(dir);
  243. faux_str_free(realpath);
  244. }
  245. faux_str_free(path);
  246. return ret;
  247. }
  248. /** @brief Iterate through element's children.
  249. */
  250. static bool_t process_children(const kxml_node_t *element, void *parent,
  251. faux_error_t *error)
  252. {
  253. const kxml_node_t *node = NULL;
  254. while ((node = kxml_node_next_child(element, node)) != NULL) {
  255. bool_t res = BOOL_FALSE;
  256. res = process_node(node, parent, error);
  257. if (!res)
  258. return res;
  259. }
  260. return BOOL_TRUE;
  261. }
  262. static bool_t process_klish(const kxml_node_t *element, void *parent,
  263. faux_error_t *error)
  264. {
  265. return process_children(element, parent, error);
  266. }
  267. static bool_t process_plugin(const kxml_node_t *element, void *parent,
  268. faux_error_t *error)
  269. {
  270. iplugin_t iplugin = {};
  271. kplugin_t *plugin = NULL;
  272. bool_t res = BOOL_FALSE;
  273. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  274. if (parent_tag != KTAG_KLISH) {
  275. faux_error_sprintf(error,
  276. TAG": Tag \"%s\" can't contain PLUGIN tag",
  277. kxml_tag_name(parent_tag));
  278. return BOOL_FALSE;
  279. }
  280. iplugin.name = kxml_node_attr(element, "name");
  281. iplugin.id = kxml_node_attr(element, "id");
  282. iplugin.file = kxml_node_attr(element, "file");
  283. iplugin.conf = kxml_node_content(element);
  284. plugin = iplugin_load(&iplugin, error);
  285. if (!plugin)
  286. goto err;
  287. if (!kscheme_add_plugins((kscheme_t *)parent, plugin)) {
  288. faux_error_sprintf(error, TAG": Can't add PLUGIN \"%s\". "
  289. "Probably duplication",
  290. kplugin_name(plugin));
  291. kplugin_free(plugin);
  292. goto err;
  293. }
  294. if (!process_children(element, plugin, error))
  295. goto err;
  296. res = BOOL_TRUE;
  297. err:
  298. kxml_node_attr_free(iplugin.name);
  299. kxml_node_attr_free(iplugin.id);
  300. kxml_node_attr_free(iplugin.file);
  301. kxml_node_content_free(iplugin.conf);
  302. return res;
  303. }
  304. static bool_t process_action(const kxml_node_t *element, void *parent,
  305. faux_error_t *error)
  306. {
  307. iaction_t iaction = {};
  308. kaction_t *action = NULL;
  309. bool_t res = BOOL_FALSE;
  310. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  311. kentry_t *parent_entry = (kentry_t *)parent;
  312. iaction.sym = kxml_node_attr(element, "sym");
  313. iaction.lock = kxml_node_attr(element, "lock");
  314. iaction.interrupt = kxml_node_attr(element, "interrupt");
  315. iaction.in = kxml_node_attr(element, "in");
  316. iaction.out = kxml_node_attr(element, "out");
  317. iaction.exec_on = kxml_node_attr(element, "exec_on");
  318. iaction.update_retcode = kxml_node_attr(element, "update_retcode");
  319. iaction.permanent = kxml_node_attr(element, "permanent");
  320. iaction.sync = kxml_node_attr(element, "sync");
  321. iaction.script = kxml_node_content(element);
  322. action = iaction_load(&iaction, error);
  323. if (!action)
  324. goto err;
  325. if ( (KTAG_ENTRY != parent_tag) &&
  326. (KTAG_COMMAND != parent_tag) &&
  327. (KTAG_FILTER != parent_tag) &&
  328. (KTAG_PARAM != parent_tag) &&
  329. (KTAG_COND != parent_tag) &&
  330. (KTAG_COMPL != parent_tag) &&
  331. (KTAG_HELP != parent_tag) &&
  332. (KTAG_PROMPT != parent_tag) &&
  333. (KTAG_PTYPE != parent_tag)) {
  334. faux_error_sprintf(error,
  335. TAG": Tag \"%s\" can't contain ACTION tag",
  336. kxml_tag_name(parent_tag));
  337. kaction_free(action);
  338. goto err;
  339. }
  340. if (!kentry_add_actions(parent_entry, action)) {
  341. faux_error_sprintf(error,
  342. TAG": Can't add ACTION #%d to ENTRY \"%s\". "
  343. "Probably duplication",
  344. kentry_actions_len(parent_entry) + 1,
  345. kentry_name(parent_entry));
  346. kaction_free(action);
  347. goto err;
  348. }
  349. if (!process_children(element, action, error))
  350. goto err;
  351. res = BOOL_TRUE;
  352. err:
  353. kxml_node_attr_free(iaction.sym);
  354. kxml_node_attr_free(iaction.lock);
  355. kxml_node_attr_free(iaction.interrupt);
  356. kxml_node_attr_free(iaction.in);
  357. kxml_node_attr_free(iaction.out);
  358. kxml_node_attr_free(iaction.exec_on);
  359. kxml_node_attr_free(iaction.update_retcode);
  360. kxml_node_attr_free(iaction.permanent);
  361. kxml_node_attr_free(iaction.sync);
  362. kxml_node_content_free(iaction.script);
  363. return res;
  364. }
  365. static kentry_t *add_entry_to_hierarchy(const kxml_node_t *element, void *parent,
  366. ientry_t *ientry, faux_error_t *error)
  367. {
  368. kentry_t *entry = NULL;
  369. ktags_e tag = kxml_node_tag(element);
  370. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  371. assert(ientry);
  372. // Parent is mandatory field
  373. if (!parent) {
  374. faux_error_sprintf(error,
  375. TAG": Broken parent object for entry \"%s\"",
  376. ientry->name);
  377. return NULL;
  378. }
  379. if ((parent_tag == KTAG_ACTION) ||
  380. // (parent_tag == KTAG_HOTKEY) ||
  381. (parent_tag == KTAG_PLUGIN)) {
  382. faux_error_sprintf(error,
  383. TAG": Tag \"%s\" can't contain %s tag \"%s\"",
  384. kxml_tag_name(parent_tag),
  385. kxml_tag_name(tag), ientry->name);
  386. return NULL;
  387. }
  388. // High level ENTRY
  389. if (KTAG_KLISH == parent_tag) {
  390. kscheme_t *scheme = (kscheme_t *)parent;
  391. // Does such ENTRY already exist
  392. entry = kscheme_find_entry(scheme, ientry->name);
  393. if (entry) {
  394. if (!ientry_parse(ientry, entry, error))
  395. return NULL;
  396. } else { // New entry object
  397. entry = ientry_load(ientry, error);
  398. if (!entry)
  399. return NULL;
  400. if (!kscheme_add_entrys(scheme, entry)) {
  401. faux_error_sprintf(error, TAG": Can't add entry \"%s\". "
  402. "Probably duplication",
  403. kentry_name(entry));
  404. kentry_free(entry);
  405. return NULL;
  406. }
  407. }
  408. // ENTRY within ENTRY
  409. } else {
  410. kentry_t *parent_entry = (kentry_t *)parent;
  411. // Does such ENTRY already exist
  412. entry = kentry_find_entry(parent_entry, ientry->name);
  413. if (entry) {
  414. if (!ientry_parse(ientry, entry, error))
  415. return NULL;
  416. } else { // New entry object
  417. entry = ientry_load(ientry, error);
  418. if (!entry)
  419. return NULL;
  420. kentry_set_parent(entry, parent_entry);
  421. if (!kentry_add_entrys(parent_entry, entry)) {
  422. faux_error_sprintf(error, TAG": Can't add entry \"%s\". "
  423. "Probably duplication",
  424. kentry_name(entry));
  425. kentry_free(entry);
  426. return NULL;
  427. }
  428. }
  429. }
  430. return entry;
  431. }
  432. static bool_t process_entry(const kxml_node_t *element, void *parent,
  433. faux_error_t *error)
  434. {
  435. ientry_t ientry = {};
  436. kentry_t *entry = NULL;
  437. bool_t res = BOOL_FALSE;
  438. // Mandatory entry name
  439. ientry.name = kxml_node_attr(element, "name");
  440. if (!ientry.name) {
  441. faux_error_sprintf(error, TAG": entry without name");
  442. return BOOL_FALSE;
  443. }
  444. ientry.help = kxml_node_attr(element, "help");
  445. ientry.container = kxml_node_attr(element, "container");
  446. ientry.mode = kxml_node_attr(element, "mode");
  447. ientry.purpose = kxml_node_attr(element, "purpose");
  448. ientry.min = kxml_node_attr(element, "min");
  449. ientry.max = kxml_node_attr(element, "max");
  450. ientry.ref = kxml_node_attr(element, "ref");
  451. ientry.value = kxml_node_attr(element, "value");
  452. ientry.restore = kxml_node_attr(element, "restore");
  453. ientry.order = kxml_node_attr(element, "order");
  454. ientry.filter = kxml_node_attr(element, "filter");
  455. if (!(entry = add_entry_to_hierarchy(element, parent, &ientry, error)))
  456. goto err;
  457. if (!process_children(element, entry, error))
  458. goto err;
  459. res = BOOL_TRUE;
  460. err:
  461. kxml_node_attr_free(ientry.name);
  462. kxml_node_attr_free(ientry.help);
  463. kxml_node_attr_free(ientry.container);
  464. kxml_node_attr_free(ientry.mode);
  465. kxml_node_attr_free(ientry.purpose);
  466. kxml_node_attr_free(ientry.min);
  467. kxml_node_attr_free(ientry.max);
  468. kxml_node_attr_free(ientry.ref);
  469. kxml_node_attr_free(ientry.value);
  470. kxml_node_attr_free(ientry.restore);
  471. kxml_node_attr_free(ientry.order);
  472. kxml_node_attr_free(ientry.filter);
  473. return res;
  474. }
  475. static bool_t process_view(const kxml_node_t *element, void *parent,
  476. faux_error_t *error)
  477. {
  478. ientry_t ientry = {};
  479. kentry_t *entry = NULL;
  480. bool_t res = BOOL_FALSE;
  481. // Mandatory VIEW name
  482. ientry.name = kxml_node_attr(element, "name");
  483. if (!ientry.name) {
  484. faux_error_sprintf(error, TAG": VIEW without name");
  485. return BOOL_FALSE;
  486. }
  487. ientry.help = kxml_node_attr(element, "help");
  488. ientry.container = "true";
  489. ientry.mode = "switch";
  490. ientry.purpose = "common";
  491. ientry.min = "1";
  492. ientry.max = "1";
  493. ientry.ref = kxml_node_attr(element, "ref");
  494. ientry.value = NULL;
  495. ientry.restore = "false";
  496. ientry.order = "false";
  497. ientry.filter = "false";
  498. if (!(entry = add_entry_to_hierarchy(element, parent, &ientry, error)))
  499. goto err;
  500. if (!process_children(element, entry, error))
  501. goto err;
  502. res = BOOL_TRUE;
  503. err:
  504. kxml_node_attr_free(ientry.name);
  505. kxml_node_attr_free(ientry.help);
  506. kxml_node_attr_free(ientry.ref);
  507. return res;
  508. }
  509. static bool_t process_ptype(const kxml_node_t *element, void *parent,
  510. faux_error_t *error)
  511. {
  512. ientry_t ientry = {};
  513. kentry_t *entry = NULL;
  514. bool_t res = BOOL_FALSE;
  515. bool_t is_name = BOOL_FALSE;
  516. // Mandatory PTYPE name or reference
  517. ientry.name = kxml_node_attr(element, "name");
  518. ientry.ref = kxml_node_attr(element, "ref");
  519. if (ientry.name) {
  520. is_name = BOOL_TRUE;
  521. } else {
  522. if (!ientry.ref) {
  523. faux_error_sprintf(error,
  524. TAG": PTYPE without name or reference");
  525. return BOOL_FALSE;
  526. }
  527. ientry.name = "__ptype";
  528. }
  529. ientry.help = kxml_node_attr(element, "help");
  530. ientry.container = "true";
  531. ientry.mode = "sequence";
  532. ientry.purpose = "ptype";
  533. ientry.min = "1";
  534. ientry.max = "1";
  535. ientry.value = kxml_node_attr(element, "value");
  536. ientry.restore = "false";
  537. ientry.order = "true";
  538. ientry.filter = "false";
  539. if (!(entry = add_entry_to_hierarchy(element, parent, &ientry, error)))
  540. goto err;
  541. if (!process_children(element, entry, error))
  542. goto err;
  543. res = BOOL_TRUE;
  544. err:
  545. if (is_name)
  546. kxml_node_attr_free(ientry.name);
  547. kxml_node_attr_free(ientry.help);
  548. kxml_node_attr_free(ientry.ref);
  549. kxml_node_attr_free(ientry.value);
  550. return res;
  551. }
  552. static kentry_t *create_ptype(const char *ptype,
  553. const char *compl, const char *help, const char *ref)
  554. {
  555. kentry_t *ptype_entry = NULL;
  556. if (!ptype && !ref)
  557. return NULL;
  558. ptype_entry = kentry_new("__ptype");
  559. assert(ptype_entry);
  560. kentry_set_purpose(ptype_entry, KENTRY_PURPOSE_PTYPE);
  561. if (ref) {
  562. kentry_set_ref_str(ptype_entry, ref);
  563. // If ptype is reference then another actions is not needed.
  564. return ptype_entry;
  565. } else {
  566. kaction_t *ptype_action = kaction_new();
  567. assert(ptype_action);
  568. kaction_set_sym_ref(ptype_action, ptype);
  569. kaction_set_permanent(ptype_action, TRI_TRUE);
  570. kentry_add_actions(ptype_entry, ptype_action);
  571. }
  572. if (compl) {
  573. kentry_t *compl_entry = NULL;
  574. kaction_t *compl_action = NULL;
  575. compl_action = kaction_new();
  576. assert(compl_action);
  577. kaction_set_sym_ref(compl_action, compl);
  578. kaction_set_permanent(compl_action, TRI_TRUE);
  579. compl_entry = kentry_new("__compl");
  580. assert(compl_entry);
  581. kentry_set_purpose(compl_entry, KENTRY_PURPOSE_COMPLETION);
  582. kentry_add_actions(compl_entry, compl_action);
  583. kentry_add_entrys(ptype_entry, compl_entry);
  584. }
  585. if (help) {
  586. kentry_t *help_entry = NULL;
  587. kaction_t *help_action = NULL;
  588. help_action = kaction_new();
  589. assert(help_action);
  590. kaction_set_sym_ref(help_action, help);
  591. kaction_set_permanent(help_action, TRI_TRUE);
  592. help_entry = kentry_new("__help");
  593. assert(help_entry);
  594. kentry_set_purpose(help_entry, KENTRY_PURPOSE_HELP);
  595. kentry_add_actions(help_entry, help_action);
  596. kentry_add_entrys(ptype_entry, help_entry);
  597. }
  598. return ptype_entry;
  599. }
  600. // PARAM, SWITCH, SEQ
  601. static bool_t process_param(const kxml_node_t *element, void *parent,
  602. faux_error_t *error)
  603. {
  604. ientry_t ientry = {};
  605. kentry_t *entry = NULL;
  606. bool_t res = BOOL_FALSE;
  607. ktags_e tag = kxml_node_tag(element);
  608. bool_t is_mode = BOOL_FALSE;
  609. char *ptype_str = NULL;
  610. // Mandatory PARAM name
  611. ientry.name = kxml_node_attr(element, "name");
  612. if (!ientry.name) {
  613. faux_error_sprintf(error, TAG": PARAM without name");
  614. return BOOL_FALSE;
  615. }
  616. ientry.help = kxml_node_attr(element, "help");
  617. // Container
  618. if (KTAG_PARAM == tag)
  619. ientry.container = "false";
  620. else
  621. ientry.container = "true"; // SWITCH, SEQ
  622. // Mode
  623. switch (tag) {
  624. case KTAG_PARAM:
  625. ientry.mode = kxml_node_attr(element, "mode");
  626. is_mode = BOOL_TRUE;
  627. break;
  628. case KTAG_SWITCH:
  629. ientry.mode = "switch";
  630. break;
  631. case KTAG_SEQ:
  632. ientry.mode = "sequence";
  633. break;
  634. default:
  635. ientry.mode = "empty";
  636. break;
  637. }
  638. ientry.purpose = "common";
  639. ientry.min = kxml_node_attr(element, "min");
  640. ientry.max = kxml_node_attr(element, "max");
  641. ientry.ref = kxml_node_attr(element, "ref");
  642. ientry.value = kxml_node_attr(element, "value");
  643. ientry.restore = "false";
  644. ientry.order = kxml_node_attr(element, "order");
  645. ientry.filter = "false";
  646. if (!(entry = add_entry_to_hierarchy(element, parent, &ientry, error)))
  647. goto err;
  648. // Special attribute "ptype". It exists for more simple XML only. It
  649. // just links existing PTYPE. User can to don't specify nested tag PTYPE.
  650. ptype_str = kxml_node_attr(element, "ptype");
  651. if (ptype_str) {
  652. kentry_t *ptype_entry = create_ptype(NULL, NULL, NULL, ptype_str);
  653. assert(ptype_entry);
  654. kentry_add_entrys(entry, ptype_entry);
  655. }
  656. if (!process_children(element, entry, error))
  657. goto err;
  658. res = BOOL_TRUE;
  659. err:
  660. kxml_node_attr_free(ientry.name);
  661. kxml_node_attr_free(ientry.help);
  662. if (is_mode)
  663. kxml_node_attr_free(ientry.mode);
  664. kxml_node_attr_free(ientry.min);
  665. kxml_node_attr_free(ientry.max);
  666. kxml_node_attr_free(ientry.ref);
  667. kxml_node_attr_free(ientry.value);
  668. kxml_node_attr_free(ientry.order);
  669. kxml_node_attr_free(ptype_str);
  670. return res;
  671. }
  672. // COMMAND, FILTER, COND, COMPL, HELP, PROMPT
  673. static bool_t process_command(const kxml_node_t *element, void *parent,
  674. faux_error_t *error)
  675. {
  676. ientry_t ientry = {};
  677. kentry_t *entry = NULL;
  678. bool_t res = BOOL_FALSE;
  679. ktags_e tag = kxml_node_tag(element);
  680. bool_t is_name = BOOL_FALSE;
  681. bool_t is_filter = BOOL_FALSE;
  682. kentry_entrys_node_t *iter = NULL;
  683. kentry_t *nested_entry = NULL;
  684. bool_t ptype_exists = BOOL_FALSE;
  685. // Mandatory COMMAND name
  686. ientry.name = kxml_node_attr(element, "name");
  687. if (ientry.name) {
  688. is_name = BOOL_TRUE;
  689. } else {
  690. switch (tag) {
  691. case KTAG_COMMAND:
  692. case KTAG_FILTER:
  693. faux_error_sprintf(error, TAG": COMMAND without name");
  694. return BOOL_FALSE;
  695. case KTAG_COND:
  696. ientry.name = "__cond";
  697. break;
  698. case KTAG_COMPL:
  699. ientry.name = "__compl";
  700. break;
  701. case KTAG_HELP:
  702. ientry.name = "__help";
  703. break;
  704. case KTAG_PROMPT:
  705. ientry.name = "__prompt";
  706. break;
  707. default:
  708. faux_error_sprintf(error, TAG": Unknown tag");
  709. return BOOL_FALSE;
  710. }
  711. }
  712. ientry.help = kxml_node_attr(element, "help");
  713. ientry.container = "false";
  714. ientry.mode = kxml_node_attr(element, "mode");
  715. // Purpose
  716. switch (tag) {
  717. case KTAG_COND:
  718. ientry.purpose = "cond";
  719. break;
  720. case KTAG_COMPL:
  721. ientry.purpose = "completion";
  722. break;
  723. case KTAG_HELP:
  724. ientry.purpose = "help";
  725. break;
  726. case KTAG_PROMPT:
  727. ientry.purpose = "prompt";
  728. break;
  729. default:
  730. ientry.purpose = "common";
  731. break;
  732. }
  733. ientry.min = kxml_node_attr(element, "min");
  734. ientry.max = kxml_node_attr(element, "max");
  735. ientry.ref = kxml_node_attr(element, "ref");
  736. if ((KTAG_FILTER == tag) || (KTAG_COMMAND == tag)) {
  737. ientry.value = kxml_node_attr(element, "value");
  738. ientry.restore = kxml_node_attr(element, "restore");
  739. } else {
  740. ientry.value = NULL;
  741. ientry.restore = "false";
  742. }
  743. ientry.order = "false";
  744. // Filter
  745. ientry.filter = kxml_node_attr(element, "filter");
  746. if (ientry.filter) {
  747. is_filter = BOOL_TRUE;
  748. } else {
  749. if (KTAG_FILTER == tag)
  750. ientry.filter = "true";
  751. else
  752. ientry.filter = "false";
  753. }
  754. if (!(entry = add_entry_to_hierarchy(element, parent, &ientry, error)))
  755. goto err;
  756. if (!process_children(element, entry, error))
  757. goto err;
  758. // Add special PTYPE for command. It uses symbol from internal klish
  759. // plugin.
  760. // Iterate child entries to find out is there PTYPE entry already. We
  761. // can't use kentry_nested_by_purpose() because it's not calculated yet.
  762. iter = kentry_entrys_iter(entry);
  763. while ((nested_entry = kentry_entrys_each(&iter))) {
  764. if (kentry_purpose(nested_entry) == KENTRY_PURPOSE_PTYPE) {
  765. ptype_exists = BOOL_TRUE;
  766. break;
  767. }
  768. }
  769. if (!ptype_exists) {
  770. kentry_t *ptype_entry = create_ptype(
  771. "COMMAND@klish",
  772. "completion_COMMAND@klish",
  773. "help_COMMAND@klish",
  774. NULL);
  775. assert(ptype_entry);
  776. kentry_add_entrys(entry, ptype_entry);
  777. }
  778. res = BOOL_TRUE;
  779. err:
  780. if (is_name)
  781. kxml_node_attr_free(ientry.name);
  782. kxml_node_attr_free(ientry.help);
  783. kxml_node_attr_free(ientry.mode);
  784. kxml_node_attr_free(ientry.min);
  785. kxml_node_attr_free(ientry.max);
  786. kxml_node_attr_free(ientry.ref);
  787. if ((KTAG_FILTER == tag) || (KTAG_COMMAND == tag)) {
  788. kxml_node_attr_free(ientry.value);
  789. kxml_node_attr_free(ientry.restore);
  790. }
  791. if (is_filter)
  792. kxml_node_attr_free(ientry.filter);
  793. return res;
  794. }
  795. static bool_t process_hotkey(const kxml_node_t *element, void *parent,
  796. faux_error_t *error)
  797. {
  798. ihotkey_t ihotkey = {};
  799. khotkey_t *hotkey = NULL;
  800. bool_t res = BOOL_FALSE;
  801. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  802. kentry_t *parent_entry = (kentry_t *)parent;
  803. ihotkey.key = kxml_node_attr(element, "key");
  804. if (!ihotkey.key) {
  805. faux_error_sprintf(error, TAG": hotkey without \"key\" attribute");
  806. return BOOL_FALSE;
  807. }
  808. ihotkey.cmd = kxml_node_attr(element, "cmd");
  809. if (!ihotkey.cmd) {
  810. faux_error_sprintf(error, TAG": hotkey without \"cmd\" attribute");
  811. return BOOL_FALSE;
  812. }
  813. hotkey = ihotkey_load(&ihotkey, error);
  814. if (!hotkey)
  815. goto err;
  816. if ( (KTAG_ENTRY != parent_tag) &&
  817. (KTAG_VIEW != parent_tag)) {
  818. faux_error_sprintf(error,
  819. TAG": Tag \"%s\" can't contain HOTKEY tag",
  820. kxml_tag_name(parent_tag));
  821. khotkey_free(hotkey);
  822. goto err;
  823. }
  824. if (!kentry_add_hotkeys(parent_entry, hotkey)) {
  825. faux_error_sprintf(error,
  826. TAG": Can't add HOTKEY \"%s\" to ENTRY \"%s\". "
  827. "Probably duplication",
  828. khotkey_key(hotkey),
  829. kentry_name(parent_entry));
  830. khotkey_free(hotkey);
  831. goto err;
  832. }
  833. // HOTKEY doesn't have children
  834. res = BOOL_TRUE;
  835. err:
  836. kxml_node_attr_free(ihotkey.key);
  837. kxml_node_attr_free(ihotkey.cmd);
  838. return res;
  839. }