]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
markdown doc: more work; added support for other doc pages and TOC
authorJaroslav Kysela <perex@perex.cz>
Sun, 3 Apr 2016 11:08:41 +0000 (13:08 +0200)
committerJaroslav Kysela <perex@perex.cz>
Sun, 3 Apr 2016 11:24:44 +0000 (13:24 +0200)
13 files changed:
.gitignore
Makefile
docs/markdown/configure_tvheadend.md [new file with mode: 0644]
docs/markdown/index.md [new file with mode: 0644]
docs/markdown/toc.md [new file with mode: 0644]
src/docs.c
src/webui/doc_md.c
src/webui/extjs.c
src/webui/static/app/ext.css
src/webui/static/app/micromarkdown.js
src/webui/static/app/tvheadend.js
src/webui/static/img/logobig.png [moved from docs/docresources/tvheadendlogo.png with 100% similarity]
support/doc/md_to_c.py

index c262d6aac604b3c2fa5220fd75479985ee8346a0..59c2cf18b30063d95da48f9143b423903e4d349e 100644 (file)
@@ -3,6 +3,7 @@ build.*
 
 src/version.c
 src/tvh_locale_inc.c
+src/docs_inc.[ch]
 src/webui/extjs-std.c
 src/webui/extjs-debug.c
 src/webui/extjs-tv-std.c
index 0e7458616cd7bcf4c4a91c877215cb15cdfaab41..4d91463f2f87c3415748a63ece67543e63c1557a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -541,9 +541,11 @@ ALL-$(CONFIG_DVBSCAN)     += check_dvb_scan
 # Documentation
 #
 
