1 /*
2 * tvheadend, documenation markdown generator
3 * Copyright (C) 2016 Jaroslav Kysela
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "tvheadend.h"
20 #include "config.h"
21 #include "webui.h"
22 #include "http.h"
23 #include "docs.h"
24
25 static int md_doc(htsbuf_queue_t *hq, const char **doc, const char *lang, int nl);
26 static int md_class(htsbuf_queue_t *hq, const char *clazz, const char *lang,
27 int hdr, int docs, int props);
28
29 /* */
30 static int
md_nl(htsbuf_queue_t * hq,int nl)31 md_nl(htsbuf_queue_t *hq, int nl)
32 {
33 if (nl)
34 htsbuf_append(hq, "\n", 1);
35 return 1;
36 }
37
38 /* */
39 static void
md_header(htsbuf_queue_t * hq,const char * prefix,const char * s)40 md_header(htsbuf_queue_t *hq, const char *prefix, const char *s)
41 {
42 htsbuf_append_str(hq, prefix);
43 htsbuf_append_str(hq, s);
44 htsbuf_append(hq, "\n", 1);
45 }
46
47 /* */
48 static void
md_style(htsbuf_queue_t * hq,const char * style,const char * s)49 md_style(htsbuf_queue_t *hq, const char *style, const char *s)
50 {
51 size_t l = strlen(style);
52 htsbuf_append(hq, style, l);
53 htsbuf_append_str(hq, s);
54 htsbuf_append(hq, style, l);
55 }
56
57 /* */
58 static void
md_text(htsbuf_queue_t * hq,const char * first,const char * next,const char * text)59 md_text(htsbuf_queue_t *hq, const char *first, const char *next, const char *text)
60 {
61 char *s, *t, *p;
62 int col, nl;
63
64 t = s = p = tvh_strdupa(text);
65 col = nl = 0;
66 while (*s) {
67 if (++col > 74) {
68 nl = md_nl(hq, nl);
69 if (first) {
70 htsbuf_append_str(hq, first);
71 first = NULL;
72 } else if (next) {
73 htsbuf_append_str(hq, next);
74 }
75 if (p <= t)
76 p = t + 74;
77 htsbuf_append(hq, t, p - t);
78 col = 0;
79 t = s = p;
80 } else if (*s <= ' ') {
81 *s = ' ';
82 p = ++s;
83 } else {
84 s++;
85 }
86 }
87 if (t < s) {
88 md_nl(hq, nl);
89 if (first)
90 htsbuf_append_str(hq, first);
91 else if (next)
92 htsbuf_append_str(hq, next);
93 htsbuf_append(hq, t, s - t);
94 }
95 }
96
97 /* */
98 static int
md_props(htsbuf_queue_t * hq,htsmsg_t * m,const char * lang,int nl)99 md_props(htsbuf_queue_t *hq, htsmsg_t *m, const char *lang, int nl)
100 {
101 htsmsg_t *l, *n, *e, *x;
102 htsmsg_field_t *f, *f2;
103 const char *s;
104 int first = 1, b;
105
106 l = htsmsg_get_list(m, "props");
107 if (l == NULL)
108 return nl;
109 HTSMSG_FOREACH(f, l) {
110 n = htsmsg_field_get_map(f);
111 if (!n) continue;
112 if (!htsmsg_get_bool(n, "noui", &b) && b) continue;
113 s = htsmsg_get_str(n, "caption");
114 if (!s) continue;
115 if (first) {
116 nl = md_nl(hq, nl);
117 htsbuf_append_str(hq, "#### ");
118 htsbuf_append_str(hq, tvh_gettext_lang(lang, N_("Items")));
119 md_nl(hq, 1);
120 md_nl(hq, 1);
121 htsbuf_append_str(hq, tvh_gettext_lang(lang, N_("The items have the following functions:")));
122 md_nl(hq, 1);
123 first = 0;
124 }
125 nl = md_nl(hq, nl);
126 md_style(hq, "**", s);
127 if (!htsmsg_get_bool(n, "rdonly", &b) && b) {
128 htsbuf_append(hq, " _", 2);
129 htsbuf_append_str(hq, tvh_gettext_lang(lang, N_("(Read-only)")));
130 htsbuf_append(hq, "_", 1);
131 }
132 md_nl(hq, 1);
133 s = htsmsg_get_str(n, "description");
134 if (s) {
135 md_text(hq, ": ", " ", s);
136 md_nl(hq, 1);
137 }
138 s = htsmsg_get_str(n, "doc");
139 if (s) {
140 htsbuf_append_str(hq, s);
141 md_nl(hq, 1);
142 }
143 if (!htsmsg_get_bool_or_default(n, "doc_nlist", 0)) {
144 e = htsmsg_get_list(n, "enum");
145 if (e) {
146 HTSMSG_FOREACH(f2, e) {
147 x = htsmsg_field_get_map(f2);
148 if (x) {
149 s = htsmsg_get_str(x, "val");
150 } else {
151 s = htsmsg_field_get_string(f2);
152 }
153 if (s) {
154 md_nl(hq, 1);
155 htsbuf_append(hq, " * ", 4);
156 md_style(hq, "**", s);
157 }
158 }
159 md_nl(hq, 1);
160 }
161 }
162 }
163 return nl;
164 }
165
166 /* */
167 static void
md_render(htsbuf_queue_t * hq,const char * doc,const char * lang)168 md_render(htsbuf_queue_t *hq, const char *doc, const char *lang)
169 {
170 const struct tvh_doc_page *page;
171 if (doc[0] == '\xff') {
172 switch (doc[1]) {
173 case 1:
174 htsbuf_append_str(hq, tvh_gettext_lang(lang, doc + 2));
175 break;
176 case 2:
177 md_class(hq, doc + 2, lang, 0, 1, 0);
178 break;
179 case 3:
180 md_class(hq, doc + 2, lang, 0, 0, 1);
181 break;
182 case 4:
183 for (page = tvh_doc_markdown_pages; page->name; page++)
184 if (!strcmp(page->name, doc + 2)) {
185 if (page->strings)
186 md_doc(hq, page->strings, lang, 0);
187 break;
188 }
189 break;
190 }
191 } else {
192 htsbuf_append_str(hq, doc);
193 }
194 }
195
196 /* */
197 static int
md_doc(htsbuf_queue_t * hq,const char ** doc,const char * lang,int nl)198 md_doc(htsbuf_queue_t *hq, const char **doc, const char *lang, int nl)
199 {
200 if (doc == NULL)
201 return nl;
202 for (; *doc; doc++) {
203 md_render(hq, *doc, lang);
204 nl = 1;
205 }
206 return nl;
207 }
208
209 /* */
210 static int
md_class(htsbuf_queue_t * hq,const char * clazz,const char * lang,int hdr,int docs,int props)211 md_class(htsbuf_queue_t *hq, const char *clazz, const char *lang,
212 int hdr, int docs, int props)
213 {
214 const idclass_t *ic;
215 htsmsg_t *m;
216 const char *s, **doc;
217 int nl = 0;
218
219 pthread_mutex_lock(&global_lock);
220 ic = idclass_find(clazz);
221 if (ic == NULL) {
222 pthread_mutex_unlock(&global_lock);
223 return HTTP_STATUS_NOT_FOUND;
224 }
225 doc = idclass_get_doc(ic);
226 m = idclass_serializedoc(ic, lang);
227 pthread_mutex_unlock(&global_lock);
228 if (hdr) {
229 s = htsmsg_get_str(m, "caption");
230 if (s) {
231 md_header(hq, "## ", s);
232 nl = md_nl(hq, 1);
233 }
234 }
235 if (docs)
236 nl = md_doc(hq, doc, lang, nl);
237 if (props)
238 nl = md_props(hq, m, lang, nl);
239 htsmsg_destroy(m);
240 return 0;
241 }
242
243 /**
244 * List of all classes with documentation
245 */
246 static int
http_markdown_classes(http_connection_t * hc)247 http_markdown_classes(http_connection_t *hc)
248 {
249 idclass_t const **all, **all2;
250 const idclass_t *ic;
251 htsbuf_queue_t *hq = &hc->hc_reply;
252
253 pthread_mutex_lock(&global_lock);
254 all = idclass_find_all();
255 if (all == NULL) {
256 pthread_mutex_unlock(&global_lock);
257 return HTTP_STATUS_NOT_FOUND;
258 }
259 for (all2 = all; *all2; all2++) {
260 ic = *all2;
261 if (ic->ic_caption) {
262 htsbuf_append_str(hq, ic->ic_class);
263 htsbuf_append(hq, "\n", 1);
264 }
265 }
266 pthread_mutex_unlock(&global_lock);
267
268 free(all);
269 return 0;
270 }
271
272 /**
273 *
274 */
275 static int
http_markdown_class(http_connection_t * hc,const char * clazz)276 http_markdown_class(http_connection_t *hc, const char *clazz)
277 {
278 const char *lang = hc->hc_access->aa_lang_ui;
279 htsbuf_queue_t *hq = &hc->hc_reply;
280
281 return md_class(hq, clazz, lang, 1, 1, 1);
282 }
283
284 /**
285 *
286 */
287 static int
http_markdown_page(http_connection_t * hc,const struct tvh_doc_page * page)288 http_markdown_page(http_connection_t *hc, const struct tvh_doc_page *page)
289 {
290 const char **doc = page->strings;
291 const char *lang = hc->hc_access->aa_lang_ui;
292 htsbuf_queue_t *hq = &hc->hc_reply;
293
294 if (doc == NULL)
295 return HTTP_STATUS_NOT_FOUND;
296 md_doc(hq, doc, lang, 0);
297 return 0;
298 }
299
300 /**
301 * Handle requests for markdown export.
302 */
303 int
page_markdown(http_connection_t * hc,const char * remain,void * opaque)304 page_markdown(http_connection_t *hc, const char *remain, void *opaque)
305 {
306 const struct tvh_doc_page *page;
307 char *components[2];
308 int nc, r;
309
310 nc = http_tokenize((char *)remain, components, 2, '/');
311 if (!nc)
312 return HTTP_STATUS_BAD_REQUEST;
313
314 if (nc == 2)
315 http_deescape(components[1]);
316
317 if (nc == 1 && !strcmp(components[0], "classes"))
318 r = http_markdown_classes(hc);
319 else if (nc == 2 && !strcmp(components[0], "class"))
320 r = http_markdown_class(hc, components[1]);
321 else if (nc == 1) {
322 for (page = tvh_doc_markdown_pages; page->name; page++)
323 if (!strcmp(page->name, components[0])) {
324 r = http_markdown_page(hc, page);
325 goto done;
326 }
327 r = HTTP_STATUS_BAD_REQUEST;
328 } else {
329 r = HTTP_STATUS_BAD_REQUEST;
330 }
331
332 done:
333 if (r == 0)
334 http_output_content(hc, "text/markdown");
335
336 return r;
337 }
338