]> git.neil.brown.name Git - edlib.git/blob - lib-config.c
TODO: clean out done items.
[edlib.git] / lib-config.c
1 /*
2  * Copyright Neil Brown ©2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Read an "ini" config file and set some attributes.
6  * Sections:
7  *   global - set attr on editor
8  *   module - set trigger to load module
9  *   file:glob - set attributes when matching file visited
10  *
11  * When not in a section, or in the "include" section, include= will
12  * load another file.
13  *
14  */
15
16 #include <unistd.h>
17 #include <stdlib.h>
18 #include <fcntl.h>
19 #include "core.h"
20 #include "parse-ini.h"
21
22 static void load_config(const char *path safe, void *data);
23
24 static bool _glob_match(const char *patn safe, const char *path safe)
25 {
26         while(1) {
27                 switch (*patn) {
28                 case '\0':
29                         return *path == '\0';
30                 case '?':
31                         if (!*path || *path == '/')
32                                 return False;
33                         patn += 1;
34                         path += 1;
35                         break;
36                 case '*':
37                         if (patn[1] == '*') {
38                                 if (_glob_match(patn+2, path))
39                                         return True;
40                         } else {
41                                 if (_glob_match(patn+1, path))
42                                         return True;
43                                 if (*path == '/')
44                                         return False;
45                         }
46                         if (!*path)
47                                 return False;
48                         path += 1;
49                         break;
50                 default:
51                         if (*patn != *path)
52                                 return False;
53                         patn += 1;
54                         path += 1;
55                         break;
56                 }
57         }
58 }
59
60 static bool glob_match(const char *patn safe, const char *path safe)
61 {
62         int ret;
63         if (patn[0] != '/' && !strstarts(patn, "**")) {
64                 /* must match basename */
65                 const char *sl = strrchr(path, '/');
66                 if (sl)
67                         path = sl + 1;
68         }
69         ret = _glob_match(patn, path);
70         return ret;
71 }
72
73 struct config_data {
74         struct command c;
75         struct command appeared;
76         struct pane *root safe;
77         struct trigger {
78                 char *path safe;
79                 enum {
80                         TRIGGER_FILE,
81                         TRIGGER_DOC,
82                 } type;
83                 struct attrset *attrs;
84                 struct trigger *next;
85         } *triggers, *last_trigger;
86 };
87
88 static void add_trigger(struct config_data *cd safe, unsigned int type,
89                         char *path safe,
90                         char *name safe, char *val safe, int append)
91 {
92         struct trigger *t = cd->last_trigger;
93
94         if (!t || strcmp(t->path, path) != 0 || t->type != type) {
95                 alloc(t, pane);
96                 t->path = strdup(path);
97                 t->next = NULL;
98                 t->type = type;
99                 if (cd->last_trigger)
100                         cd->last_trigger->next = t;
101                 else
102                         cd->triggers = t;
103                 cd->last_trigger = t;
104         }
105         if (append) {
106                 const char *old = attr_find(t->attrs, name);
107                 if (old) {
108                         val = strconcat(NULL, old, val);
109                         attr_set_str(&t->attrs, name, val);
110                         free(val);
111                 } else
112                         attr_set_str(&t->attrs, name, val);
113         } else
114                 attr_set_str(&t->attrs, name, val);
115 }
116
117 static void config_file(char *path safe, unsigned int type,
118                         struct pane *doc safe,
119                         struct config_data *cd safe)
120 {
121         struct trigger *t;
122
123         for (t = cd->triggers; t; t = t->next)
124                 if (t->type == type && glob_match(t->path, path)) {
125                         const char *val;
126                         const char *k = "";
127                         while ((k = attr_get_next_key(t->attrs, k, -1, &val)) != NULL) {
128                                 if (strstarts(k, "APPEND "))
129                                         call("doc:append:", doc, 0, NULL, val,
130                                              0, NULL, k + 7);
131                                 else
132                                         call("doc:set:", doc, 0, NULL, val,
133                                              0, NULL, k);
134                         }
135                 }
136 }
137
138 struct mod_cmd {
139         char *module;
140         int tried;
141         struct command c;
142 };
143
144 DEF_CB(autoload)
145 {
146         struct mod_cmd *mc = container_of(ci->comm, struct mod_cmd, c);
147
148         if (mc->tried)
149                 return Efallthrough;
150         mc->tried = 1;
151
152         /* NOTE: this might free mc, so don't touch it again */
153         call("global-load-module", ci->home, 0, NULL, mc->module);
154         return home_call(ci->home, ci->key, ci->focus,
155                          ci->num, ci->mark, ci->str,
156                          ci->num2, ci->mark2, ci->str2,
157                          ci->x, ci->y, ci->comm2);
158 }
159
160 static void al_free(struct command *c safe)
161 {
162         struct mod_cmd *mc = container_of(c, struct mod_cmd, c);
163
164         free(mc->module);
165         free(mc);
166 }
167
168 static void handle(void *data, char *section safe, char *name safe, char *value safe,
169                    const char *path safe, int append)
170 {
171         struct config_data *cd;
172
173         if (!data)
174                 return;
175         cd = data;
176
177         if (strstarts(name, "TESTING ")) {
178                 if (!edlib_testing(cd->root))
179                         return;
180                 name += 8;
181         }
182         if (strstarts(name, "NOTESTING ")) {
183                 if (edlib_testing(cd->root))
184                         return;
185                 name += 10;
186         }
187         if (strcmp(section, "") == 0 || strcmp(section,"include") == 0) {
188                 if (strcmp(name, "include") == 0) {
189                         load_config(value, data);
190                         return;
191                 }
192                 return;
193         }
194
195         if (strcmp(section, "global") == 0) {
196                 call("global-set-attr", cd->root, append, NULL, name,
197                      0, NULL, value);
198                 return;
199         }
200
201         if (strcmp(section, "module") == 0 && value[0]) {
202                 struct mod_cmd *mc;
203
204                 mc = malloc(sizeof(*mc));
205                 mc->module = strdup(name);
206                 mc->tried = 0;
207                 mc->c = autoload;
208                 mc->c.free = al_free;
209                 if (strstarts(value, "PREFIX "))
210                         call_comm("global-set-command-prefix", cd->root, &mc->c, 0, NULL,
211                                   value + 7);
212                 else
213                         call_comm("global-set-command", cd->root, &mc->c, 0, NULL,
214                                   value);
215                 return;
216         }
217
218         if (strstarts(section, "file:")) {
219                 add_trigger(cd, TRIGGER_FILE, section+5, name, value, append);
220                 return;
221         }
222         if (strstarts(section, "doc:")) {
223                 add_trigger(cd, TRIGGER_DOC, section+4, name, value, append);
224                 return;
225         }
226 }
227
228 static void load_config(const char *path safe, void *data)
229 {
230         char *p;
231         struct config_data *cd = data;
232
233         if (*path == '/') {
234                 parse_ini(path, handle, data);
235                 return;
236         }
237         /*
238          * Relative paths can be loaded using xdg-find-edlib-file data
239          */
240         p = call_ret(str, "xdg-find-edlib-file", cd->root, 0, NULL,
241                      path, 0, NULL, "config");
242         if (p && access(p, F_OK) == 0)
243                 parse_ini(p, handle, data);
244         free(p);
245 }
246
247 static void config_free(struct command *c safe)
248 {
249         struct config_data *cd = container_of(c, struct config_data, c);
250         struct trigger *t;
251
252         while ((t = cd->triggers) != NULL) {
253                 cd->triggers = t->next;
254                 free(t->path);
255                 attr_free(&t->attrs);
256                 free(t);
257         }
258         free(cd);
259 }
260
261 DEF_CMD(config_appeared)
262 {
263         struct config_data *cd = container_of(ci->comm, struct config_data, appeared);
264         char *path = pane_attr_get(ci->focus, "filename");
265         if (path) {
266                 config_file(path, TRIGGER_FILE, ci->focus, cd);
267                 return Efallthrough;
268         }
269         path = pane_attr_get(ci->focus, "doc-name");
270         if (path) {
271                 config_file(path, TRIGGER_DOC, ci->focus, cd);
272                 return Efallthrough;
273         }
274         return Efallthrough;
275 }
276
277 DEF_CMD(config_load)
278 {
279         struct config_data *cd;
280         if (ci->comm == &config_load) {
281                 /* This is the first call - need to allocate storage
282                  * and register a new command.
283                  */
284                 alloc(cd, pane);
285                 cd->c = config_load;
286                 cd->c.free = config_free;
287                 cd->appeared = config_appeared;
288                 cd->root = ci->home;
289                 call_comm("global-set-command", ci->home, &cd->c, 0, NULL, "config-load");
290                 call_comm("global-set-command", ci->home, &cd->appeared,
291                           0, NULL, "doc:appeared-config");
292         } else {
293                 cd = container_of(ci->comm, struct config_data, c);
294         }
295         if (ci->str)
296                 load_config(ci->str, cd);
297         return 1;
298 }
299
300 void edlib_init(struct pane *ed safe)
301 {
302         call_comm("global-set-command", ed, &config_load,
303                   0, NULL, "config-load");
304 }