-MD-CLASS = $(patsubst docs/class/%.md,%,$(wildcard docs/class/*.md))
 SRCS-yes += src/docs.c
 I18N-C   += src/docs_inc.c
+I18N-DOCS = $(wildcard docs/markdown/*.md) $(wildcard docs/class/*.md)
+MD-ROOT   = $(patsubst docs/markdown/%.md,%,$(wildcard docs/markdown/*.md))
+MD-CLASS  = $(patsubst docs/class/%.md,%,$(wildcard docs/class/*.md))
 
 #
 # Internationalization
@@ -662,18 +664,30 @@ $(BUILDDIR)/build.o: $(BUILDDIR)/build.c
        $(pCC) -c -o $@ $<
 
 # Documentation
-$(BUILDDIR)/docs-timestamp: $(MD-FILES) support/doc/md_to_c.py
+$(BUILDDIR)/docs-timestamp: $(I18N-DOCS) support/doc/md_to_c.py
        @-rm -f src/docs_inc.c
+       @for i in $(MD-ROOT); do \
+          echo "Markdown: docs/markdown/$${i}.md"; \
+          support/doc/md_to_c.py --in="docs/markdown/$${i}.md" \
+                                 --name="tvh_doc_root_$${i}" >> src/docs_inc.c || exit 1; \
+        done
        @for i in $(MD-CLASS); do \
-          echo "Markdown class: $${i}"; \
+          echo "Markdown: docs/class/$${i}.md"; \
           support/doc/md_to_c.py --in="docs/class/$${i}.md" \
-                                 --name="tvh_doc_$${i}_class" >> src/docs_inc.c; \
+                                 --name="tvh_doc_$${i}_class" >> src/docs_inc.c || exit 1; \
+        done
+       @printf "\n\nconst struct tvh_doc_page tvh_doc_markdown_pages[] = {\n" >> src/docs_inc.c
+       @for i in $(MD-ROOT); do \
+          echo "  { \"$${i}\", tvh_doc_root_$${i} }," >> src/docs_inc.c || exit 1; \
         done
+       @echo "  { NULL, NULL }," >> src/docs_inc.c || exit 1
+       @echo "};" >> src/docs_inc.c
        @touch $@
 
 src/docs_inc.c: $(BUILDDIR)/docs-timestamp
+src/docs_inc.h: $(BUILDDIR)/docs-timestamp
 
-$(BUILDDIR)/src/docs.o: src/docs_inc.c
+$(BUILDDIR)/src/docs.o: src/docs_inc.c $(I18N-DOCS) support/doc/md_to_c.py
 
 # Internationalization
 .PHONY: intl
diff --git a/docs/markdown/configure_tvheadend.md b/docs/markdown/configure_tvheadend.md
new file mode 100644 (file)
index 0000000..029bb11
--- /dev/null
@@ -0,0 +1,150 @@
+##Configure Tvheadend
+
+This section gives a high-level overview of the steps needed to get Tvheadend
+up and running. For more detailed information, please consult the rest of
+this guide - much of it is arranged in the same order as the tabs on the
+Tvheadend interface so you know where to look.
+
+You can also consult the in-application help text, which mirrors this guide
+to a very great extent.
+
+###1. Ensure Tuners are Available for Use
+
+**Tvheadend web interface: _Configuration -> DVB Inputs -> TV Adapters_**
+
+On this tab, you'll see a tree structure, with the Linux device list at the
+top level (e.g. `/dev/dvb/adapter0`)
+
+Individual tuners are then the next level down (e.g. `DiBcom 7000PC : DVB-T #0`)
+
+Click on each tuner that you want Tvheadend to use, and ensure "Enabled"
+is checked in the 'Parameters' list
+
+If anything is obviously wrong at this point, you probably have a
+driver/firmware error which you'll need to resolve before going any further.
+
+###2. Set up Relevant Network(s)
+
+**Tvheadend web interface: _Configuration -> DVB Inputs -> Networks_**
+
+Create a network of the appropriate type here. You can have multiple networks
+of the same type as necessary, e.g. to have two DVB-T networks defined,
+one with HD muxes, one without.
+
+The creation process allows you to select from a series of pre-defined mux
+lists for common DVB sources. These are maintained outside of Tvheadend, and
+are downloaded from [linuxtv](http://git.linuxtv.org/cgit.cgi/dtv-scan-tables.git/) -
+but they do go out of date as broadcasters move services around and national
+authorities change entire pieces of spectrum. As such, you should try the
+pre-defined values, but you may need to add muxes manually.
+
+**TODO: Critical configuration items at this stage: ........**
+
+###3. Associate the Network with the Respective Tuner(s)
+
+**Tvheadend web interface: _Configuration -> DVB Inputs -> TV Adapters_**
+
+Associate each of your tuners with the correct network through _Parameters -> Basic Settings_. 
+
+This can be as simple or as complex as necessary. You may simply have, for
+example, a single DVB-S2 network defined and then associate this with all
+DVB-S2 tuners. Or, you might have multiple networks defined - different
+satellites, different encoding. So, as further examples, you might define
+and then associate an HD DVB-T2 (e.g. H.264) network with HD tuners, while
+having a separate SD network associated with an independent SD (e.g. MPEG-2)
+tuner. 
+
+**TODO: Critical configuration items at this stage: ........**
+
+At this point, your tuners now know what networks to use: one network can
+appear on multiple tuners (many-to-one), and one tuner can have multiple
+networks.
+
+### 4. If Necessary, Manually Add Muxes
+
+**Tvheadend web interface: _Configuration -> DVB Inputs -> Muxes_**
+
+Ideally, this is where you'll see a list of the pre-populated muxes as created
+when you set up your initial network. However, should there be any issues,
+this is where you can manually add missing muxes. You only really need to
+worry about this if the pre-defined list didn't work (e.g. because of
+out-of-date data as broadcasters re-arrange their services or because automatic
+detection hasn't successfully found all the muxes over time. 
+
+If you do need to add something manually, you'll need to search the Internet
+for details of the appropriate transmitter and settings: satellites tend not
+to change much and are universal over a large area, but terrestrial muxes
+are typically very localised and you'll need to know which specific transmitter
+you're listening to. 
+
+Good sources of transmitter/mux information include:
+
+* [KingofSat](http://en.kingofsat.net) for all European satellite information
+
+* [ukfree.tv](http://www.ukfree.tv/maps/freeview) for UK DVB-T transmitters
+
+* [Interactive EU DVB-T map](http://www.dvbtmap.eu/mapmux.html) for primarily
+central and northern Europe
+
+> other major sources....?
+##NOTE: TODO: TEXT REQUIRED
+
+You can also use [dvbscan](http://www.linuxtv.org/wiki/index.php/Dvbscan) to
+force a scan and effectively ask your tuner what it can see.
+**TODO: Critical configuration items at this stage: ........**
+
+### 5. Scan for Services
+
+**Tvheadend web interface: _Configuration -> DVB Inputs -> Services_**
+
+This is where the services will appear as your tuners tune to the muxes based
+on the network you told them to look on. Again, remember what's happening: 
+Tvheadend is telling your tuner hardware (via the drivers) to sequentially
+tune to each mux it knows about, and then see what 'programmes' it can see
+on that mux, each of which is identified by a series of unique identifiers
+that describe the audio stream(s), the video stream(s), the subtitle stream(s)
+and language(s), and so on.
+
+(For the technically-minded, these unique identifiers - the elementary streams
+- are referred to as 'packet identifiers' or 'PIDs').
+
+> To force a scan ...
+##NOTE: TODO: TEXT REQUIRED
+
+### 6. Map Services to Channels
+
+Once scanning for services is complete, you need to map the services to 
+channels so your client can actually request them (i.e. so you can watch
+or record). You can do this in two places:
+
+**Tvheadend web interface: _Configuration -> DVB Inputs -> Services_**
+
+Press the "Map All" button. Note the resultant dialog box that allows you
+to exclude some services from the mapping: this is covered in more detail
+later in this guide. 
+
+**Tvheadend web interface: _Configuration -> Channel/EPG -> Channels_**
+
+Press the "Map Services" button. Again, you can exclude services as you
+can for 'Map All', above.
+
+> Any explanation on how a channel can map to multiple services <......>
+
+> Anything about using bouquets
+##NOTE: TODO: TEXT REQUIRED
+
+### 7. Watch TV
+
+That's it - you're done. You should now have a working basic Tvheadend
+installation with channels mapped and ready for use!
+
+As required, you may now wish to look into:
+
+* Setting up different EPGs (inc. localised character sets and timing offsets)
+* Setting up channel icons
+* Setting up recording profiles
+* Setting up streaming profiles (including transcoding)
+* Arranging your channels into groups (channel tags)
+* Setting up softcams for descrambling
+* Setting up access control rules for different client types/permission levels
diff --git a/docs/markdown/index.md b/docs/markdown/index.md
new file mode 100644 (file)
index 0000000..600d4c2
--- /dev/null
@@ -0,0 +1,19 @@
+#Tvheadend 4.2 User Guide
+
+![Tvheadend Logo](images/logobig.png)
+
+#Purpose
+
+This document is intended to give you a high-level overview of how to set 
+up Tvheadend for the first time. It does not aim to provide a complete description
+of every step or answer every question: more details are available on the
+tvheadend [wiki](https://tvheadend.org/projects/tvheadend/wiki).
+
+As you set things up, consult the on-line (web interface) help as well.
+Tvheadend includes copies of many of these pages in the application, which
+is easier to find when you're wondering what to do next.
+
+If you get really stuck, there's the [forum](https://tvheadend.org/projects/tvheadend/boards)
+and IRC (*#hts* on *freenode*) - [Kiwi IRC](https://kiwiirc.com/) is a good web
+client if you don't already have an IRC client installed, please [Freenode](https://webchat.freenode.net/)
+has one of their own.
diff --git a/docs/markdown/toc.md b/docs/markdown/toc.md
new file mode 100644 (file)
index 0000000..bb0edb5
--- /dev/null
@@ -0,0 +1,19 @@
+####Table of Contents
+
+Introduction
+* Intro1
+
+Getting Started
+* [Configure Tvheadend](configure_tvheadend)
+
+Web Interface Guide
+
+* Access Configuration
+
+  - [Users](class/access)
+  - [Passwords](class/passwd)
+
+Appendices
+
+[About](index)
+
index 1138f8592a9eeee1dc9a64b481bd2e0038413ae6..3a7ac9a1210d96737977295d9cf6dbe5d46416ab 100644 (file)
@@ -1,5 +1,5 @@
 #include <unistd.h>
-//#include "tvh_locale.h"
+#include "docs.h"
 
 #define LANGPREF "\xff"
 #define N_(s) s
index 22d20ab397cc21f360502a0863adfa93c6a34fc6..2947839bf66dee59fcfa9db7348e5146b896362e 100644 (file)
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "webui.h"
 #include "http.h"
+#include "docs.h"
 
 /* */
 static int
@@ -201,12 +202,35 @@ http_markdown_class(http_connection_t *hc, const char *clazz)
   return 0;
 }
 
+/**
+ *
+ */
+static int
+http_markdown_page(http_connection_t *hc, const struct tvh_doc_page *page)
+{
+  const char **doc = page->strings;
+  const char *lang = hc->hc_access->aa_lang_ui;
+  htsbuf_queue_t *hq = &hc->hc_reply;
+
+  if (doc == NULL)
+    return HTTP_STATUS_NOT_FOUND;
+  for (; *doc; doc++) {
+    if (*doc[0] == '\xff') {
+      htsbuf_append_str(hq, tvh_gettext_lang(lang, *doc + 1));
+    } else {
+      htsbuf_append_str(hq, *doc);
+    }
+  }
+  return 0;
+}
+
 /**
  * Handle requests for markdown export.
  */
 int
 page_markdown(http_connection_t *hc, const char *remain, void *opaque)
 {
+  const struct tvh_doc_page *page;
   char *components[2];
   int nc, r;
 
@@ -221,9 +245,18 @@ page_markdown(http_connection_t *hc, const char *remain, void *opaque)
     r = http_markdown_classes(hc);
   else if (nc == 2 && !strcmp(components[0], "class"))
     r = http_markdown_class(hc, components[1]);
-  else
+  else if (nc == 1) {
+    for (page = tvh_doc_markdown_pages; page->name; page++)
+      if (!strcmp(page->name, components[0])) {
+        r = http_markdown_page(hc, page);
+        goto done;
+      }
     r = HTTP_STATUS_BAD_REQUEST;
+  } else {
+    r = HTTP_STATUS_BAD_REQUEST;
+  }
 
+done:
   if (r == 0)
     http_output_content(hc, "text/markdown");
 
index d691be6b24800072b154bb80fafce4a0867b338c..3fc8f6bdb11e65f9b4c59e14c147fddca9f04452 100644 (file)
@@ -196,7 +196,7 @@ page_about(http_connection_t *hc, const char *remain, void *opaque)
   htsbuf_qprintf(hq, "<center>\n\
 <div class=\"about-title\">HTS Tvheadend %s</div>\n\
 <p>&copy; 2006 - 2016 Andreas \303\226man, Jaroslav Kysela, Adam Sutton, et al.</p>\n\
-<p><img src=\"docresources/tvheadendlogo.png\"></p>\n\
+<p><img src=\"static/img/logobig.png\"></p>\n\
 <p><a href=\"https://tvheadend.org\">https://tvheadend.org</a></p>\n",
     tvheadend_version);
 
index 46b5e7b09dd0cdb89158676f0d38da3fce0aa5d3..0a262dd20ecf66c44c5ae59f9e928751b72d6d1c 100644 (file)
 /* Table styles for webUI help */
 
 .hts-doc-text table, .hts-doc-text th, .hts-doc-text td {
-  font-size: inherit;
-  border: 1px solid #98c0f4;
-  margin: 20px
+    font-size: inherit;
+    border: 1px solid #98c0f4;
+    margin: 20px
 }
 
 .hts-doc-text td,.hts-doc-text th {
-  padding: 5px
+    padding: 5px
 }
 
 .hts-doc-text table {
-  border-collapse: collapse
+    border-collapse: collapse
 }
 
 .hts-doc-text th {
-  text-align: center;
-  height: 2em;
-  background-color: #d9e8fb;
-  color: black;
-  font-weight: bold
+    text-align: center;
+    height: 2em;
+    background-color: #d9e8fb;
+    color: black;
+    font-weight: bold
 }
 
 .hts-doc-text tr:nth-child(even) {
-  background-color: #F2F7FE
+    background-color: #F2F7FE
 }
 
 /* Additional general text styles for webUI help */
     padding: 5px;
 }
 
-
 .hts-doc-text p {
-  margin-top: 1em;
-  margin-bottom: 1em;
-  margin-left: 1em
+    margin-top: 1em;
+    margin-bottom: 1em;
+    margin-left: 1em
 }
 
 .hts-doc-text h2, h3, h4 {
-  font-size: larger
+    font-size: larger
 }
 
 .hts-doc-text h4 {
-  color: #2163A6;
-  margin-top: 15px
+    color: #2163A6;
+    margin-top: 15px
 }
 
 .hts-doc-text strong {
-  font-weight: bold
+    font-weight: bold
 }
 
 .hts-doc-text em {
-  font-style: italic
+    font-style: italic
 }
 
 .hts-doc-text td img {
-  display: inline;
-  margin: -5px auto
+    display: inline;
+    margin: -5px auto
 }
 
 /* List styles for webUI help */
 }
 
 .hts-doc-text ul {
-  list-style-type:disc;
-  padding-left:3em
+    list-style-type:disc;
+    padding-left:3em
 }
 
 .hts-doc-text ul li {
-  padding-top: 0px;
-  padding-bottom: 0px
+    padding-top: 0px;
+    padding-bottom: 0px
+}
+
+/* Styles for help TOC */
+
+.hts-doc-toc {
+    width: 200px;
+    padding: 5px 5px 5px 5px;
+    float: right;
+}
+
+.hts-doc-toc a {
+    cursor: pointer;
+}
+
+.hts-doc-toc p {
+    margin: 3px 0 1px 5px;
+}
+
+.hts-doc-toc li {
+    padding-top: 5px;
+    padding-bottom: 5px;
+}
+
+.hts-doc-toc ul {
+    list-style-type: disc;
+    padding-left: 2em;
+}
+
+.hts-doc-toc ul ul {
+    list-style-type: circle;
+    padding-left: 1em;
+}
+
+.hts-doc-toc ul ul ul {
+    list-style-type: square;
+    padding-left: 1em;
+}
+
+.hts-doc-toc ul li {
+    padding-top: 0px;
+    padding-bottom: 0px
 }
 
 /* Additional image and caption styles for webUI help */
