Commit 881ee06f authored by Claude Paroz's avatar Claude Paroz

Add ability to build Mallard documentation with uploaded help po files

parent c84e70e3
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')}),
]
......@@ -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.
......
......@@ -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 %}
......
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):
......
# 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>"
......@@ -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)
......
......@@ -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'),
]
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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment