ctags-Unterstützung für Noweb

Werbung
Literate Programming
ctags-Unterstützung
für Noweb-Markdown
CoMet-Arbeitsberichte
Meik Teßmer*
2015
Bereich Computergestützte Methoden (� CoMet)
Version: 0.1.3
ctags hil bei der Navigation in ellcode- und textbasierten Dateien. Von
Haus aus werden viele Sprachen und Markup-Formate unterstützt, allerdings nicht
die Kombination Noweb-Markdown. Das hier vorgestellte Werkzeug behebt diesen
Mangel, indem es eine ctags-kompatible Ausgabe erzeugt, die eine Integration mit
dem Editor Vim über das Tagbar-Plugin ermöglicht.
Inhaltsverzeichnis
1 Von Noweb zu Markdown zu ctags
1.1 Navigation mit Outlines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Integration mit Vim über das Tagbar-Plugin . . . . . . . . . . . . . . . . . . .
2
2
2
2 Das Format einer ctags-kompatiblen tags-Datei
3
3 Relevante Informationen für die tags-Datei
4
4 Erstellung der tags-Datei
4.1 Verarbeiten der Eingabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Iteratoren und Generatorfunktionen in Python . . . . . . . . . . . . . . . . . .
4.3 Ein Iterator ür die Generation von Tags ür Code- und Dokumentation-Chunks
4.4 Die Klasse Tag und die dazu gehörende Factory-Funktion . . . . . . . . . . . .
4.5 Die Suche nach Code-Chunk-Definitionen . . . . . . . . . . . . . . . . . . . .
4.6 Test auf Vorhandensein einer Dokumentation-Chunk-Definitionen . . . . . .
4.7 Die Suche nach Absatzüberschrien . . . . . . . . . . . . . . . . . . . . . . . .
4.8 Zusammenügen der Lösung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
4
5
5
7
8
8
9
9
*[email protected]
1
5 Die Ergebnisdatei
10
6 Lizenz
10
7 Release Notes
11
8 Code Chunks
12
1 Von Noweb zu Markdown zu ctags
1.1 Navigation mit Outlines
Viele Editoren helfen bei der Navigation in großen Dokumenten durch sog. Outlines, eine
Baumstruktur, die den Textauau übersichtlich darstellt. Das Werkzeug Exuberant ctags, dessen Hauptaufgabe in der Erstellung eines Indexes von Sprachelementen aus ellcode-Dateien
besteht, kann auch ür die Erstellung einer solchen Übersicht benutzt werden und kommt daher
bei vielen dieser Editoren zum Einsatz.
ctags unterstützt laut Website 41 Programmiersprachen (Stand: 2015-03-05) und kann relativ einfach um zusätzliche Sprachen erweitert werden. Literate Programme (zu Literate Programming siehe [Knuth1984 ]) stellen ür das Werkzeug jedoch eine nicht zu überwindende
Hürde dar, denn es kann den Unterschied zwischen Code- und Dokumentation-Chunk nicht
erkennen.
nw2md2ctags erstellt eine zu ctags kompatible tags-Datei aus einem literaten Programm,
dessen Dokumentation-Chunks in Markdown-Markup verfasst sind. Dabei wird sowohl die
klassische noweb-Syntax akzeptiert als auch die mit nw2md eingeührte erweiterte Syntax.
1.2 Integration mit Vim über das Tagbar-Plugin
kann als Ergänzung zu ctags im Vim-Plugin Tagbar benutzt werden. Dazu ist
folgender Eintrag in der Vim-Konfigurationsdatei ~/.vimrc nötig:
nw2md2ctags
let g:tagbar_type_nw2md = {
\ ’ctagstype’: ’nw2md’,
\ ’ctagsbin’ : ’/path/to/nw2md2ctags’,
\ ’ctagsargs’ : ’’,
\ ’kinds’ : [
\ ’s:Abschnitte’,
\ ’c:Code Chunks’
\ ],
\ ’sort’: 0,
\ }
2
Damit das Plugin die Ergänzung erkennt, muss Vim via filetype=nw2md mitgeteilt werden,
dass es sich um den mit let g:tagbar_type_nw2md angegebenen Dateityp nw2md handelt. Eine
sog. Modeline am Anfang oder Ende des literaten Programms erledigt das automatisch:
.. vim:filetype=nw2md
2 Das Format einer ctags-kompatiblen tags-Datei
Das ür unsere Zwecke erforderliche Dateiformat der tags-Datei ist zeilenweise aufgebaut. Jede
Zeile besteht aus folgenden Spalten:
tagname<TAB>tagfile<TAB>ex_cmd;”<TAB>field ..
Ein Beispiel:
markers
nw2md2ctags.nw
/^# markers$/;” h
Es handelt sich hierbei um das Format Version 2, mit dem ctags das Standardformat erweitert.
Es ist abwärtskompatibel mit Vi und wird nur von neueren ctags-Versionen wie Exuberant
ctags unterstützt.
Bezeichner, bspw. der Name einer Funktion. Er darf Leerzeichen enthalten, aber keinen Tabulator. Der Bezeichner kann so auereitet werden, dass bspw. eine Gliederungshierarchie sichtbar wird: Überschrien erster Stufe werden nicht eingerückt, die zweiter
Stufe um zwei Leerzeichen, die drier Stufe um 4 Leerzeichen.
tagname
TAB
Ein Tabulator-Zeichen.
Der Name der Datei, in der der Bezeichner steht. Die Pfadangabe kann absolut oder
relativ sein und kann Umgebungsvariablen und Wildcards enthalten (wobei Letzere nicht
verwendet werden sollten). Der Name darf kein Tabulator-Zeichen enthalten.
tagfile
ex_cmd
;”
Der Ex-Befehl, der den Cursor an die Stelle des Bezeichners positioniert.
Das Semikolon und die doppelten Anührungszeichen leiten ür Vi den Beginn eines Kommentars ein. Alle folgenden Zeichen werden also von alten Vi-Versionen nicht beachtet.
Eine Liste optionaler Schlüssel-Wert-Paare, getrennt durch ein Tabulator-Zeichen
Felder. Jedes Feld folgt der Form
field ..
<TAB>{fieldname}:{value}
Der Feldname identifiziert das Feld und darf nur aus Buchstaben bestehen ([a-zA-Z]).
Der Wert kann eine beliebige Zeichenkee ohne Tabulator-Zeichen sein. Folgende Zeichen werden speziell behandelt:
3
•
•
•
•
\t:
Tabulator-Zeichen
\r: Carriage Return
\n: Newline
\\: einzelner Backslash
Es gibt ein Feld, das von dieser Regel ausgenommen ist. Das kind-Feld wird so behandelt,
als ob der Schlüssel kind: davor stünde (im obigen Beispiel ist dies das Feld h). Das Feld
line: gibt an, in welcher Zeile sich das Tag befindet.
Mit speziellen Zeilen können zusätzliche Angaben bspw. zur Sortierung oder zur Kodierung
gemacht werden. Die nachfolgende Zeile legt die Kodierung auf UTF-8 fest:
!_TAG_FILE_ENCODING<TAB>utf-8<TAB>{anything} ~
3 Relevante Informationen für die tags-Datei
Die tags-Datei soll Code Chunks und Abschnisüberschrien auühren. Wir benötigen dazu
• die Namen aller Code Chunks,
• alle Abschnisüberschrien (zunächst im einfacher zu verarbeitenden Atx-Format)
• die Zeilennummern der jeweiligen Tags
Die Einträge ür das kind-Feld lauten entsprechend:
•
•
ür Code Chunks
s ür Abschnie (Sections)
c
Der Ex-Befehl ist üblicherweise ein regulärer Ausdruck, der auf das gesuchte Tag passt. Da
es aber durchaus Code Chunks mit gleichem Namen gibt, brauchen wir zusätzlich die Zeilennumer.
Eine Beispiel-Datei könnte dann aussehen wie folgt:
. Die Suche nach Definitionen nw2md2ctags.nw /^## Die Suche nach Definitionen$/;”
.
Generatorfunktionen nw2md2ctags.nw
.
Reader
nw2md2ctags.nw
<<Definitionen>>=
/^## Reader$/;” s
nw2md2ctags.nw
<<Generator-Beispiel>>=
/^## Generatorfunktionen$/;”
line:2
line:87
/^<<Definitionen>>= (python)$/;”
c line:99
nw2md2ctags.nw /^<<Generator-Beispiel>>= (python)$/;” c
4 Erstellung der tags-Datei
4.1 Verarbeiten der Eingabe
Die Verabeitung von Textdateien erfolgt in Python o nach folgendem Schema: Durchlaufe die
Eingabe zeilenweise mit Hilfe einer for-Schleife und verarbeite jede Zeile separat. Das fileObjekt liefert der Schleife dazu einen Iterator.
Für die Erstellung der tags-Datei interessiert uns aber nicht jede Zeile, sondern wir benötigen Chunks. Warum also nicht einen Generator konstruieren, der an Stelle eines Zeileniterators
einen Chunk-Iterator bereitstellt?
4
s
s line:56
line:191
4.2 Iteratoren und Generatorfunktionen in Python
Mit PEP 234 (ab Python Version 2.2) erwarten for-Schleifen keine Sequenz mehr, sondern einen
Ausdruck, der als Ergebnis des Aufrufs iter() einen Iterator zurückgibt. Ein Iterator stellt
eine Funktion bereit, die ür eine Schleife „das nächste Element“ bereitstellt bzw. None, falls
es kein nächstes Element gibt. Eine for-Schleife kann so mit allen Objekten arbeiten, die eine
entsprechende Iterator-Funktion vorhalten.
Ein Generator erzeugt einen solchen Iterator (s. PEP 255, ebenfalls ab Version 2.2.). Anders
als herkömmliche Funktionen verwendet eine Generator-Funktion an Stelle von return das
Schlüsselwort yield ür die Rückgabe eines Elements. Der Funktionszustand inkl. lokaler Variablen wird bis zum nächsten Aufruf des Iterators zwischengespeichert. Ein Beispiel:
<Generator-Beispiel>=
# Verwendung des zurückgegebenen Iterators
for value in generator_funktion():
print(value)
# oder auch:
a, b, c = generator_funktion()
def generator_funktion():
yield 1
yield 2
yield 3
> 1
> 2
> 3
An Stelle einer einfachen Rückgabe gibt ein Generator ein Objekt zurück, das dass IteratorProtokoll unterstützt und damit bspw. in for-Schleifen verwendet werden kann.
4.3 Ein Iterator für die Generation von Tags für Code- und
Dokumentation-Chunks
Der Generator erhält ein file
object
als Lesequelle.
<Tag-Generator>=
def chunks(source):
”””read lines from source and yield either code or doc chunks
”””
Der Literate Programming-Ansatz beschreibt eine beliebige Abfolge von Code- und
Dokumentation-Chunks. Der Generator muss daher ür jede zu verarbeitende Zeile festhalten, zu welcher Chunk-Art sie gehört bzw. in welchem Zustand sich der Generator
5
befindet. Das Flag in_code_chunk übernimmt diese Aufgabe. Standardmäßig wird mit einem
Dokumentation-Chunk begonnen.
<Tag-Generator 2>=
in_code_chunk = False
line_no = 0
for line in source:
line_no += 1
Zunächst wird aktuelle Zeile nach einer möglichen Code-Chunk-Definition untersucht. Ist
die Suche erfolgreich, wird mit der Factory-Funktion create_tag eine Tag-Instanz erzeugt und
miels yield an die übergeordnete for-Schleife zurückgegeben.
<Tag-Generator 3>=
# look for a code chunk definition
match = search_for_code_chunk_definition(line)
if match:
in_code_chunk = True
#chunk_name = match[0]
yield create_tag(line, line_no, ”c”)
continue
Ist in der Zeile keine Code-Chunk-Definition zu finden, wird nach einem DokumentationChunk gesucht. Ist die Suche erfolgreich, muss der Zustand des Generators auf geändert werden, da wir uns nicht mehr in einem Code Chunk befinden. Alle Eingabezeilen, die ab jetzt
verarbeitet werden, gehören zu einem Dokumentation-Chunk und müssen auf Abschnisüberschrien hin untersucht werden (suche Absatzüberschrien). Für gefundene Überschrien
werden Tag-Instanzen erzeugt und zurückgegeben.
<Tag-Generator 4>=
# is the a new doc chunk?
if contains_doc_chunk_header(line):
in_code_chunk = False
# check for markdown markup, only if we are in_code_chunk
if not in_code_chunk:
# search for headers
match = search_for_header(line)
if match and not in_code_chunk:
#yield Tag(line.strip(), ”s”)
yield create_tag(line, line_no, ”s”)
continue
# maybe search for more markup?
# ...
6
4.4 Die Klasse Tag und die dazu gehörende Factory-Funktion
Die Klasse Tag steht ür einen Eintrag in der zu generierenden tags-Datei. Sie hält den ChunkNamen, die Art des Eintrags (kind) und die zugehörige Eingabezeile vor und gibt bei der Ausgabe als Zeichenkee eine zum tags-Format kompatible Repräsentation zurück. Damit kann
durch eine simple print-Anweisung ein tags-Eintrag erzeugt werden.
Da das tags-Format zusätzlich den Namen der zugehörigen Eingabedatei benötigt, wird zusätzlich eine Klassenvariable namens filename definiert, die vor Erzeugung der ersten Instanz
passend belegt werden muss
<Tag-Klasse>=
class Tag:
”””A tags file entry
”””
filename = None
def __init__(self, name, kind, line, line_no):
self.name = name
self.kind = kind
self.line = line
self.line_no = line_no
def __repr__(self):
return ”{0}\t{1}\t/^{2}$/;\”\t{3}\tline:{4}”.format(self.name, self.filename,
self.line, self.kind, self.line_no)
Die Factory-Funktion übernimmt die Erzeugung von Tag-Instanzen und bereitet die Werte
entsprechend der kind-Angabe auf. Hier werden bspw. die Doppelkreuze der Abschnisüberschrien durch Punkte ersetzt.
Newline muss vom Ende entfernt werden.
<Tag-Factory-Funktion>=
def create_tag(line, line_no, kind):
# remove newline char
line = line[:-1]
if kind == ”s”:
# replace # with spaces
name = line.replace(”#”, ”.”)
return Tag(name, kind, line, line_no)
elif kind == ”c”:
return Tag(line, kind, line, line_no)
else:
# Error
7
return None
4.5 Die Suche nach Code-Chunk-Definitionen
Der Generator ist „abwärtskompatibel” zur klassischen Noweb-Syntax:
<<Code-Chunk-Bezeichner>>=
Er verarbeitet aber vorrangig die mit nw2md (Link?) eingeührte Erweiterung um die Angabe
der verwendeten Programmiersprache:
<<Code-Chunk-Bezeichner>>= (python)
Sämtliche durch pandoc bzw. Pygments unterstützte Sprachen können angegeben werden.
Ist die Suche erfolgreich, wird ür die erweiterte Syntax das Paar (Chunk-Name, Sprache)
zurückgegeben, ür die alte (Chunk-Name, None); bei erfolgloser Suche ist die Rückgabe None.
<suche nach Code-Chunk-Definitionen>=
def search_for_code_chunk_definition(line):
”””look for code chunk definition and return chunk name”””
# look for annotated code chunk header, e.g. <<code>>= (make)
match = re.match(open_mark + ”([^>]+)” + close_mark + ”\s*\((\w+)\)”, line)
if not match:
# ok, use normal noweb syntax
match = re.match(open_mark + ”([^>]+)” + close_mark, line)
if match:
return (match.group(1), None)
else:
return None
else:
return (match.group(1), match.group(2))
4.6 Test auf Vorhandensein einer Dokumentation-Chunk-Definitionen
Die Funktion sucht nach dem Marker ür Dokumentation-Chunks und gibt entsprechend einen
boolschen Wert zurück.
<enthält die Zeile eine Dokumentation-Chunk-Definition>=
def contains_doc_chunk_header(line):
match = re.match(doc_chunk_marker + ”[ ]?”, line)
if match:
return True
else:
return False
8
4.7 Die Suche nach Absatzüberschrien
Diese Funktion sucht nach den definierten Überschrien (header_1 bis header_3) und gibt sie
bei erfolgreicher Suche zurück.
<suche nach Absatzüberschriften>=
def search_for_header(line):
for marker in [header_1, header_2, header_3]:
match = re.match(marker + ” \s*(.+)\s*$”, line)
if match:
return (match.group(1))
return None
4.8 Zusammenfügen der Lösung
Zunächst müssen wir noch die verwendeten Konstanten definieren, bevor die Gesamtlösung
zusammengestellt werden kann.
<Definitionen von Konstanten>=
# markers of code chunk definitions
open_mark = ”<<”
close_mark = ”>>=”
doc_chunk_marker = ”^@”
# markers for headers
header_1 = ”#”
header_2 = ”##”
header_3 = ”###”
Das Hauptprogramm setzt zunächst die Klassenvariable filename auf den Dateinamen der
Eingabedatei. Anschließend wird diese geöffnet und mit einer for-Schleife der von uns konstruierte Tag-Generator ür die Extraktion von tags-Zeilen verwendet. Hier zeigt sich, wie eine
eigene Generatorfunktion den Code vereinfachen kann.
Die generierten Tag-Instanzen werden zunächst zwischengespeichert und anschließend ausgegeben.
<Hauptprogramm>=
def main():
Tag.filename = sys.argv[1]
out = []
with open(Tag.filename, ”r”) as handle:
9
for chunk in chunks(handle):
out.append(str(chunk))
#for line in sorted(out):
for line in out:
print(line)
5 Die Ergebnisdatei
<nw2md2ctags>=
#!/usr/bin/env python3
import re
import sys
__author__ = ”Meik Teßmer”
__email__ = ”[email protected]”
__version__ = ”0.1.3”
<<Definitionen von Konstanten>>
<<Tag-Klasse>>
<<Tag-Factory-Funktion>>
<<Tag-Generator>>
<<suche nach Code-Chunk-Definitionen>>
<<enthält die Zeile eine Dokumentation-Chunk-Definition>>
<<suche nach Absatzüberschriften>>
<<Hauptprogramm>>
if __name__ == ”__main__”:
main()
6 Lizenz
nw2md2ctags generates a ctags-compatible output for noweb/nw2md input
Copyright (C) 2015
Meik Teßmer
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
10
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.
If not, see <http://www.gnu.org/licenses/>.
<build-script>=
#!/bin/sh
if [ -z ”${NOWEB_SOURCE}” ]; then
NOWEB_SOURCE=nw2md2ctags.nw
fi
if [ -z ”${NOWEB_CODE}” ]; then
NOWEB_CODE=‘pwd‘/code
fi
# check if we need to create target dirs
[ -d ${NOWEB_CODE} ] || mkdir -p ${NOWEB_CODE}
FILES=”nw2md2ctags”
for f in ${FILES}; do
tangle -R”$f” ”${NOWEB_SOURCE}” > ”${NOWEB_CODE}/$f”
chmod u+x ”${NOWEB_CODE}/$f”
done
7 Release Notes
[v0.1.3]
• base-build-script: deleted file.
• Makefile, base-build-script, metadata.yaml, nw2md2ctags.nw, nw2md2ctags.py:
– added line number to tags line for precise cursor positioning
– fixed the fix ;-)
– added Release Notes
[v0.1.2]
11
• Makefile, nw2md2ctags.nw: removed strip
• Makefile: tried some fixes for fragment generation
[v0.1.1]
• Makefile, nw2md2ctags.nw: fixed HTML fragment generation and removed link placeholder to nw2md
[v0.1.0]
• Makefile, nw2md2ctags.nw, nw2md2ctags.py: relase 0.1.0
8 Code Chunks
• Definitionen von Konstanten
• Generator-Beispiel
• Hauptprogramm
• Tag-Factory-Funktion
• Tag-Generator
–
–
–
–
Tag-Generator
Tag-Generator 2
Tag-Generator 3
Tag-Generator 4
• Tag-Klasse
• build-script
• enthält die Zeile eine Dokumentation-Chunk-Definition
• nw2md2ctags
• suche nach Absatzüberschrien
• suche nach Code-Chunk-Definitionen
12
Herunterladen