index 54e7bc020271f5ce3ba3e33322e60f8c99b4885c..92ef85f073a2f2354b824041b3e5331f278228b4 100644 (file)
@@ -51,7 +51,7 @@ var micromarkdown = {
     url2: /[ \t\n]([a-zA-Z]{2,16}:\/\/[a-zA-Z0-9@:%_\+.~#?&=]{2,256}.[a-z]{2,4}\b(\/[\-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)?)[ \t\n]/g
   },
   codeblocks: {},
-  parse: function (str, strict) {
+  parse: function (str, strict, linkfcn) {
     'use strict';
     var line, nstatus = 0,
       status, cel, calign, indent, helper, helper1, helper2, count, repstr, stra, trashgc = [],
@@ -207,9 +207,17 @@ var micromarkdown = {
     /* links */
     while ((stra = micromarkdown.regexobject.links.exec(str)) !== null) {
       if (stra[0].substr(0, 1) === '!') {
-        str = str.replace(stra[0], '<img src="' + stra[2] + '" alt="' + stra[1] + '" title="' + stra[1] + '" />\n');
+        var src = stra[2];
+        if (linkfcn && src.indexOf('://') === -1)
+          src = linkfcn('src', src);
+        str = str.replace(stra[0], '<img src="' + src + '" alt="' + stra[1] + '" title="' + stra[1] + '" />\n');
       } else {
-        str = str.replace(stra[0], '<a ' + micromarkdown.mmdCSSclass(stra[2], strict) + 'href="' + stra[2] + '">' + stra[1] + '</a>\n');
+        var href = stra[2];
+        if (linkfcn && href.indexOf('://') === -1)
+          href = linkfcn('href', href);
+        else if (href)
+          href = 'href="' + href + '"'
+        str = str.replace(stra[0], '<a ' + micromarkdown.mmdCSSclass(stra[2], strict) + href + '>' + stra[1] + '</a>\n');
       }
     }
     while ((stra = micromarkdown.regexobject.mail.exec(str)) !== null) {
@@ -402,11 +410,13 @@ var micromarkdown = {
     if ((str.indexOf('/') !== -1) && (strict !== true)) {
       urlTemp = str.split('/');
       if (urlTemp[1].length === 0) {
-        urlTemp = urlTemp[2].split('.');
+        if (urlTemp[2].indexOf('.') !== -1)
+          urlTemp = urlTemp[2].split('.');
       } else {
-        urlTemp = urlTemp[0].split('.');
+        if (urlTemp[0].indexOf('.') !== -1)
+          urlTemp = urlTemp[0].split('.');
       }
-      return 'class="mmd_' + urlTemp[urlTemp.length - 2].replace(/[^\w\d]/g, '') + urlTemp[urlTemp.length - 1] + '" ';
+      return 'class="tvh_mmd_' + urlTemp[urlTemp.length - 2].replace(/[^\w\d]/g, '') + urlTemp[urlTemp.length - 1] + '" ';
     }
     return '';
   }
index 71148e886cbabaeef71560ef7d6e86231eb55084..0d342659d2c4d99fa81fbf393bdb1833622def8f 100644 (file)
@@ -7,6 +7,7 @@ tvheadend.uilevel = 'expert';
 tvheadend.uilevel_nochange = false;
 tvheadend.quicktips = true;
 tvheadend.wizard = null;
+tvheadend.docs_toc = null;
 
 tvheadend.cookieProvider = new Ext.state.CookieProvider({
   // 7 days from now
@@ -87,42 +88,75 @@ tvheadend.help = function(title, pagename) {
                 items: [content]
             });
             win.show();
-
         }
     });
 };
 
 tvheadend.mdhelp = function(pagename) {
-    Ext.Ajax.request({
-        url: 'markdown/' + pagename,
-        success: function(result, request) {
 
-            var text = result.responseText;
-            var title = text.split('\n')[0].split('#');
-            
-            if (title)
-                title = title[title.length-1];
-            
-            text = '<div class="hts-doc-text">' + micromarkdown.parse(text) + '</div>';
+    var linkfcn = function(type, href) {
+        if (type == 'src') {
+            if (href.substring(0, 7) == 'images/')
+               return 'static/img' + href.substring(6);
+            return href;
+        } else {
+            return 'page="' + href + '"';
+        }
+    }
 
-            var content = new Ext.Panel({
-                autoScroll: true,
-                border: false,
-                layout: 'fit',
-                html: text
-            });
+    var fcn = function(result) {
+        var mdtext = result.responseText;
+        var title = mdtext.split('\n')[0].split('#');
+        
+        if (title)
+            title = title[title.length-1];
+        
+        var text = '<div>';
+        if (tvheadend.docs_toc)
+            text += '<div class="hts-doc-toc">' + tvheadend.docs_toc + '</div>';
+        text += '<div class="hts-doc-text">' + micromarkdown.parse(mdtext, 0, linkfcn) + '</div>';
+        text += '</div>';
 
-            var win = new Ext.Window({
-                title: _('Help for') + ' ' + title,
-                iconCls: 'help',
-                layout: 'fit',
-                width: 900,
-                height: 400,
-                constrainHeader: true,
-                items: [content]
-            });
-            win.show();
+        var content = new Ext.Panel({
+            autoScroll: true,
+            border: false,
+            layout: 'fit',
+            html: text
+        });
 
+        var win = new Ext.Window({
+            title: _('Help for') + ' ' + title,
+            iconCls: 'help',
+            layout: 'fit',
+            width: 900,
+            height: 400,
+            constrainHeader: true,
+            items: [content],
+            listeners: {
+                render: function(win) {
+                    win.body.on('click', function(e, dom) {
+                        tvheadend.mdhelp(dom.getAttribute('page'));
+                    });
+                }
+            },
+        });
+        win.show();
+    }
+
+    Ext.Ajax.request({
+        url: 'markdown/' + pagename,
+        success: function(result) {
+            if (!tvheadend.docs_toc) {
+                Ext.Ajax.request({
+                    url: 'markdown/toc',
+                    success: function(result_toc) {
+                        tvheadend.docs_toc = micromarkdown.parse(result_toc.responseText, 0, linkfcn);
+                        fcn(result);
+                    }
+                });
+            } else {
+                fcn(result);
+            }
         }
     });
 };
index 802b619360ac826e3d98ac7ff26102c02282188f..25593b83400a0f0a12c89726b0db1ea0f056710a 100755 (executable)
@@ -18,11 +18,17 @@ import sys
 from textwrap import wrap
 from mistune import Markdown, Renderer
 
+HUMAN=False
+DEBUG=False
+
 NOLANG=[
   '.',
   ','
 ]
 
+def debug(str):
+   sys.stderr.write('DEBUG: ' + str + '\n')
+
 class Object:
 
    pass
@@ -30,12 +36,18 @@ class Object:
 class TVH_C_Renderer(Renderer):
 
   def get_nolang(self, text):
-    return '"' + text + '",\n'
+    if HUMAN:
+      return text
+    else:
+      return '_' + str(len(text)) + ':' + text
     
   def get_lang(self, text):
     if text in NOLANG:
       return self.get_nolang(text)
-    return 'LANGPREF N_("' + text + '"),\n'
+    if HUMAN:
+      return text
+    else:
+      return 'x' + str(len(text)) + ':' + text
 
   def get_block(self, text):
     type = text[0]
@@ -47,53 +59,60 @@ class TVH_C_Renderer(Renderer):
     return (text[p+1+l:], type, t)
 
   def newline(self):
-    return self.get_nolang('\n')
+    if DEBUG: debug('newline')
+    return '\n'
 
   def text(self, text):
     if not text:
       return ''
-    pre = ''
-    post = ''
-    if ord(text[0]) <= ord(' '):
-      pre = self.get_nolang(' ')
-    if ord(text[-1]) <= ord(' '):
-      post = self.get_nolang(' ')
+    if DEBUG: debug('text: ' + repr(text))
     text = text.replace('\n', ' ')
-    text = ' \\\n'.join(wrap(text, 74))
-    return pre + self.get_lang(text) + post
+    return self.get_lang(text)
 
   def linebreak(self):
-    return self.get_nolang('\\n')
+    if DEBUG: debug('linebreak')
+    return '\n'
 
   def hrule(self):
-    return self.get_nolang('---\\n')
+    if DEBUG: debug('hrule')
+    return self.get_nolang('---') + '\n'
 
   def header(self, text, level, raw=None):
-    return self.get_nolang('#'*(level+1)) + \
-           text + \
-           self.get_nolang('\\n\\n')
+    if DEBUG: debug('header[%d]: ' % level + repr(text))
+    return '\n' + self.get_nolang('#'*(level+1)) + text + '\n'
 
   def paragraph(self, text):
-    return text + self.get_nolang('\\n\\n')
+    if DEBUG: debug('paragraph: ' + repr(text))
+    return '\n' + text + '\n'
 
   def list(self, text, ordered=True):
-    r = ''
+    r = '\n'
     while text:
       text, type, t = self.get_block(text)
+      if DEBUG: debug('list[' + type + ']: ' + repr(t))
       if type == 'l':
-        r += self.get_nolang((ordered and ('# ' + t) or ('* ' + t)) + '\n')
+        r += self.get_nolang(ordered and '# ' or '* ') + t
     return r
 
   def list_item(self, text):
-    return self.get_nolang('l' + str(len(text)) + ':') + text
+    while text[0] == '\n':
+      text = text[1:]
+    if DEBUG: debug('list item: ' + repr(text))
+    a = text.split('\n')
+    text = a[0] + '\n'
+    for t in a[1:]:
+      if t:
+        text += self.get_nolang('  ') + t + '\n'
+    return 'l' + str(len(text)) + ':' + text
 
   def block_code(self, code, lang=None):
-    return self.get_nolang('```no-highlight\n') + code + self.get_nolang('\n```\n')
+    return self.get_nolang('```no-highlight') + '\n' + \
+           code + '\n' + self.get_nolang('```') + '\n'
 
   def block_quote(self, text):
     r = ''
     for line in text.splitlines():
-      r += self.get_nolang((line and '> ' or '')) + line + self.get_nolang('\n')
+      r += self.get_nolang((line and '> ' or '')) + line + '\n'
     return r
 
   def block_html(self, text):
@@ -103,6 +122,7 @@ class TVH_C_Renderer(Renderer):
     fatal('Inline HTML not allowed: ' + repr(text))
 
   def _emphasis(self, text, pref):
+    if DEBUG: debug('emphasis[' + pref + ']: ' + repr(text))
     return self.get_nolang(pref) + text + self.get_nolang(pref + ' ')
 
   def emphasis(self, text):
@@ -115,7 +135,7 @@ class TVH_C_Renderer(Renderer):
     return self._emphasis(text, '~~')
 
   def codespan(self, text):
-    return self.get_nolang('`') + text + self.get_nolang('`')
+    return self.get_nolang('`' + text + '`')
 
   def autolink(self, link, is_email=False):
     return self.get_nolang('<') + link + self.get_nolang('>')
@@ -225,30 +245,64 @@ class TVH_C_Renderer(Renderer):
     return self.get_nolang('[^' + str(index) + ']')
 
   def footnote_item(self, key, text):
-    r = self.get_nolang('[^' + str(index) + ']:\n')
+    r = self.get_nolang('[^' + str(index) + ']:' + '\n')
     for l in text.split('\n'):
-      r += self.get_nolang('  ') + l.lstrip().rstrip() + self.get_nolang('\n')
+      r += self.get_nolang('  ') + self.get_lang(l.lstrip().rstrip()) + '\n'
     return r
 
   def footnotes(self, text):
-    return text
+    text = text.replace('\n', ' ')
+    return self.get_lang(text.lstrip().rstrip()) + '\n'
 
 #
 #
 #
 
 def optimize(text):
-  lines = text.splitlines()
+
   r = ''
-  prev = ''
-  for line in lines:
-    if prev.startswith('"') and line.startswith('"'):
-      prev = prev[:-2] + line[1:]
-      continue
-    elif prev:
-      r += prev + '\n'
-    prev = line
-  return r + (prev and (prev + '\n')  or '') 
+  x = ''
+  n = ''
+
+  def repl(t):
+    return t.replace('"', '\\"')
+
+  def nolang(t):
+    return '"' + repl(t) + '",\n'
+
+  def lang(t):
+    return 'LANGPREF N_("' + repl(x) + '"),\n'
+
+  text = text.lstrip().rstrip()
+  while text.find('\n\n\n') >= 0:
+    text = text.replace('\n\n\n', '\n\n')
+  if HUMAN:
+    return text
+
+  for text in text.splitlines():
+    while text:
+      type = text[0]
+      p = text.find(':')
+      if p <= 0:
+        fatal('wrong text entry: ' + repr(text))
+        break
+      l = int(text[1:p])
+      t = text[p+1:p+1+l]
+      if type == 'x':
+        if n: r += nolang(n)
+        n = ''
+        x += t
+      elif type == '_':
+        if x: r += lang(x)
+        x = ''
+        n += t
+      text = text[p+l+1:]
+    if x: r += lang(x)
+    x = ''
+    n += '\\n'
+  if n: r += nolang(n)
+  if x: r += lang(x)
+  return r
 
 #
 #
@@ -273,6 +327,8 @@ def argv_get(what):
 #
 #
 
+HUMAN=argv_get('human')
+DEBUG=argv_get('debug')
 input = argv_get('in')
 if not input:
   fatal('Specify input file.')
@@ -289,4 +345,7 @@ md = Markdown(renderer)
 text = md(text)
 text = optimize(text)
 
-print('const char *' + name + '[] = {\n' + text + '\nNULL\n};\n');
+if not HUMAN:
+  print('const char *' + name + '[] = {\n' + text + '\nNULL\n};\n');
+else:
+  print(text)