[damned-lies] Add ability to build Mallard documentation with uploaded help po files
- From: Claude Paroz <claudep src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [damned-lies] Add ability to build Mallard documentation with uploaded help po files
- Date: Fri, 14 Sep 2018 18:04:10 +0000 (UTC)
commit 881ee06fe7a191ddc2002ef23f67fb9f0746a0f9
Author: Claude Paroz <claude 2xlibre net>
Date: Fri Sep 14 18:11:26 2018 +0200
Add ability to build Mallard documentation with uploaded help po files
damnedlies/urls.py | 5 ++
stats/models.py | 3 +
templates/vertimus/vertimus_detail.html | 7 +-
vertimus/models.py | 27 ++++++-
vertimus/tests/gnome-hello.help.fr.po | 134 ++++++++++++++++++++++++++++++++
vertimus/tests/tests.py | 21 +++++
vertimus/urls.py | 11 ++-
vertimus/views.py | 123 +++++++++++++++++++++++------
8 files changed, 300 insertions(+), 31 deletions(-)
---
diff --git a/damnedlies/urls.py b/damnedlies/urls.py
index 5668db97..2bf30741 100644
--- a/damnedlies/urls.py
+++ b/damnedlies/urls.py
@@ -1,3 +1,5 @@
+import os
+
from django.conf import settings
from django.contrib import admin
from django.contrib.auth import views as auth_views
@@ -121,4 +123,7 @@ if settings.STATIC_SERVE:
path('POT/<path:path>',
serve,
kwargs={'document_root': settings.POTDIR}),
+ path('HTML/<path:path>',
+ serve,
+ kwargs={'document_root': os.path.join(settings.SCRATCHDIR, 'HTML')}),
]
diff --git a/stats/models.py b/stats/models.py
index 0b0987d5..9e79271d 100644
--- a/stats/models.py
+++ b/stats/models.py
@@ -785,6 +785,9 @@ class Domain(models.Model):
return (self.dtype == 'ui' and self.layout == 'po/{lang}.po') or (
self.dtype == 'doc' and self.layout == 'help/{lang}/{lang}.po')
+ def can_build_docs(self, branch):
+ return self.dtype == 'doc' and self.doc_format(branch).format == 'mallard'
+
def get_po_path(self, locale):
"""
Return the relative filesystem path to the po file, existing or not.
diff --git a/templates/vertimus/vertimus_detail.html b/templates/vertimus/vertimus_detail.html
index e9032ba7..b82ff3db 100644
--- a/templates/vertimus/vertimus_detail.html
+++ b/templates/vertimus/vertimus_detail.html
@@ -224,7 +224,12 @@ $(document).ready(function() {
</a>
<span style="margin-left: 5px;">{{ action.merged_file|num_stats:'short' }}</span><br/>
{% endif %}
- <div class="right">{% trans "diff with:" %}
+ {% if action.can_build %}
+ {% if action.build_url %}<a href="{{ action.build_url }}">{% trans "Help index" %}</a>
+ {% else %}<form method="post" action="{% url 'action-build-help' action.pk %}">{% csrf_token
%}<button>{% trans "Build help" %}</button></form>
+ {% endif %}
+ {% endif %}
+ <div style="text-align: right">{% trans "diff with:" %}
{% for f in files %}
<a href="{% url 'vertimus_diff' action.id f.action_id level %}" title="{{ f.title }}">[{{
forloop.revcounter }}]</a>
{% endfor %}
diff --git a/vertimus/models.py b/vertimus/models.py
index 1724dd16..df12519b 100644
--- a/vertimus/models.py
+++ b/vertimus/models.py
@@ -1,6 +1,7 @@
import os, sys
import shutil
from datetime import datetime, timedelta
+from pathlib import Path
from django.conf import settings
from django.db import models
@@ -64,7 +65,7 @@ class State(models.Model):
self.branch.name, self.language.name, self.domain.name)
def get_absolute_url(self):
- return reverse('vertimus_by_ids', args=[self.branch.id, self.domain.id, self.language.id])
+ return reverse('vertimus_by_ids', args=[self.branch_id, self.domain_id, self.language_id])
@property
def stats(self):
@@ -368,6 +369,20 @@ class ActionAbstract(models.Model):
def most_uptodate_file(self):
return self.merged_file if self.merged_file else self.file
+ @property
+ def can_build(self):
+ return (
+ not isinstance(self, ActionArchived) and
+ self.state_db.domain.can_build_docs(self.state_db.branch)
+ )
+
+ @property
+ def build_url(self):
+ path = Path(
+ settings.SCRATCHDIR, 'HTML', str(self.pk), 'index.html'
+ )
+ return '/' + str(path.relative_to(settings.SCRATCHDIR)) if path.exists() else None
+
def get_filename(self):
if self.file:
return os.path.basename(self.file.name)
@@ -792,8 +807,10 @@ def merge_uploaded_file(sender, instance, **kwargs):
@receiver(pre_delete)
def delete_action_files(sender, instance, **kwargs):
"""
- pre_delete callback for Action that deletes the file + the merged file from upload
- directory.
+ pre_delete callback for Action that deletes:
+ - the uploaded file
+ - the merged file
+ - the html dir where docs are built
"""
if not isinstance(instance, ActionAbstract) or not getattr(instance, 'file'):
return
@@ -803,6 +820,10 @@ def delete_action_files(sender, instance, **kwargs):
os.remove(instance.merged_file.path)
if os.access(instance.file.path, os.W_OK):
os.remove(instance.file.path)
+ html_dir = Path(settings.SCRATCHDIR, 'HTML', str(instance.pk))
+ if html_dir.exists():
+ shutil.rmtree(str(html_dir))
+
@receiver(pre_delete, sender=Statistics)
def clean_dangling_states(sender, instance, **kwargs):
diff --git a/vertimus/tests/gnome-hello.help.fr.po b/vertimus/tests/gnome-hello.help.fr.po
new file mode 100644
index 00000000..07e03ca4
--- /dev/null
+++ b/vertimus/tests/gnome-hello.help.fr.po
@@ -0,0 +1,134 @@
+# French translation of gnome-hello documentation.
+# Copyright (C) 2008-2012 Free Software Foundation, Inc.
+# This file is distributed under the same license as the gnome-hello documentation package.
+msgid ""
+msgstr ""
+"Project-Id-Version: GNOME Hello 2.0\n"
+"POT-Creation-Date: 2012-12-27 21:29+0000\n"
+"PO-Revision-Date: 2013-02-26 16:33+0100\n"
+"Last-Translator:\n"
+"Language-Team: GNOME French Team <gnomefr traduc org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. Put one translator per line, in the form NAME <EMAIL>, YEAR1, YEAR2
+msgctxt "_"
+msgid "translator-credits"
+msgstr ""
+
+#. This is a reference to an external file such as an image or video. When
+#. the file changes, the md5 hash will change to let you know you need to
+#. update your localized copy. The msgstr is not used at all. Set it to
+#. whatever you like once you have updated your copy of the file.
+#: C/index.page:7(media) C/index.page:26(media)
+msgctxt "_"
+msgid ""
+"external ref='figures/gnome-hello-logo.png' "
+"md5='1ae47b7a7c4fbeb1f6bb72c0cf18d389'"
+msgstr ""
+"external ref='figures/gnome-hello-logo.png' "
+"md5='1ae47b7a7c4fbeb1f6bb72c0cf18d389'"
+
+#: C/index.page:6(info/desc)
+msgid "Help for GNOME Hello."
+msgstr "Aide de GNOME Hello."
+
+#: C/index.page:20(license/p) C/compiling.page:15(license/p)
+#: C/what-is.page:15(license/p) C/what-is-like.page:16(license/p)
+#: C/what-can-do.page:15(license/p)
+msgid "Creative Commons Share Alike 3.0"
+msgstr "Creative Commons Partage des conditions initiales à l'identique 3.0"
+
+#: C/index.page:26(page/title)
+msgid ""
+"<media type=\"image\" src=\"figures/gnome-hello-logo.png\">GNOME Hello logo</"
+"media>GNOME Hello"
+msgstr ""
+"<media type=\"image\" src=\"figures/gnome-hello-logo.png\">Logo de GNOME "
+"Hello</media>GNOME Hello"
+
+#: C/index.page:28(section/title)
+msgid "Introduction"
+msgstr "Introduction"
+
+#: C/index.page:32(section/title)
+msgid "It's a small world"
+msgstr "C'est un petit monde"
+
+#: C/index.page:36(section/title)
+msgid "Advanced"
+msgstr "Avancé"
+
+#: C/compiling.page:7(info/desc)
+msgid ""
+"<link xref=\"get#compile\">Compile</link> and <link xref=\"get#run\">run</"
+"link> and <link xref=\"get#help\">more</link>."
+msgstr ""
+"<link xref=\"get#compile\">Compiler</link>, <link xref=\"get#run\">lancer</"
+"link> et <link xref=\"get#help\">plus encore</link>."
+
+#: C/compiling.page:21(page/title)
+msgid "Get <app>GNOME Hello</app>"
+msgstr "Obtenir <app>GNOME Hello</app>"
+
+#: C/compiling.page:24(section/title)
+msgid "Compile"
+msgstr "Compiler"
+
+#: C/compiling.page:25(section/p)
+msgid "To compile the app from source:"
+msgstr "Pour compiler l'application à partir des sources :"
+
+#: C/compiling.page:29(item/p)
+msgid "In a terminal, type:"
+msgstr "Dans un terminal, saisissez :"
+
+#: C/compiling.page:30(item/p)
+msgid "<cmd>git clone git://git.gnome.org/gnome-hello</cmd>"
+msgstr "<cmd>git clone git://git.gnome.org/gnome-hello</cmd>"
+
+#: C/compiling.page:31(item/p)
+msgid "<cmd>cd gnome-hello</cmd>"
+msgstr "<cmd>cd gnome-hello</cmd>"
+
+#: C/compiling.page:32(item/p)
+msgid "<cmd>./autogen.sh --prefix=`pwd`/install</cmd>"
+msgstr "<cmd>./autogen.sh --prefix=`pwd`/install</cmd>"
+
+#: C/compiling.page:33(item/p)
+msgid "<cmd>make</cmd>"
+msgstr "<cmd>make</cmd>"
+
+#: C/compiling.page:34(item/p)
+msgid "<cmd>make install</cmd>"
+msgstr "<cmd>make install</cmd>"
+
+#: C/compiling.page:39(section/title)
+msgid "Run"
+msgstr "Lancer"
+
+#: C/compiling.page:40(section/p)
+msgid "To run gnome-hello type:"
+msgstr "Pour lancer gnome-hello, saisissez :"
+
+#: C/compiling.page:43(section/p)
+msgid "<cmd>./src/gnome-hello</cmd>"
+msgstr "<cmd>./src/gnome-hello</cmd>"
+
+#: C/compiling.page:49(section/title)
+msgid "View most up-to-date help pages"
+msgstr "Afficher les pages d'aide les plus à jour"
+
+#: C/compiling.page:50(section/p)
+msgid ""
+"To run most recent help pages (and view your changes if you made any) type:"
+msgstr ""
+"Pour afficher les pages d'aide les plus récentes (et voir vos modifications "
+"si vous en avez faites), saisissez :"
+
+#: C/compiling.page:53(section/p)
+msgid "<cmd>yelp help/C/</cmd>"
+msgstr "<cmd>yelp help/C/</cmd>"
diff --git a/vertimus/tests/tests.py b/vertimus/tests/tests.py
index 9f8cfbb4..2587c684 100644
--- a/vertimus/tests/tests.py
+++ b/vertimus/tests/tests.py
@@ -740,6 +740,27 @@ class VertimusTest(TeamsAndRolesTests):
response = self.client.get(reverse('stats-quality-check', args=[po_stat.pk]))
self.assertContains(response, "The po file looks good!")
+ @test_scratchdir
+ def test_doc_building(self):
+ dom = Domain.objects.create(
+ module=self.m, name='help', description='User Guide', dtype='doc',
+ layout='help/{lang}/{lang}.po'
+ )
+ state = StateTranslating(branch=self.b, domain=dom, language=self.l, person=self.pt)
+ state.save()
+
+ file_path = Path(__file__).parent / "gnome-hello.help.fr.po"
+ with file_path.open() as test_file:
+ action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
+ action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done by translator."})
+ self.assertTrue(action.can_build)
+ self.assertIsNone(action.build_url)
+ response = self.client.post(reverse('action-build-help', args=[action.pk]))
+ self.assertRedirects(
+ response, '/HTML/%d/index.html' % action.pk, fetch_redirect_response=False
+ )
+ self.assertEqual(action.build_url, '/HTML/%d/index.html' % action.pk)
+
def test_mysql(self):
# Copied from test_action_undo() with minor changes
state = StateNone(branch=self.b, domain=self.d, language=self.l)
diff --git a/vertimus/urls.py b/vertimus/urls.py
index f730e821..90c7c6ce 100644
--- a/vertimus/urls.py
+++ b/vertimus/urls.py
@@ -26,6 +26,13 @@ urlpatterns = [
path('<locale:locale>/activity_summary/',
views.activity_by_language,
name='activity_by_language'),
- path('action/<int:action_pk>/qcheck/', views.quality_check, name='action-quality-check'),
- path('stats/<int:stats_pk>/qcheck/', views.quality_check, name='stats-quality-check'),
+ path('action/<int:action_pk>/qcheck/',
+ views.QualityCheckView.as_view(),
+ name='action-quality-check'),
+ path('stats/<int:stats_pk>/qcheck/',
+ views.QualityCheckView.as_view(),
+ name='stats-quality-check'),
+ path('action/<int:action_pk>/build_help/',
+ views.BuildTranslatedDocsView.as_view(),
+ name='action-build-help'),
]
diff --git a/vertimus/views.py b/vertimus/views.py
index 321ae304..ba2fe9e2 100644
--- a/vertimus/views.py
+++ b/vertimus/views.py
@@ -1,6 +1,9 @@
import difflib
import os
import re
+import subprocess
+import tempfile
+from pathlib import Path
from django.conf import settings
from django.contrib import messages
@@ -9,10 +12,11 @@ from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import mark_safe
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
+from django.views.generic import View
from stats.models import Statistics, FakeLangStatistics, Module, Branch, Domain, Language
-from stats.utils import check_po_quality, is_po_reduced
+from stats.utils import DocFormat, check_po_quality, is_po_reduced
from vertimus.models import State, Action, ActionArchived, SendMailFailed
from vertimus.forms import ActionForm
@@ -246,28 +250,97 @@ def activity_by_language(request, locale):
return render(request, 'vertimus/activity_summary.html', context)
-def quality_check(request, action_pk=None, stats_pk=None):
- context = {'base': 'base_modal.html'}
- pofile = None
- if action_pk:
- action = get_object_or_404(Action, pk=action_pk)
- if not action.has_po_file():
- context['results'] = _('No po file to check')
+class PoFileActionBase(View):
+ def get(self, request, *args, **kwargs):
+ self.pofile = self.get_po_file()
+ context = self.get_context_data(**kwargs)
+ return render(request, self.template_name, context)
+
+ def get_po_file(self):
+ pofile = None
+ if self.kwargs.get('action_pk'):
+ self.action = get_object_or_404(Action, pk=self.kwargs['action_pk'])
+ if self.action.has_po_file():
+ pofile = self.action.most_uptodate_file.path
+ elif self.kwargs.get('stats_pk'):
+ stats = get_object_or_404(Statistics, pk=self.kwargs['stats_pk'])
+ pofile = stats.po_path()
else:
- pofile = action.most_uptodate_file.path
- elif stats_pk:
- stats = get_object_or_404(Statistics, pk=stats_pk)
- pofile = stats.po_path()
- else:
- raise Http404("action_pk and stats_pk are both None")
-
- if pofile:
- context['checks'] = ['xmltags']
- results = check_po_quality(pofile, context['checks'])
- if results:
- context['results'] = mark_safe(re.sub(
- r'^(# \(pofilter\) .*)', r'<span class="highlight">\1</span>', escape(results), flags=re.M
- ))
+ raise Http404("action_pk and stats_pk are both None")
+ return pofile
+
+
+class QualityCheckView(PoFileActionBase):
+ template_name = 'vertimus/quality-check.html'
+
+ def get_context_data(self, **kwargs):
+ context = {'base': 'base_modal.html'}
+ if self.pofile is None:
+ context['results'] = _('No po file to check')
else:
- context['results'] = _('The po file looks good!')
- return render(request, 'vertimus/quality-check.html', context)
+ context['checks'] = ['xmltags']
+ results = check_po_quality(self.pofile, context['checks'])
+ if results:
+ context['results'] = mark_safe(re.sub(
+ r'^(# \(pofilter\) .*)', r'<span class="highlight">\1</span>', escape(results),
flags=re.M
+ ))
+ else:
+ context['results'] = _('The po file looks good!')
+ return context
+
+
+class BuildTranslatedDocsView(PoFileActionBase):
+ def post(self, request, *args, **kwargs):
+ self.pofile = self.get_po_file()
+ if self.pofile is None:
+ raise Http404('No target po file for this action')
+
+ html_dir = Path(settings.SCRATCHDIR, 'HTML', str(self.kwargs['action_pk']))
+ if (html_dir / 'index.html').exists():
+ # If the build already ran, redirect to the static results
+ return HttpResponseRedirect(self.action.build_url)
+
+ state = self.action.state_db
+ try:
+ doc_format = DocFormat(state.domain, state.branch)
+ except Exception as err:
+ messages.error(request, err)
+ return HttpResponseRedirect(state.get_absolute_url())
+ build_error = _('Build failed (%(program)s): %(err)s')
+ with tempfile.NamedTemporaryFile(suffix='.gmo') as gmo, \
+ tempfile.TemporaryDirectory() as build_dir:
+ result = subprocess.run([
+ 'msgfmt', self.pofile, '-o', os.path.join(gmo.name)
+ ], stderr=subprocess.PIPE)
+ if result.returncode != 0:
+ messages.error(request, build_error % {
+ 'program': 'msgfmt', 'err': result.stderr.decode()
+ })
+ return HttpResponseRedirect(state.get_absolute_url())
+
+ sources = doc_format.source_files()
+ result = subprocess.run([
+ 'itstool', '-m', gmo.name, '-o', str(build_dir), '--strict',
+ *[str(s) for s in sources],
+ ], cwd=str(doc_format.vcs_path), stderr=subprocess.PIPE)
+ if result.returncode != 0:
+ messages.error(request, build_error % {
+ 'program': 'itstool', 'err': result.stderr.decode()
+ })
+ return HttpResponseRedirect(state.get_absolute_url())
+
+ # Now build the html version
+ if not html_dir.exists():
+ html_dir.mkdir(parents=True)
+ cmd = [
+ 'yelp-build', 'html', '-o', str(html_dir),
+ '-p', str(doc_format.vcs_path / 'C'),
+ str(build_dir)
+ ]
+ result = subprocess.run(cmd, cwd=str(build_dir), stderr=subprocess.PIPE)
+ if result.returncode != 0:
+ messages.error(request, build_error % {
+ 'program': 'yelp-build', 'err': result.stderr.decode()
+ })
+ return HttpResponseRedirect(state.get_absolute_url())
+ return HttpResponseRedirect(self.action.build_url)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]