Monday, 24 February 2014

[Python] docutils package (hay nhật ký tìm hiểu 1 python package)

Bài viết này dành cho những ai biết các khái niệm pip, virtualenv, package/module trong python. Hoặc những ai muốn xem ví dụ sử dụng một số câu lệnh để khám phá 1 directory.

Docutils is a modular system for processing documentation into useful formats, such as HTML, XML, and LaTeX. For input Docutils supports reStructuredText, an easy-to-read, what-you-see-is-what-you-get plaintext markup syntax.

Tóm lại module (hay package) này thực hiện các công việc giúp đọc 1 file .rst (reStructuredText) và cho phép xuất ra các output khác nhau (HTML, XML, LaTeX)

Ví dụ:
 rst2html.py monitor.rst out.html
Document của docutils khá là sơ sài, có mỗi page này có vẻ có chút thông tin:
http://docutils.sourceforge.net/docs/dev/hacking.html


Các bước hoạt động

docutils chuyển đổi 1 file .rst sang .html qua 4 giai đoạn:
1. Reader : đọc document từ source file và chuyển cho Parser
2. Parser: phân tích document nhận được, tạo 1node tree document.
3. Transform: thực hiện tranform ví dụ như biến 1 reference thành 1 link cụ thể.
4. Writer: nhận node tree sau khi transform và thực hiện ghi ra file đích.

Introspect docutils (soi và ngắm)


Để cho sạch sẽ, cài docutils vào 1 virtualenv riêng.

hvn@archhvn: /tmp () $ virtualenv2 crazy
New python executable in crazy/bin/python2
Also creating executable in crazy/bin/python
Installing Setuptools.................................................................................................................................
.............................................................................................done.
Installing Pip........................................................................................................................................
......................................................................................................................................................
.......................................done.
hvn@archhvn: /tmp () $ . crazy/bin/activate
(crazy)hvn@archhvn: /tmp () $ pip install docutils
Downloading/unpacking docutils
  Downloading docutils-0.11.tar.gz (1.6MB): 1.6MB downloaded
...
Successfully installed docutils
Cleaning up...

Tổng số file python code:

(crazy)hvn@archhvn: /tmp/crazy/lib/python2.7/site-packages/docutils () $ find . -type f  -name '*.py' | wc -l
108
Có vẻ nhiều, nhưng bỏ đi các file languages/ thì chỉ còn:
 $ find . -type f  -name '*.py' | grep -v languages | wc -l
60
Các thư mục:
$ find . -type d
.
./languages
./parsers
./parsers/rst
./parsers/rst/directives
./parsers/rst/languages
./parsers/rst/include
./readers
./transforms
./utils
./utils/math
./writers
./writers/html4css1
./writers/pep_html
./writers/s5_html
./writers/s5_html/themes
./writers/s5_html/themes/medium-white
./writers/s5_html/themes/small-black
./writers/s5_html/themes/medium-black
./writers/s5_html/themes/default
./writers/s5_html/themes/small-white
./writers/s5_html/themes/big-black
./writers/s5_html/themes/big-white
./writers/latex2e
./writers/xetex
./writers/odf_odt

Như phần phân tích cách hoạt động, ta thấy code được bố trí thành các sub-packages, mỗi sub-package chứa 1 thành phần. Mỗi file __init__.py trong các subpackage implement các class base cho các file bên trong subpackage đó.

Việc quan sát các thành phần như vừa làm ở trên có thể sử dụng lệnh `tree` (không có thì cài), hay ls -R.

Đếm dòng 10 file có nhiều line nhất

