Graphviz 13.0.0~dev.20241220.2304
Loading...
Searching...
No Matches
tcldot.c
Go to the documentation of this file.
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
11#include "tcldot.h"
12#include <cgraph/rdr.h>
13#include <stdlib.h>
14#include <string.h>
15#include <tcl.h>
16#include <util/alloc.h>
17
18static int dotnew_internal(ClientData clientData, Tcl_Interp *interp, int argc,
19 char *argv[]) {
20 ictx_t *ictx = (ictx_t *)clientData;
21 Agraph_t *g;
22 int i;
23 Agdesc_t kind;
24
25 if ((argc < 2)) {
26 Tcl_AppendResult(
27 interp, "wrong # args: should be \"", argv[0],
28 " graphtype ?graphname? ?attributename attributevalue? ?...?\"", NULL);
29 return TCL_ERROR;
30 }
31 if (strcmp("digraph", argv[1]) == 0) {
32 kind = Agdirected;
33 } else if (strcmp("digraphstrict", argv[1]) == 0) {
34 kind = Agstrictdirected;
35 } else if (strcmp("graph", argv[1]) == 0) {
36 kind = Agundirected;
37 } else if (strcmp("graphstrict", argv[1]) == 0) {
38 kind = Agstrictundirected;
39 } else {
40 Tcl_AppendResult(interp, "bad graphtype \"", argv[1], "\": must be one of:",
41 "\n\tdigraph, digraphstrict, graph, graphstrict.", NULL);
42 return TCL_ERROR;
43 }
44 if (argc % 2) {
45 /* if odd number of args then argv[2] is name */
46 g = agopen(argv[2], kind, (Agdisc_t *)ictx);
47 i = 3;
48 } else {
49 /* else use handle as name */
50#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 4
51 char *name = gv_strdup(Tcl_GetStringResult(interp));
52 g = agopen(name, kind, (Agdisc_t *)ictx);
53 free(name);
54#else
55 g = agopen(Tcl_GetStringResult(interp), kind, (Agdisc_t *)ictx);
56#endif
57 i = 2;
58 }
59 if (!g) {
60 Tcl_AppendResult(interp, "\nFailure to open graph.", NULL);
61 return TCL_ERROR;
62 }
63 setgraphattributes(g, &argv[i], argc - i);
64 Tcl_AppendResult(interp, obj2cmd(g), NULL);
65
66 return TCL_OK;
67}
68
69static int dotnew(ClientData clientData, Tcl_Interp *interp, int argc,
70 const char *argv[]) {
71 char **argv_copy = tcldot_argv_dup(argc, argv);
72 int rc = dotnew_internal(clientData, interp, argc, argv_copy);
73 tcldot_argv_free(argc, argv_copy);
74 return rc;
75}
76
77static int dotread(ClientData clientData, Tcl_Interp *interp, int argc,
78 const char *argv[]) {
79 Agraph_t *g;
80 Tcl_Channel channel;
81 int mode;
82 ictx_t *ictx = (ictx_t *)clientData;
83
84 ictx->myioDisc.afread =
85 myiodisc_afread; /* replace afread to use Tcl Channels */
86
87 if (argc < 2) {
88 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
89 " fileHandle\"", NULL);
90 return TCL_ERROR;
91 }
92 channel = Tcl_GetChannel(interp, argv[1], &mode);
93 if (channel == NULL || !(mode & TCL_READABLE)) {
94 Tcl_AppendResult(interp, "\nChannel \"", argv[1], "\"", "is unreadable.",
95 NULL);
96 return TCL_ERROR;
97 }
98 /*
99 * read a graph from the channel, the channel is left open
100 * ready to read the first line after the last line of
101 * a properly parsed graph. If the graph doesn't parse
102 * during reading then the channel will be left at EOF
103 */
104 g = agread((FILE *)channel, (Agdisc_t *)clientData);
105 if (!g) {
106 Tcl_AppendResult(interp, "\nFailure to read graph \"", argv[1], "\"", NULL);
107 if (agerrors()) {
108 Tcl_AppendResult(interp, " because of syntax errors.", NULL);
109 }
110 return TCL_ERROR;
111 }
112 if (agerrors()) {
113 Tcl_AppendResult(interp, "\nSyntax errors in file \"", argv[1], " \"",
114 NULL);
115 return TCL_ERROR;
116 }
117 Tcl_AppendResult(interp, obj2cmd(g), NULL);
118 return TCL_OK;
119}
120
121static int dotstring(ClientData clientData, Tcl_Interp *interp, int argc,
122 const char *argv[]) {
123 Agraph_t *g;
124 ictx_t *ictx = (ictx_t *)clientData;
125 rdr_t rdr;
126
127 if (argc < 2) {
128 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " string\"",
129 NULL);
130 return TCL_ERROR;
131 }
132
133 ictx->myioDisc.afread =
134 myiodisc_memiofread; /* replace afread to use memory range */
135 rdr.data = argv[1];
136 rdr.len = strlen(rdr.data);
137 rdr.cur = 0;
138
139 /* agmemread() is broken for our use because it replaces the id disc */
140 g = agread(&rdr, (Agdisc_t *)clientData);
141 if (!g) {
142 Tcl_AppendResult(interp, "\nFailure to read string \"", argv[1], "\"",
143 NULL);
144 if (agerrors()) {
145 Tcl_AppendResult(interp, " because of syntax errors.", NULL);
146 }
147 return TCL_ERROR;
148 }
149 if (agerrors()) {
150 Tcl_AppendResult(interp, "\nSyntax errors in string \"", argv[1], " \"",
151 NULL);
152 return TCL_ERROR;
153 }
154 Tcl_AppendResult(interp, obj2cmd(g), NULL);
155 return TCL_OK;
156}
157
158int Tcldot_Init(Tcl_Interp *interp);
159int Tcldot_Init(Tcl_Interp *interp) {
160 ictx_t *ictx = calloc(1, sizeof(ictx_t));
161 if (!ictx)
162 return TCL_ERROR;
163
164 ictx->interp = interp;
165 /* build disciplines dynamically so we can selectively replace functions */
166
167 ictx->myioDisc.afread =
168 NULL; /* set in dotread() or dotstring() according to need */
169 ictx->myioDisc.putstr = AgIoDisc.putstr; /* no change */
170 ictx->myioDisc.flush = AgIoDisc.flush; /* no change */
171
172 ictx->mydisc.id = &myiddisc; /* complete replacement */
173 ictx->mydisc.io = &(ictx->myioDisc); /* change parts */
174
175 ictx->ctr = 1; /* init to first odd number, increment by 2 */
176
177#ifdef USE_TCL_STUBS
178 if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
179 return TCL_ERROR;
180 }
181#else
182 if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 0) == NULL) {
183 return TCL_ERROR;
184 }
185#endif
186 // inter-release Graphviz versions have a number including '~dev.' that does
187 // not comply with TCL version number rules, so replace this with 'b'
188 char adjusted_version[sizeof(PACKAGE_VERSION)] = PACKAGE_VERSION;
189 char *tilde_dev = strstr(adjusted_version, "~dev.");
190 if (tilde_dev != NULL) {
191 *tilde_dev = 'b';
192 memmove(tilde_dev + 1, tilde_dev + strlen("~dev."),
193 strlen(tilde_dev + strlen("~dev.")) + 1);
194 }
195 if (Tcl_PkgProvide(interp, "Tcldot", adjusted_version) != TCL_OK) {
196 return TCL_ERROR;
197 }
198
199#ifdef HAVE_LIBGD
200 Gdtclft_Init(interp);
201#endif
202
203 /* create a GraphViz Context and pass a pointer to it in clientdata */
204 ictx->gvc = gvContextPlugins(lt_preloaded_symbols, DEMAND_LOADING);
205
206 Tcl_CreateCommand(interp, "dotnew", dotnew, ictx, free);
207 Tcl_CreateCommand(interp, "dotread", dotread, ictx, NULL);
208 Tcl_CreateCommand(interp, "dotstring", dotstring, ictx, NULL);
209
210 return TCL_OK;
211}
212
213int Tcldot_SafeInit(Tcl_Interp *interp);
214int Tcldot_SafeInit(Tcl_Interp *interp) { return Tcldot_Init(interp); }
215
216int Tcldot_builtin_Init(Tcl_Interp *interp);
217int Tcldot_builtin_Init(Tcl_Interp *interp) { return Tcldot_Init(interp); }
Memory allocation wrappers that exit on failure.
static char * gv_strdup(const char *original)
Definition alloc.h:101
mode
Definition cvtgxl.c:33
lt_symlist_t lt_preloaded_symbols[]
Tcl_AppInitProc Gdtclft_Init
Definition gdtclft.c:41
void free(void *)
node NULL
Definition grammar.y:163
Agiodisc_t AgIoDisc
Definition io.c:39
int agerrors(void)
Definition agerror.c:181
Agdesc_t Agundirected
undirected
Definition graph.c:286
Agdesc_t Agstrictundirected
strict undirected
Definition graph.c:287
Agdesc_t Agstrictdirected
strict directed. A strict graph cannot have multi-edges or self-arcs.
Definition graph.c:285
Agraph_t * agopen(char *name, Agdesc_t desc, Agdisc_t *disc)
creates a new graph with the given name and kind
Definition graph.c:44
Agraph_t * agread(void *chan, Agdisc_t *disc)
constructs a new graph
Definition grammar.c:2292
Agdesc_t Agdirected
directed
Definition graph.c:284
GVC_t * gvContextPlugins(const lt_symlist_t *builtins, int demand_loading)
Definition gvc.c:35
graph descriptor
Definition cgraph.h:284
user's discipline
Definition cgraph.h:337
Agiddisc_t * id
Definition cgraph.h:338
Agiodisc_t * io
Definition cgraph.h:339
int(* afread)(void *chan, char *buf, int bufsize)
Definition cgraph.h:328
int(* flush)(void *chan)
Definition cgraph.h:330
int(* putstr)(void *chan, const char *str)
Definition cgraph.h:329
graph or subgraph
Definition cgraph.h:425
Tcl_Interp * interp
Definition tcldot.h:29
uint64_t ctr
Definition tcldot.h:28
Agdisc_t mydisc
Definition tcldot.h:26
Agiodisc_t myioDisc
Definition tcldot.h:27
GVC_t * gvc
Definition tcldot.h:30
Definition rdr.h:10
size_t cur
Definition rdr.h:13
const char * data
Definition rdr.h:11
size_t len
Definition rdr.h:12
Agiddisc_t myiddisc
Definition tcldot-id.c:97
int myiodisc_memiofread(void *chan, char *buf, int bufsize)
Definition tcldot-io.c:88
int myiodisc_afread(void *channel, char *ubuf, int n)
Definition tcldot-io.c:31
void setgraphattributes(Agraph_t *g, char *argv[], int argc)
char * obj2cmd(void *obj)
Definition tcldot-util.c:66
void tcldot_argv_free(int argc, char *argv[])
free the strings pointed to by argv
char ** tcldot_argv_dup(int argc, const char *argv[])
duplicate the strings pointed to by argv as non-const strings
int Tcldot_SafeInit(Tcl_Interp *interp)
Definition tcldot.c:214
int Tcldot_Init(Tcl_Interp *interp)
Definition tcldot.c:159
int Tcldot_builtin_Init(Tcl_Interp *interp)
Definition tcldot.c:217
static int dotstring(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
Definition tcldot.c:121
static int dotnew_internal(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
Definition tcldot.c:18
static int dotread(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
Definition tcldot.c:77
static int dotnew(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
Definition tcldot.c:69
#define Tcl_GetStringResult(interp)
Definition tclpathplan.c:43