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