$ find . -name '*.py' -exec wc -l {} \; | sort -nr | head
5249 ./utils/math/math2html.py
3291 ./writers/odf_odt/__init__.py
3080 ./writers/latex2e/__init__.py
3072 ./parsers/rst/states.py
2204 ./nodes.py
1734 ./writers/html4css1/__init__.py
1538 ./statemachine.py
1157 ./writers/manpage.py
905 ./transforms/references.py
904 ./utils/smartquotes.py
Xem ở top-level:
$ find . -maxdepth 1 ! -name '*.pyc'  #(bỏ hết các file *.pyc)
.
./io.py
./core.py # quan trọng nhất , chứa các helper method để dùng docutils
./_compat.py
./statemachine.py # khá là trừu tượng, docutils hoạt động như 1 statemachine
./examples.py # chứa 3 ví dụ đơn giản hướng dẫn dùng docutils ở code level
./__init__.py # chứa các base class chung nhất
./frontend.py # parse config, ...
./nodes.py  # chứa các class về node và tree, bởi docutils đọc ReST vào và tạo thành 1 node tree. Implement visitor theo pattern Visitor của GoF95
./languages # support multiply langs
./parsers # chứa các parser (chỉ có rst )
./readers # chứa các reader
./transforms # chứa các transform
./utils # chứa các function hỗ trợ
./writers # chứa các writer như HTML, XML, ODT ...

Docutils được viết theo OO, dễ dàng tìm được hàng đống class:

 $ grep '^class' *.py
core.py:class Publisher:
frontend.py:class Values(optparse.Values):
frontend.py:class Option(optparse.Option):
frontend.py:class OptionParser(optparse.OptionParser, docutils.SettingsSpec):
frontend.py:class ConfigParser(CP.RawConfigParser):
frontend.py:class ConfigDeprecationWarning(DeprecationWarning):
__init__.py:class ApplicationError(StandardError):
__init__.py:class DataError(ApplicationError): pass
__init__.py:class SettingsSpec:
__init__.py:class TransformSpec:
__init__.py:class Component(SettingsSpec, TransformSpec):
io.py:class InputError(IOError): pass
io.py:class OutputError(IOError): pass
io.py:class Input(TransformSpec):
io.py:class Output(TransformSpec):
...

Ngoài phần example ra, khó có thể biết cách dùng docutils thế nào, may mắn là có sẵn các script CLI mà docutils cung cấp, ta sẽ dùng 1 chương trình debuger để chọc vào xem docutils hoạt động thế nào.

Khám phá
$ ls /tmp/crazy/bin/rst2*
/tmp/crazy/bin/rst2html.py   /tmp/crazy/bin/rst2odt_prepstyles.py  /tmp/crazy/bin/rst2s5.py
/tmp/crazy/bin/rst2latex.py  /tmp/crazy/bin/rst2odt.py             /tmp/crazy/bin/rst2xetex.py
/tmp/crazy/bin/rst2man.py    /tmp/crazy/bin/rst2pseudoxml.py       /tmp/crazy/bin/rst2xml.py


Ta có thể thử với /tmp/crazy/bin/rst2html.py.
Cài ipdb (ipython + pdb) để debug, debugger is your friend!

apt-get install -y python-dev
pip install -y ipdb
Giờ chỉ cần thêm 2 dòng vào trước đoạn code bạn muốn tìm hiểu, chạy script để nó nhảy ra ipdb rồi dùng debugger này để khám phá docutils.

import ipdb
ipdb.set_trace()
Tổng kết
- docutils được sử dụng bởi sphinx, và nhiều project liên quan đến document khác, nên chất lượng code thuộc loại rất tốt.
- docutils có cách parse script argv khá hay, nó không để ở level cao nhất của script mà ẩn ẩn phía dưói.

Ví dụ, đây là nội dung toàn bộ file script rst2html.py

 $ cat /tmp/crazy/bin/rst2html.py
#!/tmp/crazy/bin/python2

# $Id: rst2html.py 4564 2006-05-21 20:44:42Z wiemann $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.

"""
A minimal front end to the Docutils Publisher, producing HTML.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except:
    pass

from docutils.core import publish_cmdline, default_description


description = ('Generates (X)HTML documents from standalone reStructuredText '
               'sources.  ' + default_description)

publish_cmdline(writer_name='html', description=description)

- core.py chứa các publisher, là phần hồn của docutils, nó sử dụng các thành phần reader, parser, transform và writer để tạo thành 1 quy trình hoàn chỉnh.
- trong code có nhiều đoạn dùng "or" để gán default value khá hay:
x = xyz or "5"
Hết.