introspection.c 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234
  1. /*
  2. * Copyright © 2025 Pierre Le Marre <dev@wismill.eu>
  3. * SPDX-License-Identifier: MIT
  4. */
  5. #include "config.h"
  6. #include <assert.h>
  7. #include <errno.h>
  8. #include <getopt.h>
  9. #include <limits.h>
  10. #include <locale.h>
  11. #include <stdio.h>
  12. #include <stdbool.h>
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include "xkbcommon/xkbcommon.h"
  16. #include "tools-common.h"
  17. #include "src/darray.h"
  18. #include "src/utils.h"
  19. #include "src/utils-paths.h"
  20. #include "src/keymap-formats.h"
  21. #include "src/xkbcomp/ast.h"
  22. #include "src/xkbcomp/keymap-file-iterator.h"
  23. /*
  24. * The output is meant to be valid YAML; however we do not enforce it
  25. * because we expect the file and section names to be valid text values.
  26. */
  27. enum input_source {
  28. INPUT_SOURCE_AUTO = 0,
  29. INPUT_SOURCE_STDIN,
  30. INPUT_SOURCE_PATH
  31. };
  32. enum output_format {
  33. OUTPUT_FORMAT_YAML = 0,
  34. OUTPUT_FORMAT_DOT,
  35. OUTPUT_FORMAT_RDF_TURTLE,
  36. OUTPUT_FORMAT_RESOLVED_PATH,
  37. };
  38. enum output_options {
  39. OUTPUT_NO_OPTIONS = 0,
  40. OUTPUT_YAML_SHORT_LABELS = (1u << 0),
  41. };
  42. static const unsigned int indent_size = 2;
  43. static enum xkb_file_type
  44. xkb_parse_file_type(const char *raw)
  45. {
  46. static struct {
  47. enum xkb_file_type type;
  48. const char *name;
  49. } xkb_file_type_strings[_FILE_TYPE_NUM_ENTRIES] = {
  50. {FILE_TYPE_KEYCODES, "keycodes"},
  51. {FILE_TYPE_TYPES, "types"},
  52. {FILE_TYPE_COMPAT, "compat"},
  53. {FILE_TYPE_SYMBOLS, "symbols"},
  54. {FILE_TYPE_GEOMETRY, "geometry"},
  55. {FILE_TYPE_RULES, "rules"},
  56. };
  57. for (size_t t = 0; t < ARRAY_SIZE(xkb_file_type_strings); t++) {
  58. if (streq_not_null(raw, xkb_file_type_strings[t].name))
  59. return xkb_file_type_strings[t].type;
  60. }
  61. return FILE_TYPE_INVALID;
  62. }
  63. /**
  64. * Try to get the relative path of a file in an XKB hierarchy
  65. *
  66. * This is a fragile! We could improve it by using the context include paths,
  67. * but the analyzed path may be in an XKB hierarchy but not in the include paths.
  68. */
  69. static const char*
  70. xkb_relative_path(const char *path)
  71. {
  72. static const char * xkb_file_type_include_dirs[] = {
  73. "keycodes/",
  74. "types/",
  75. "compat/",
  76. "symbols/",
  77. "geometry/",
  78. // TODO: "keymap/",
  79. // TODO: "rules/",
  80. };
  81. for (size_t t = 0; t < ARRAY_SIZE(xkb_file_type_include_dirs); t++) {
  82. const char * const rel = strstr(path, xkb_file_type_include_dirs[t]);
  83. if (rel != NULL) {
  84. return rel + strlen(xkb_file_type_include_dirs[t]);
  85. }
  86. }
  87. return NULL;
  88. }
  89. static bool
  90. is_stdin_path(const char *path)
  91. {
  92. return isempty(path) || strcmp(path, "-") == 0;
  93. }
  94. static bool
  95. print_included_section(struct xkb_context *ctx,
  96. enum xkb_file_iterator_flags iterator_flags,
  97. enum output_format output_format,
  98. enum output_options output_options,
  99. enum xkb_keymap_format keymap_input_format,
  100. const char *path, const char *map,
  101. unsigned int include_depth, unsigned int ident_depth,
  102. const char *parent);
  103. /*******************************************************************************
  104. * YAML output
  105. ******************************************************************************/
  106. static void
  107. print_yaml_flags(unsigned int indent, enum xkb_map_flags flags)
  108. {
  109. printf("%*sflags: [", indent, "");
  110. bool first = true;
  111. const char *flag_name = NULL;
  112. unsigned int index = 0;
  113. while ((flag_name = xkb_map_flags_string_iter(&index, flags))) {
  114. if (first)
  115. first = false;
  116. else
  117. printf(", ");
  118. printf("%s", flag_name);
  119. }
  120. printf("]\n");
  121. }
  122. static bool
  123. print_yaml_included_sections(struct xkb_context *ctx,
  124. enum xkb_file_iterator_flags iterator_flags,
  125. enum output_options output_options,
  126. enum xkb_keymap_format format,
  127. const struct xkb_file_section *section,
  128. unsigned int include_depth, unsigned int ident_depth,
  129. bool recursive)
  130. {
  131. if (darray_size(section->includes)) {
  132. bool ok = true;
  133. const unsigned int indent1 = ident_depth * indent_size;
  134. printf("%*sincludes:\n", indent1, "");
  135. struct xkb_file_include_group *group;
  136. darray_foreach(group, section->include_groups) {
  137. assert(group->end < darray_size(section->includes));
  138. unsigned int indent2 = indent1;
  139. unsigned int ident_depth2 = ident_depth;
  140. if (group->start != group->end) {
  141. /* Multiple files included in a single statement */
  142. const enum merge_mode merge_mode =
  143. darray_item(section->includes, group->start).merge;
  144. printf("%*s- merge mode: %s\n", indent1, "",
  145. xkb_merge_mode_name(merge_mode));
  146. printf("%*s files:\n", indent1, "");
  147. ident_depth2 += 1;
  148. indent2 = ident_depth2 * indent_size;
  149. }
  150. for (darray_size_t i = group->start; i <= group->end; i++) {
  151. const struct xkb_file_include * const inc =
  152. &darray_item(section->includes, i);
  153. printf("%*s- merge mode: %s\n", indent2, "",
  154. xkb_merge_mode_name(inc->merge));
  155. printf("%*s file: \"%s\"\n", indent2, "",
  156. xkb_file_section_get_string(section, inc->file));
  157. printf("%*s section: \"%s\"\n", indent2, "",
  158. xkb_file_section_get_string(section, inc->section));
  159. printf("%*s explicit section: %s\n", indent2, "",
  160. (inc->explicit_section ? "true" : "false"));
  161. printf("%*s path: \"%s\"\n", indent2, "",
  162. xkb_file_section_get_string(section, inc->path));
  163. const char * const modifier =
  164. xkb_file_section_get_string(section, inc->modifier);
  165. if (!isempty(modifier))
  166. printf("%*s modifier: \"%s\"\n", indent2, "", modifier);
  167. if (inc->valid) {
  168. print_yaml_flags(indent2 + indent_size, inc->flags);
  169. } else {
  170. printf("%*s valid: false\n", indent2, "");
  171. }
  172. if (recursive && inc->valid) {
  173. ok = print_included_section(
  174. ctx, iterator_flags, OUTPUT_FORMAT_YAML, output_options,
  175. format, xkb_file_section_get_string(section, inc->path),
  176. xkb_file_section_get_string(section, inc->section),
  177. include_depth + 1, ident_depth2 + 1, NULL
  178. );
  179. if (!ok)
  180. break;
  181. }
  182. }
  183. }
  184. return ok;
  185. } else {
  186. return true;
  187. }
  188. }
  189. static bool
  190. print_yaml(struct xkb_context *ctx,
  191. enum xkb_file_iterator_flags iterator_flags,
  192. enum output_options output_options,
  193. enum xkb_keymap_format keymap_format,
  194. int path_index, const char *path, bool recursive,
  195. struct xkb_file_iterator *iter)
  196. {
  197. bool ok = true;
  198. const struct xkb_file_section *section;
  199. if (path_index > 0) {
  200. /* New YAML document */
  201. printf("---\n");
  202. }
  203. printf("path: \"%s\"\n", (is_stdin_path(path) ? "stdin" : path));
  204. printf("sections:");
  205. bool has_sections = false;
  206. while ((ok = xkb_file_iterator_next(iter, &section)) && section) {
  207. has_sections = true;
  208. printf("\n- type: %s", xkb_file_type_name(section->file_type));
  209. printf("\n section: \"%s\"\n",
  210. xkb_file_section_get_string(section, section->name));
  211. print_yaml_flags(indent_size, section->flags);
  212. print_yaml_included_sections(ctx, iterator_flags,
  213. output_options, keymap_format,
  214. section, 0, 1, recursive);
  215. }
  216. if (!has_sections)
  217. printf(" []\n");
  218. return ok;
  219. }
  220. /*******************************************************************************
  221. * Resolved path output
  222. ******************************************************************************/
  223. static bool
  224. print_resolved_path(struct xkb_context *ctx,
  225. enum xkb_file_iterator_flags iterator_flags,
  226. enum output_options output_options,
  227. enum xkb_keymap_format keymap_format,
  228. int path_index, const char *path, bool recursive,
  229. struct xkb_file_iterator *iter)
  230. {
  231. bool ok = true;
  232. const struct xkb_file_section *section;
  233. if (path_index > 0) {
  234. /* New YAML document */
  235. printf("---\n");
  236. }
  237. printf("path: \"%s\"\n", (is_stdin_path(path) ? "stdin" : path));
  238. while ((ok = xkb_file_iterator_next(iter, &section)) && section) {
  239. printf("type: %s\n", xkb_file_type_name(section->file_type));
  240. printf("section: \"%s\"\n",
  241. xkb_file_section_get_string(section, section->name));
  242. print_yaml_flags(0, section->flags);
  243. }
  244. return ok;
  245. }
  246. /*******************************************************************************
  247. * DOT output
  248. ******************************************************************************/
  249. static bool
  250. print_dot_node(enum output_options output_options, const char *parent_node,
  251. darray_char *node, darray_char *label,
  252. const struct xkb_file_section *section,
  253. const struct xkb_file_include *inc)
  254. {
  255. /* Cannot print invalid includes */
  256. if (!inc->valid)
  257. return true;
  258. /* Node identifier */
  259. darray_size(*node) = 0;
  260. darray_append_string(
  261. *node,
  262. xkb_file_section_get_string(section, inc->path)
  263. );
  264. if (inc->section) {
  265. darray_append_lit(*node, "(");
  266. darray_append_string(
  267. *node,
  268. xkb_file_section_get_string(section, inc->section)
  269. );
  270. darray_append_lit(*node, ")");
  271. }
  272. /* Node label */
  273. darray_size(*label) = 0;
  274. darray_append_lit(*label, "<B>");
  275. darray_append_string(
  276. *label,
  277. ((output_options & OUTPUT_YAML_SHORT_LABELS)
  278. ? xkb_file_section_get_string(section, inc->file)
  279. : xkb_file_section_get_string(section, inc->path))
  280. );
  281. darray_append_lit(*label, "</B>");
  282. if (inc->section) {
  283. darray_append_lit(*label, "(");
  284. darray_append_string(
  285. *label,
  286. xkb_file_section_get_string(section, inc->section)
  287. );
  288. darray_append_lit(*label, ")");
  289. }
  290. printf("\t\"%s\" [label=<%s>];\n",
  291. darray_items(*node), darray_items(*label));
  292. printf("\t\"%s\" -> \"%s\";\n",
  293. parent_node, darray_items(*node));
  294. return true;
  295. }
  296. static bool
  297. print_dot_included_sections(struct xkb_context *ctx,
  298. enum xkb_file_iterator_flags iterator_flags,
  299. enum output_options output_options,
  300. enum xkb_keymap_format format,
  301. const struct xkb_file_section *section,
  302. unsigned int include_depth,
  303. bool recursive, const char *parent)
  304. {
  305. if (darray_size(section->includes)) {
  306. bool ok = true;
  307. darray_char parent2_node = darray_new();
  308. darray_char parent2_label = darray_new();
  309. struct xkb_file_include *inc;
  310. darray_foreach(inc, section->includes) {
  311. assert(inc->valid);
  312. ok = print_dot_node(output_options, parent,
  313. &parent2_node, &parent2_label,
  314. section, inc);
  315. if (!ok)
  316. break;
  317. if (recursive && inc->valid) {
  318. ok = print_included_section(
  319. ctx, iterator_flags, OUTPUT_FORMAT_DOT, output_options,
  320. format, xkb_file_section_get_string(section, inc->path),
  321. xkb_file_section_get_string(section, inc->section),
  322. include_depth + 1, 0, darray_items(parent2_node)
  323. );
  324. if (!ok)
  325. break;
  326. }
  327. }
  328. darray_free(parent2_node);
  329. darray_free(parent2_label);
  330. return ok;
  331. } else {
  332. return true;
  333. }
  334. }
  335. static bool
  336. print_dot(struct xkb_context *ctx,
  337. enum xkb_file_iterator_flags iterator_flags,
  338. enum output_options output_options,
  339. enum xkb_keymap_format format,
  340. int path_index, const char *path, bool recursive,
  341. struct xkb_file_iterator *iter)
  342. {
  343. char root[PATH_MAX] = "";
  344. if (is_stdin_path(path)) {
  345. if (unlikely(!strcpy_safe(root, sizeof(root), "stdin"))) {
  346. return false;
  347. }
  348. } else {
  349. #if HAVE_REAL_PATH
  350. if (!realpath(path, root)) {
  351. return false;
  352. }
  353. #else
  354. if (unlikely(!strcpy_safe(root, sizeof(root), path)))
  355. return false;
  356. #endif
  357. }
  358. const char * root_file = xkb_relative_path(root);
  359. bool ok = true;
  360. const struct xkb_file_section *section;
  361. darray_char root_node = darray_new();
  362. darray_char parent_node = darray_new();
  363. darray_char parent_label = darray_new();
  364. unsigned int idx = 0;
  365. bool is_composite_file = false;
  366. while ((ok = xkb_file_iterator_next(iter, &section)) && section) {
  367. if (idx == 0) {
  368. /* Check if this is a composite file */
  369. if (section->file_type == FILE_TYPE_KEYMAP) {
  370. is_composite_file = true;
  371. } else {
  372. /* Root node set globally */
  373. darray_append_string(root_node, root);
  374. printf("\t\"%s\" [label=<<B>%s</B>>, style=\"rounded,filled\"];\n",
  375. darray_items(root_node), root);
  376. if (path_index == 0) {
  377. // FIXME: handle multiple roots using subgraphs?
  378. printf("root=\"%s\";\n", darray_items(root_node));
  379. }
  380. }
  381. }
  382. /* Node identifier */
  383. darray_size(parent_node) = 0;
  384. /* Prefix with section type to avoid ID clashes */
  385. if (is_composite_file) {
  386. darray_append_string(parent_node,
  387. xkb_file_type_name(section->file_type));
  388. darray_append_lit(parent_node, ":");
  389. }
  390. /* Append full path & section */
  391. darray_append_string(parent_node, root);
  392. if (section->name) {
  393. darray_append_lit(parent_node, "(");
  394. darray_append_string(
  395. parent_node,
  396. xkb_file_section_get_string(section, section->name)
  397. );
  398. darray_append_lit(parent_node, ")");
  399. }
  400. if (section->file_type == FILE_TYPE_KEYMAP) {
  401. /* Root node set for each keymap */
  402. assert(is_composite_file);
  403. darray_copy(root_node, parent_node);
  404. darray_append(root_node, '\0');
  405. }
  406. /* Node label */
  407. darray_size(parent_label) = 0;
  408. if (!is_composite_file || section->file_type == FILE_TYPE_KEYMAP) {
  409. /* Display file only for top-level components */
  410. darray_append_lit(parent_label, "<B>");
  411. if (root_file && (output_options & OUTPUT_YAML_SHORT_LABELS)) {
  412. darray_append_string(parent_label, root_file);
  413. } else {
  414. darray_append_string(parent_label, root);
  415. }
  416. darray_append_lit(parent_label, "</B>");
  417. }
  418. if (section->name) {
  419. darray_append_lit(parent_label, "(");
  420. darray_append_string(
  421. parent_label,
  422. xkb_file_section_get_string(section, section->name)
  423. );
  424. darray_append_lit(parent_label, ")");
  425. } else {
  426. darray_append_string(
  427. parent_label,
  428. (is_composite_file ? "(unnamed)" : "(-)")
  429. );
  430. }
  431. if (is_composite_file) {
  432. if (section->file_type == FILE_TYPE_KEYMAP && idx != 0) {
  433. /* Close the previous keymap subgraph */
  434. printf("}\n");
  435. }
  436. /* Draw each component in a subgraph */
  437. printf("\nsubgraph \"cluster::%s\" {\n", darray_items(parent_node));
  438. printf("\tlabel=<<B>%s</B>>;\n",
  439. xkb_file_type_name(section->file_type));
  440. }
  441. /* Create edge */
  442. if (section->file_type == FILE_TYPE_KEYMAP) {
  443. /*
  444. * Avoid keymap node being included in component clusters by
  445. * creating its own cluster.
  446. */
  447. printf("\nsubgraph \"cluster::root::%s\" {\n",
  448. darray_items(parent_node));
  449. printf("\tstyle=invis;\n");
  450. printf("\t\"%s\" [label=<%s>, style=\"rounded,filled\"];\n",
  451. darray_items(parent_node), darray_items(parent_label));
  452. printf("}\n");
  453. printf("root=\"%s\";\n\n", darray_items(parent_node));
  454. } else {
  455. printf("\t\"%s\" [label=<%s>];\n",
  456. darray_items(parent_node), darray_items(parent_label));
  457. }
  458. /* Link to root */
  459. if (section->file_type != FILE_TYPE_KEYMAP) {
  460. printf("\t\"%s\" -> \"%s\" [arrowhead=empty];\n",
  461. darray_items(root_node), darray_items(parent_node));
  462. }
  463. print_dot_included_sections(ctx, iterator_flags,
  464. output_options, format,
  465. section, 0, recursive, darray_items(parent_node));
  466. if (is_composite_file && section->file_type != FILE_TYPE_KEYMAP) {
  467. printf("}\n");
  468. }
  469. idx++;
  470. }
  471. if (is_composite_file) {
  472. /* Close the keymap subgraph */
  473. printf("}\n");
  474. }
  475. darray_free(root_node);
  476. darray_free(parent_node);
  477. darray_free(parent_label);
  478. return ok;
  479. }
  480. /*******************************************************************************
  481. * Output format: RDF Turtle
  482. ******************************************************************************/
  483. static void
  484. mk_rdf_path_id(darray_char *node, const char *path)
  485. {
  486. darray_append_lit(*node, "file:");
  487. darray_append_string(*node, (is_stdin_path(path) ? "stdin" : path));
  488. }
  489. static void
  490. mk_rdf_section_id(darray_char *node, const char *path, const char *section)
  491. {
  492. mk_rdf_path_id(node, path);
  493. darray_append_lit(*node, "#section=");
  494. darray_append_string(*node, section);
  495. }
  496. static void
  497. print_rdf_flags(enum xkb_map_flags flags)
  498. {
  499. bool first = true;
  500. const char *flag_name = NULL;
  501. unsigned int index = 0;
  502. while ((flag_name = xkb_map_flags_string_iter(&index, flags))) {
  503. if (first)
  504. first = false;
  505. else
  506. printf(", ");
  507. printf("flags:%s", flag_name);
  508. }
  509. }
  510. static bool
  511. print_rdf_sections(struct xkb_context *ctx,
  512. enum xkb_file_iterator_flags iterator_flags,
  513. enum output_options output_options,
  514. enum xkb_keymap_format keymap_format,
  515. const struct xkb_file_section *section,
  516. unsigned int include_depth, bool recursive,
  517. const char *path, const char *map,
  518. unsigned int index, const char *node)
  519. {
  520. printf("<%s>\n", node);
  521. printf("\txkb:path\t\"%s\" ;\n", (is_stdin_path(path) ? "stdin" : path));
  522. printf("\txkb:section\t\"%s\" ;\n", map);
  523. printf("\trdf:type\txkb:%s ;\n", xkb_file_type_name(section->file_type));
  524. if (section->flags) {
  525. printf("\txkb:flag\t");
  526. print_rdf_flags(section->flags);
  527. printf(" ;\n");
  528. }
  529. printf("\txkb:section-index\t%u", index);
  530. if (darray_size(section->includes)) {
  531. bool ok = true;
  532. darray_char include_target = darray_new();
  533. struct xkb_file_include_group *group;
  534. darray_size_t g = 0;
  535. printf(" ;\n\txkb:includes\t(");
  536. darray_enumerate(g, group, section->include_groups) {
  537. printf("\n\t\t(");
  538. for (darray_size_t f = group->start; f <= group->end; f++) {
  539. const struct xkb_file_include * const inc =
  540. &darray_item(section->includes, f);
  541. assert(inc->valid);
  542. darray_size(include_target) = 0;
  543. mk_rdf_section_id(
  544. &include_target,
  545. xkb_file_section_get_string(section, inc->path),
  546. xkb_file_section_get_string(section, inc->section)
  547. );
  548. printf("\n\t\t\t[\n");
  549. printf("\t\t\t\txkb:merge-mode\txkb:%s ;\n",
  550. xkb_merge_mode_name(inc->merge));
  551. printf("\t\t\t\txkb:file\t\"%s\" ;\n",
  552. xkb_file_section_get_string(section, inc->file));
  553. printf("\t\t\t\txkb:section\t\"%s\" ;\n",
  554. xkb_file_section_get_string(section, inc->section));
  555. printf("\t\t\t\txkb:path\t\"%s\"",
  556. xkb_file_section_get_string(section, inc->path));
  557. if (inc->flags) {
  558. printf(" ;\n\t\t\t\txkb:flag\t");
  559. print_rdf_flags(inc->flags);
  560. }
  561. printf(" ;\n\t\t\t\txkb:includes\t<%s>\n",
  562. darray_items(include_target));
  563. printf("\t\t\t]");
  564. }
  565. printf("\n\t\t)");
  566. }
  567. printf("\n\t) .\n\n");
  568. if (recursive) {
  569. struct xkb_file_include * inc;
  570. darray_foreach(inc, section->includes) {
  571. if (!inc->valid)
  572. continue;
  573. darray_size(include_target) = 0;
  574. mk_rdf_section_id(
  575. &include_target,
  576. xkb_file_section_get_string(section, inc->path),
  577. xkb_file_section_get_string(section, inc->section)
  578. );
  579. ok = print_included_section(
  580. ctx, iterator_flags, OUTPUT_FORMAT_RDF_TURTLE,
  581. output_options, keymap_format,
  582. xkb_file_section_get_string(section, inc->path),
  583. xkb_file_section_get_string(section, inc->section),
  584. include_depth + 1, 0, darray_items(include_target)
  585. );
  586. if (!ok)
  587. break;
  588. }
  589. }
  590. darray_free(include_target);
  591. return ok;
  592. } else {
  593. printf(" .\n\n");
  594. return true;
  595. }
  596. }
  597. static bool
  598. print_rdf(struct xkb_context *ctx,
  599. enum xkb_file_iterator_flags iterator_flags,
  600. enum output_options output_options,
  601. enum xkb_keymap_format keymap_format,
  602. int path_index, const char *path, const char *map, bool recursive,
  603. struct xkb_file_iterator *iter)
  604. {
  605. bool ok = true;
  606. const struct xkb_file_section *section;
  607. bool is_composite_file = false;
  608. darray_char keymap = darray_new();
  609. darray_char node = darray_new();
  610. /* Save some CLI arguments, so that the graph is easier to query */
  611. mk_rdf_path_id(&node, path);
  612. printf("<%s>\n", darray_items(node));
  613. printf("\trdf:type\txkb:Introspection ;\n");
  614. printf("\txkb:path\t\"%s\" ;\n",
  615. (is_stdin_path(path) ? "stdin" : path));
  616. printf("\txkb:section\t\"%s\" .\n\n",
  617. (isempty(map) ? "" : map));
  618. unsigned int index = 0;
  619. while ((ok = xkb_file_iterator_next(iter, &section)) && section) {
  620. if (section->file_type == FILE_TYPE_KEYMAP)
  621. is_composite_file = true;
  622. darray_size(node) = 0;
  623. mk_rdf_section_id(&node, path,
  624. xkb_file_section_get_string(section, section->name));
  625. if (is_composite_file) {
  626. /* Disambiguate components */
  627. darray_append_lit(node, ":type=");
  628. darray_append_string(node,
  629. xkb_file_type_name(section->file_type));
  630. if (section->file_type == FILE_TYPE_KEYMAP) {
  631. /* Backup keymap node */
  632. darray_size(keymap) = 0;
  633. darray_copy(keymap, node);
  634. darray_append(keymap, '\0');
  635. } else {
  636. /* Link component to parent keymap */
  637. printf("<%s>\txkb:includes\t<%s> .\n\n",
  638. darray_items(keymap), darray_items(node));
  639. }
  640. }
  641. print_rdf_sections(ctx, iterator_flags,
  642. output_options, keymap_format,
  643. section, 0, recursive, path,
  644. xkb_file_section_get_string(section, section->name),
  645. index, darray_items(node));
  646. index++;
  647. }
  648. darray_free(keymap);
  649. darray_free(node);
  650. return ok;
  651. }
  652. /*******************************************************************************
  653. * Common output
  654. ******************************************************************************/
  655. static bool
  656. print_included_section(struct xkb_context *ctx,
  657. enum xkb_file_iterator_flags iterator_flags,
  658. enum output_format output_format,
  659. enum output_options output_options,
  660. enum xkb_keymap_format format,
  661. const char *path, const char *map,
  662. unsigned int include_depth, unsigned int ident_depth,
  663. const char *parent)
  664. {
  665. if (isempty(map))
  666. map = NULL;
  667. struct xkb_file_section section = {0};
  668. xkb_file_section_init(&section);
  669. bool ok = xkb_file_section_parse(ctx, iterator_flags, format,
  670. XKB_KEYMAP_COMPILE_NO_FLAGS,
  671. include_depth, path, map, &section);
  672. if (!ok)
  673. goto out;
  674. switch (output_format) {
  675. case OUTPUT_FORMAT_YAML:
  676. ok = print_yaml_included_sections(ctx, iterator_flags,
  677. output_options, format, &section,
  678. include_depth, ident_depth, true);
  679. break;
  680. case OUTPUT_FORMAT_DOT:
  681. ok = print_dot_included_sections(ctx, iterator_flags,
  682. output_options, format, &section,
  683. include_depth, true, parent);
  684. break;
  685. case OUTPUT_FORMAT_RDF_TURTLE:
  686. ok = print_rdf_sections(ctx, iterator_flags,
  687. output_options, format, &section,
  688. include_depth, true, path, map, 0, parent);
  689. break;
  690. default:
  691. /* unreachable */
  692. assert(!"unreachable");
  693. }
  694. out:
  695. xkb_file_section_free(&section);
  696. return ok;
  697. }
  698. static void
  699. print_sections_header(enum output_format output_format)
  700. {
  701. switch (output_format) {
  702. case OUTPUT_FORMAT_YAML:
  703. case OUTPUT_FORMAT_RESOLVED_PATH:
  704. break;
  705. case OUTPUT_FORMAT_DOT:
  706. printf("digraph {\n");
  707. printf("node [shape=box, style=rounded];\n");
  708. printf("overlap=false;\n");
  709. printf("concentrate=true;\n");
  710. printf("rankdir=\"LR\";\n");
  711. printf("fontsize=\"20pt\";\n");
  712. break;
  713. case OUTPUT_FORMAT_RDF_TURTLE:
  714. printf("@prefix\trdf:\t<http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n");
  715. printf("@prefix\txkb:\t<xkb:> .\n");
  716. printf("@prefix\tflags:\t<xkb:flags/> .\n\n");
  717. break;
  718. default:
  719. assert(!"unreachable");
  720. }
  721. }
  722. static void
  723. print_sections_footer(enum output_format output_format)
  724. {
  725. switch (output_format) {
  726. case OUTPUT_FORMAT_YAML:
  727. case OUTPUT_FORMAT_RESOLVED_PATH:
  728. case OUTPUT_FORMAT_RDF_TURTLE:
  729. break;
  730. case OUTPUT_FORMAT_DOT:
  731. /* Close graph */
  732. printf("}\n");
  733. break;
  734. default:
  735. assert(!"unreachable");
  736. }
  737. }
  738. static int
  739. print_sections(struct xkb_context *ctx,
  740. enum xkb_file_iterator_flags iterator_flags,
  741. enum output_format output_format,
  742. enum output_options output_options,
  743. enum xkb_keymap_format keymap_format, enum input_source source,
  744. enum xkb_file_type file_type,
  745. int path_index, const char *path, const char *map,
  746. bool recursive)
  747. {
  748. if (source == INPUT_SOURCE_PATH && is_stdin_path(path)) {
  749. source = INPUT_SOURCE_STDIN;
  750. path = NULL;
  751. }
  752. int ret = EXIT_FAILURE;
  753. FILE *file = NULL;
  754. char resolved_path[PATH_MAX] = {0};
  755. char resolved_section[1024] = {0};
  756. if (source == INPUT_SOURCE_PATH && !is_stdin_path(path)) {
  757. /* Read from regular file */
  758. const bool absolute = is_absolute_path(path);
  759. if (output_format != OUTPUT_FORMAT_RESOLVED_PATH &&
  760. (absolute || file_type > FILE_TYPE_KEYMAP)) {
  761. /*
  762. * Absolute path or undefined file type: open directly.
  763. * Relative paths are resolved using the working directory as usual.
  764. */
  765. file = fopen(path, "rb");
  766. if (!file) {
  767. fprintf(stderr, "ERROR: Failed to open keymap file \"%s\": %s\n",
  768. path, strerror(errno));
  769. return ret;
  770. }
  771. if (unlikely(!strcpy_safe(resolved_path, sizeof(resolved_path), path))) {
  772. goto map_error;
  773. }
  774. } else {
  775. /*
  776. * Relative path: interpret as a file in an XKB tree of the given
  777. * file type
  778. */
  779. // TODO: this is currently a bit silly, since we are parsing the
  780. // file here and then again in the file iterator.
  781. file = xkb_resolve_file(ctx, file_type,
  782. path, (isempty(map) ? NULL : map),
  783. resolved_path, sizeof(resolved_path),
  784. resolved_section, sizeof(resolved_section));
  785. if (!file) {
  786. fprintf(stderr,
  787. "ERROR: File not found in XKB paths: %s (section: %s)\n",
  788. path, (isempty(map)) ? "(none)" : map);
  789. return ret;
  790. }
  791. if (output_format == OUTPUT_FORMAT_RESOLVED_PATH &&
  792. !isempty(resolved_section)) {
  793. assert(isempty(map) || streq(map, resolved_section));
  794. map = resolved_section;
  795. }
  796. }
  797. } else {
  798. /* Read from stdin */
  799. file = tools_read_stdin();
  800. }
  801. char *string;
  802. size_t string_len;
  803. if (!map_file(file, &string, &string_len)) {
  804. fprintf(stderr, "ERROR: cannot map file\n");
  805. goto map_error;
  806. }
  807. // FIXME: check that file_type is respected in the iterator
  808. struct xkb_file_iterator * iter = xkb_file_iterator_new_from_buffer(
  809. ctx, iterator_flags, keymap_format, XKB_KEYMAP_COMPILE_NO_FLAGS,
  810. (is_stdin_path(path) ? "(stdin)" : path), map, file_type,
  811. string, string_len
  812. );
  813. if (!iter) {
  814. fprintf(stderr, "ERROR: cannot create iterator\n");
  815. goto iter_error;
  816. }
  817. bool ok = true;
  818. switch (output_format) {
  819. case OUTPUT_FORMAT_YAML:
  820. ok = print_yaml(ctx, iterator_flags, output_options,
  821. keymap_format, path_index, path, recursive, iter);
  822. break;
  823. case OUTPUT_FORMAT_RESOLVED_PATH:
  824. ok = print_resolved_path(ctx, iterator_flags, output_options,
  825. keymap_format, path_index, resolved_path,
  826. recursive, iter);
  827. break;
  828. case OUTPUT_FORMAT_DOT:
  829. ok = print_dot(ctx, iterator_flags, output_options,
  830. keymap_format, path_index, path, recursive, iter);
  831. break;
  832. case OUTPUT_FORMAT_RDF_TURTLE:
  833. ok = print_rdf(ctx, iterator_flags, output_options,
  834. keymap_format, path_index,
  835. resolved_path, map, recursive, iter);
  836. break;
  837. default:
  838. /* unreachable */
  839. assert(!"unreachable");
  840. }
  841. xkb_file_iterator_free(iter);
  842. ret = (ok) ? EXIT_SUCCESS : EXIT_FAILURE;
  843. iter_error:
  844. unmap_file(string, string_len);
  845. map_error:
  846. if (file)
  847. fclose(file);
  848. return ret;
  849. }
  850. /*******************************************************************************
  851. * CLI handling
  852. ******************************************************************************/
  853. static void
  854. usage(FILE *file, const char *progname)
  855. {
  856. fprintf(file,
  857. "Usage: %s [OPTIONS] [FILES]\n"
  858. "\n"
  859. "Introspect an XKB file\n"
  860. "\n"
  861. "General options:\n"
  862. " --help\n"
  863. " Print this help and exit\n"
  864. " --verbose\n"
  865. " Enable verbose debugging output\n"
  866. "\n"
  867. "Input options:\n"
  868. " --include\n"
  869. " Add the given path to the include path list. This option is\n"
  870. " order-dependent, include paths given first are searched first.\n"
  871. " If an include path is given, the default include path list is\n"
  872. " not used. Use --include-defaults to add the default include\n"
  873. " paths\n"
  874. " --include-defaults\n"
  875. " Add the default set of include directories.\n"
  876. " This option is order-dependent, include paths given first\n"
  877. " are searched first.\n"
  878. " --format <format>\n"
  879. " The keymap format to use for parsing (default: '%s')\n"
  880. " --section <name>\n"
  881. " The name of a specific section to parse\n"
  882. " --type <type>\n"
  883. " The type of XKB file (KcCGST): keycodes, compatibility, geometry, symbols, types.\n"
  884. " --recursive\n"
  885. " Recursive analysis of the included sections\n"
  886. " --include-failures\n"
  887. " Do not stop on include failures but collect them (YAML only)\n"
  888. " --resolve\n"
  889. " Output resolved paths (YAML only)\n"
  890. " --yaml\n"
  891. " Output YAML\n"
  892. " --dot\n"
  893. " Output a DOT graph\n"
  894. " --rdf\n"
  895. " Output a RDF graph in the Turtle format\n"
  896. " --long-labels\n"
  897. " Output long nodes labels\n"
  898. "\n"
  899. "This program can process multiple files. Use e.g.:\n"
  900. " %s \\\n"
  901. " $(find \"" DFLT_XKB_CONFIG_ROOT "/symbols\" -type f -not -name README | xargs)\n"
  902. "to process all symbols files."
  903. "\n",
  904. progname, xkb_keymap_get_format_label(DEFAULT_INPUT_KEYMAP_FORMAT),
  905. progname);
  906. }
  907. #define DEFAULT_INCLUDE_PATH_PLACEHOLDER "__defaults__"
  908. static const char *includes[64] = { 0 };
  909. static size_t num_includes = 0;
  910. static bool
  911. parse_options(int argc, char **argv, bool *verbose,
  912. enum input_source *input_source,
  913. enum xkb_keymap_format *keymap_input_format,
  914. enum xkb_file_iterator_flags *iterator_flags,
  915. int *paths_start, char **section,
  916. enum xkb_file_type *section_type,
  917. bool *recursive,
  918. enum output_format *output_format,
  919. enum output_options *output_options)
  920. {
  921. enum input_source input_format = INPUT_SOURCE_AUTO;
  922. enum options {
  923. /* General */
  924. OPT_VERBOSE,
  925. /* Input */
  926. OPT_INCLUDE,
  927. OPT_INCLUDE_DEFAULTS,
  928. OPT_INCLUDE_FAILURES,
  929. OPT_KEYMAP_FORMAT,
  930. OPT_SECTION_NAME,
  931. OPT_SECTION_TYPE,
  932. OPT_RECURSIVE,
  933. OPT_OUTPUT_RESOLVED_PATH,
  934. OPT_OUTPUT_YAML,
  935. OPT_OUTPUT_DOT,
  936. OPT_OUTPUT_RDF,
  937. OPT_LONG_LABELS,
  938. };
  939. static struct option opts[] = {
  940. /*
  941. * General
  942. */
  943. {"help", no_argument, 0, 'h'},
  944. {"verbose", no_argument, 0, OPT_VERBOSE},
  945. /*
  946. * Input
  947. */
  948. {"include", required_argument, 0, OPT_INCLUDE},
  949. {"include-defaults", no_argument, 0, OPT_INCLUDE_DEFAULTS},
  950. {"include-failures", no_argument, 0, OPT_INCLUDE_FAILURES},
  951. {"format", required_argument, 0, OPT_KEYMAP_FORMAT},
  952. {"section", required_argument, 0, OPT_SECTION_NAME},
  953. {"type", required_argument, 0, OPT_SECTION_TYPE},
  954. {"recursive", no_argument, 0, OPT_RECURSIVE},
  955. {"resolve", no_argument, 0, OPT_OUTPUT_RESOLVED_PATH},
  956. {"yaml", no_argument, 0, OPT_OUTPUT_YAML},
  957. {"dot", no_argument, 0, OPT_OUTPUT_DOT},
  958. {"rdf", no_argument, 0, OPT_OUTPUT_RDF},
  959. {"long-labels", no_argument, 0, OPT_LONG_LABELS},
  960. {0, 0, 0, 0},
  961. };
  962. int option_index = 0;
  963. while (1) {
  964. option_index = 0;
  965. int c = getopt_long(argc, argv, "h", opts, &option_index);
  966. if (c == -1)
  967. break;
  968. switch (c) {
  969. /* General */
  970. case 'h':
  971. usage(stdout, argv[0]);
  972. exit(0);
  973. case OPT_VERBOSE:
  974. *verbose = true;
  975. break;
  976. /* Input */
  977. case OPT_INCLUDE:
  978. if (num_includes >= ARRAY_SIZE(includes))
  979. goto too_many_includes;
  980. includes[num_includes++] = optarg;
  981. break;
  982. case OPT_INCLUDE_DEFAULTS:
  983. if (num_includes >= ARRAY_SIZE(includes))
  984. goto too_many_includes;
  985. includes[num_includes++] = DEFAULT_INCLUDE_PATH_PLACEHOLDER;
  986. break;
  987. case OPT_INCLUDE_FAILURES:
  988. *iterator_flags &= ~XKB_FILE_ITERATOR_FAIL_ON_INCLUDE_ERROR;
  989. break;
  990. case OPT_KEYMAP_FORMAT:
  991. *keymap_input_format = xkb_keymap_parse_format(optarg);
  992. if (!(*keymap_input_format)) {
  993. fprintf(stderr, "ERROR: invalid --format: \"%s\"\n", optarg);
  994. usage(stderr, argv[0]);
  995. exit(EXIT_INVALID_USAGE);
  996. }
  997. break;
  998. case OPT_SECTION_NAME:
  999. *section = optarg;
  1000. break;
  1001. case OPT_SECTION_TYPE:
  1002. *section_type = xkb_parse_file_type(optarg);
  1003. if (*section_type == FILE_TYPE_INVALID) {
  1004. fprintf(stderr, "ERROR: invalid --type: \"%s\"\n", optarg);
  1005. usage(stderr, argv[0]);
  1006. exit(EXIT_INVALID_USAGE);
  1007. }
  1008. break;
  1009. case OPT_RECURSIVE:
  1010. *recursive = true;
  1011. break;
  1012. case OPT_OUTPUT_RESOLVED_PATH:
  1013. *output_format = OUTPUT_FORMAT_RESOLVED_PATH;
  1014. *iterator_flags |= XKB_FILE_ITERATOR_NO_INCLUDES;
  1015. break;
  1016. case OPT_OUTPUT_YAML:
  1017. *output_format = OUTPUT_FORMAT_YAML;
  1018. break;
  1019. case OPT_OUTPUT_DOT:
  1020. *output_format = OUTPUT_FORMAT_DOT;
  1021. break;
  1022. case OPT_OUTPUT_RDF:
  1023. *output_format = OUTPUT_FORMAT_RDF_TURTLE;
  1024. break;
  1025. case OPT_LONG_LABELS:
  1026. *output_options &= ~OUTPUT_YAML_SHORT_LABELS;
  1027. break;
  1028. default:
  1029. goto invalid_usage;
  1030. }
  1031. }
  1032. if (*output_format != OUTPUT_FORMAT_YAML &&
  1033. !(*iterator_flags & XKB_FILE_ITERATOR_FAIL_ON_INCLUDE_ERROR)) {
  1034. fprintf(stderr,
  1035. "ERROR: --include-failures is only compatible with YAML output\n");
  1036. goto invalid_usage;
  1037. }
  1038. if (optind < argc && !isempty(argv[optind])) {
  1039. /* Some positional arguments left: use as a file input */
  1040. if (input_format != INPUT_SOURCE_AUTO) {
  1041. fprintf(stderr, "ERROR: Too many positional arguments\n");
  1042. goto invalid_usage;
  1043. }
  1044. input_format = INPUT_SOURCE_PATH;
  1045. *paths_start = optind;
  1046. } else if (is_pipe_or_regular_file(STDIN_FILENO) &&
  1047. input_format == INPUT_SOURCE_AUTO &&
  1048. *output_format != OUTPUT_FORMAT_RESOLVED_PATH) {
  1049. /* No positional argument: detect piping */
  1050. input_format = INPUT_SOURCE_STDIN;
  1051. }
  1052. *input_source = input_format;
  1053. return true;
  1054. too_many_includes:
  1055. fprintf(stderr, "ERROR: too many includes (max: %zu)\n",
  1056. ARRAY_SIZE(includes));
  1057. invalid_usage:
  1058. usage(stderr, argv[0]);
  1059. exit(EXIT_INVALID_USAGE);
  1060. }
  1061. int
  1062. main(int argc, char **argv)
  1063. {
  1064. struct xkb_context *ctx;
  1065. bool verbose = false;
  1066. int paths_start = argc;
  1067. char *map = NULL;
  1068. enum xkb_file_type section_type = FILE_TYPE_INVALID;
  1069. enum xkb_keymap_format keymap_input_format = DEFAULT_INPUT_KEYMAP_FORMAT;
  1070. enum xkb_file_iterator_flags iterator_flags =
  1071. XKB_FILE_ITERATOR_FAIL_ON_INCLUDE_ERROR;
  1072. bool recursive = false;
  1073. enum output_format output_format = OUTPUT_FORMAT_YAML;
  1074. enum output_options output_options = OUTPUT_YAML_SHORT_LABELS;
  1075. int rc = 1;
  1076. setlocale(LC_ALL, "");
  1077. if (argc < 1) {
  1078. usage(stderr, argv[0]);
  1079. return EXIT_INVALID_USAGE;
  1080. }
  1081. enum input_source input_source = INPUT_SOURCE_AUTO;
  1082. if (!parse_options(argc, argv, &verbose, &input_source,
  1083. &keymap_input_format, &iterator_flags,
  1084. &paths_start, &map, &section_type, &recursive,
  1085. &output_format, &output_options))
  1086. return EXIT_INVALID_USAGE;
  1087. enum xkb_context_flags ctx_flags = XKB_CONTEXT_NO_DEFAULT_INCLUDES;
  1088. ctx = xkb_context_new(ctx_flags);
  1089. assert(ctx);
  1090. if (verbose)
  1091. tools_enable_verbose_logging(ctx);
  1092. if (num_includes == 0)
  1093. includes[num_includes++] = DEFAULT_INCLUDE_PATH_PLACEHOLDER;
  1094. for (size_t i = 0; i < num_includes; i++) {
  1095. const char *include = includes[i];
  1096. if (strcmp(include, DEFAULT_INCLUDE_PATH_PLACEHOLDER) == 0)
  1097. xkb_context_include_path_append_default(ctx);
  1098. else
  1099. xkb_context_include_path_append(ctx, include);
  1100. }
  1101. static char * no_paths[] = { NULL };
  1102. if (input_source == INPUT_SOURCE_STDIN) {
  1103. /* Dummy list of paths */
  1104. argv = no_paths;
  1105. argc = (int) ARRAY_SIZE(no_paths);
  1106. paths_start = 0;
  1107. }
  1108. print_sections_header(output_format);
  1109. for (int p = paths_start; p < argc; p++) {
  1110. // fprintf(stderr,
  1111. // "------\nProcessing: %s\n",
  1112. // (is_stdin_path(argv[p]) ? "stdin" : argv[p]));
  1113. rc = print_sections(ctx, iterator_flags, output_format, output_options,
  1114. keymap_input_format, input_source, section_type,
  1115. p - paths_start, argv[p], map, recursive);
  1116. if (rc != EXIT_SUCCESS)
  1117. break;
  1118. }
  1119. print_sections_footer(output_format);
  1120. xkb_context_unref(ctx);
  1121. return rc;
  1122. }