tcldot.c 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at https://graphviz.org
  9. *************************************************************************/
  10. #include "tcldot.h"
  11. #include <cgraph/rdr.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <tcl.h>
  15. #include <util/alloc.h>
  16. static int dotnew_internal(ClientData clientData, Tcl_Interp *interp, int argc,
  17. char *argv[]) {
  18. ictx_t *ictx = (ictx_t *)clientData;
  19. Agraph_t *g;
  20. int i;
  21. Agdesc_t kind;
  22. if ((argc < 2)) {
  23. Tcl_AppendResult(
  24. interp, "wrong # args: should be \"", argv[0],
  25. " graphtype ?graphname? ?attributename attributevalue? ?...?\"", NULL);
  26. return TCL_ERROR;
  27. }
  28. if (strcmp("digraph", argv[1]) == 0) {
  29. kind = Agdirected;
  30. } else if (strcmp("digraphstrict", argv[1]) == 0) {
  31. kind = Agstrictdirected;
  32. } else if (strcmp("graph", argv[1]) == 0) {
  33. kind = Agundirected;
  34. } else if (strcmp("graphstrict", argv[1]) == 0) {
  35. kind = Agstrictundirected;
  36. } else {
  37. Tcl_AppendResult(interp, "bad graphtype \"", argv[1], "\": must be one of:",
  38. "\n\tdigraph, digraphstrict, graph, graphstrict.", NULL);
  39. return TCL_ERROR;
  40. }
  41. if (argc % 2) {
  42. /* if odd number of args then argv[2] is name */
  43. g = agopen(argv[2], kind, (Agdisc_t *)ictx);
  44. i = 3;
  45. } else {
  46. /* else use handle as name */
  47. #if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 4
  48. char *name = gv_strdup(Tcl_GetStringResult(interp));
  49. g = agopen(name, kind, (Agdisc_t *)ictx);
  50. free(name);
  51. #else
  52. g = agopen(Tcl_GetStringResult(interp), kind, (Agdisc_t *)ictx);
  53. #endif
  54. i = 2;
  55. }
  56. if (!g) {
  57. Tcl_AppendResult(interp, "\nFailure to open graph.", NULL);
  58. return TCL_ERROR;
  59. }
  60. setgraphattributes(g, &argv[i], argc - i);
  61. Tcl_AppendResult(interp, obj2cmd(g), NULL);
  62. return TCL_OK;
  63. }
  64. static int dotnew(ClientData clientData, Tcl_Interp *interp, int argc,
  65. const char *argv[]) {
  66. char **argv_copy = tcldot_argv_dup(argc, argv);
  67. int rc = dotnew_internal(clientData, interp, argc, argv_copy);
  68. tcldot_argv_free(argc, argv_copy);
  69. return rc;
  70. }
  71. static int dotread(ClientData clientData, Tcl_Interp *interp, int argc,
  72. const char *argv[]) {
  73. Agraph_t *g;
  74. Tcl_Channel channel;
  75. int mode;
  76. ictx_t *ictx = (ictx_t *)clientData;
  77. ictx->myioDisc.afread =
  78. myiodisc_afread; /* replace afread to use Tcl Channels */
  79. if (argc < 2) {
  80. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  81. " fileHandle\"", NULL);
  82. return TCL_ERROR;
  83. }
  84. channel = Tcl_GetChannel(interp, argv[1], &mode);
  85. if (channel == NULL || !(mode & TCL_READABLE)) {
  86. Tcl_AppendResult(interp, "\nChannel \"", argv[1], "\"", "is unreadable.",
  87. NULL);
  88. return TCL_ERROR;
  89. }
  90. /*
  91. * read a graph from the channel, the channel is left open
  92. * ready to read the first line after the last line of
  93. * a properly parsed graph. If the graph doesn't parse
  94. * during reading then the channel will be left at EOF
  95. */
  96. g = agread((FILE *)channel, (Agdisc_t *)clientData);
  97. if (!g) {
  98. Tcl_AppendResult(interp, "\nFailure to read graph \"", argv[1], "\"", NULL);
  99. if (agerrors()) {
  100. Tcl_AppendResult(interp, " because of syntax errors.", NULL);
  101. }
  102. return TCL_ERROR;
  103. }
  104. if (agerrors()) {
  105. Tcl_AppendResult(interp, "\nSyntax errors in file \"", argv[1], " \"",
  106. NULL);
  107. return TCL_ERROR;
  108. }
  109. Tcl_AppendResult(interp, obj2cmd(g), NULL);
  110. return TCL_OK;
  111. }
  112. static int dotstring(ClientData clientData, Tcl_Interp *interp, int argc,
  113. const char *argv[]) {
  114. Agraph_t *g;
  115. ictx_t *ictx = (ictx_t *)clientData;
  116. rdr_t rdr;
  117. if (argc < 2) {
  118. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " string\"",
  119. NULL);
  120. return TCL_ERROR;
  121. }
  122. ictx->myioDisc.afread =
  123. myiodisc_memiofread; /* replace afread to use memory range */
  124. rdr.data = argv[1];
  125. rdr.len = strlen(rdr.data);
  126. rdr.cur = 0;
  127. /* agmemread() is broken for our use because it replaces the id disc */
  128. g = agread(&rdr, (Agdisc_t *)clientData);
  129. if (!g) {
  130. Tcl_AppendResult(interp, "\nFailure to read string \"", argv[1], "\"",
  131. NULL);
  132. if (agerrors()) {
  133. Tcl_AppendResult(interp, " because of syntax errors.", NULL);
  134. }
  135. return TCL_ERROR;
  136. }
  137. if (agerrors()) {
  138. Tcl_AppendResult(interp, "\nSyntax errors in string \"", argv[1], " \"",
  139. NULL);
  140. return TCL_ERROR;
  141. }
  142. Tcl_AppendResult(interp, obj2cmd(g), NULL);
  143. return TCL_OK;
  144. }
  145. int Tcldot_Init(Tcl_Interp *interp);
  146. int Tcldot_Init(Tcl_Interp *interp) {
  147. ictx_t *ictx = calloc(1, sizeof(ictx_t));
  148. if (!ictx)
  149. return TCL_ERROR;
  150. ictx->interp = interp;
  151. /* build disciplines dynamically so we can selectively replace functions */
  152. ictx->myioDisc.afread =
  153. NULL; /* set in dotread() or dotstring() according to need */
  154. ictx->myioDisc.putstr = AgIoDisc.putstr; /* no change */
  155. ictx->myioDisc.flush = AgIoDisc.flush; /* no change */
  156. ictx->mydisc.id = &myiddisc; /* complete replacement */
  157. ictx->mydisc.io = &(ictx->myioDisc); /* change parts */
  158. ictx->ctr = 1; /* init to first odd number, increment by 2 */
  159. #ifdef USE_TCL_STUBS
  160. if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
  161. return TCL_ERROR;
  162. }
  163. #else
  164. if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 0) == NULL) {
  165. return TCL_ERROR;
  166. }
  167. #endif
  168. // inter-release Graphviz versions have a number including '~dev.' that does
  169. // not comply with TCL version number rules, so replace this with 'b'
  170. char adjusted_version[sizeof(PACKAGE_VERSION)] = PACKAGE_VERSION;
  171. char *tilde_dev = strstr(adjusted_version, "~dev.");
  172. if (tilde_dev != NULL) {
  173. *tilde_dev = 'b';
  174. memmove(tilde_dev + 1, tilde_dev + strlen("~dev."),
  175. strlen(tilde_dev + strlen("~dev.")) + 1);
  176. }
  177. if (Tcl_PkgProvide(interp, "Tcldot", adjusted_version) != TCL_OK) {
  178. return TCL_ERROR;
  179. }
  180. #ifdef HAVE_LIBGD
  181. Gdtclft_Init(interp);
  182. #endif
  183. /* create a GraphViz Context and pass a pointer to it in clientdata */
  184. ictx->gvc = gvContextPlugins(lt_preloaded_symbols, DEMAND_LOADING);
  185. Tcl_CreateCommand(interp, "dotnew", dotnew, ictx, free);
  186. Tcl_CreateCommand(interp, "dotread", dotread, ictx, NULL);
  187. Tcl_CreateCommand(interp, "dotstring", dotstring, ictx, NULL);
  188. return TCL_OK;
  189. }
  190. int Tcldot_SafeInit(Tcl_Interp *interp);
  191. int Tcldot_SafeInit(Tcl_Interp *interp) { return Tcldot_Init(interp); }
  192. int Tcldot_builtin_Init(Tcl_Interp *interp);
  193. int Tcldot_builtin_Init(Tcl_Interp *interp) { return Tcldot_Init(interp); }