From 9b66bdd02b4ca18f9b48b8903599bbdef4dd599a Mon Sep 17 00:00:00 2001 From: John MacFarlane Date: Sun, 11 Jan 2015 10:46:51 -0800 Subject: Added cmark.3 man page to repository and archive. It simplifies the build if python and the cmark library aren't needed to build the man page. The top level Makefile has a rule to regenerate this when src/cmark.h changes. Updated Makefile.nmake for recent changes. Added case folding data file to archive, since otherwise make fails. --- Makefile | 26 ++- Makefile.nmake | 18 +- man/CMakeLists.txt | 32 +--- man/make_man_page.py | 4 +- man/man3/cmark.3 | 520 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 548 insertions(+), 52 deletions(-) create mode 100644 man/man3/cmark.3 diff --git a/Makefile b/Makefile index 7f9da9d..0cbf7e8 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ $(PROG): $(BUILDDIR) check: @cmake --version > /dev/null || (echo "You need cmake to build this program: http://www.cmake.org/download/" && exit 1) -$(BUILDDIR): check $(SRCDIR)/html_unescape.h $(SRCDIR)/case_fold_switch.inc +$(BUILDDIR): check $(SRCDIR)/html_unescape.h $(SRCDIR)/case_fold_switch.inc man/man3/cmark.3 mkdir -p $(BUILDDIR); \ cd $(BUILDDIR); \ cmake .. -G "$(GENERATOR)" -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) @@ -50,22 +50,28 @@ mingw: cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw32.cmake -DCMAKE_INSTALL_PREFIX=$(MINGW_INSTALLDIR) ;\ make && make install -archive: spec.html $(BUILDDIR) man/man1/cmark.1 man/make_man_page.py +man/man3/cmark.3: src/cmark.h + mkdir -p man/man3 && \ + python3 man/make_man_page.py $< > $@ + +archive: spec.html man/man1/cmark.1 man/man3/cmark.3 @rm -rf $(PKGDIR); \ - mkdir -p $(PKGDIR)/$(SRCDIR); \ - mkdir -p $(PKGDIR)/api_test $(PKGDIR)/man/man1 $(PKGDIR)/man/man3 ; \ - mkdir -p $(PKGDIR)/test ; \ + mkdir -p $(PKGDIR)/$(SRCDIR) \ + $(PKGDIR)/api_test $(PKGDIR)/man/man1 $(PKGDIR)/man/man3 \ + $(PKGDIR)/test $(PKGDIR)/data ; \ srcfiles=`git ls-tree --full-tree -r HEAD --name-only $(SRCDIR) test api_test`; \ - for f in $$srcfiles; do cp -a $$f $(PKGDIR)/$$f; done; \ + for f in $$srcfiles; \ + do cp -a $$f $(PKGDIR)/$$f; \ + done; \ cp -a $(SRCDIR)/scanners.c $(PKGDIR)/$(SRCDIR)/; \ - cp -a spec.html $(PKGDIR); \ - cp -a man/CMakeLists.txt $(PKGDIR)/man;\ - cp -a man/make_man_page.py $(PKGDIR)/man;\ + cp -a man/CMakeLists.txt man/make_man_page.py $(PKGDIR)/man;\ cp -a man/man1/cmark.1 $(PKGDIR)/man/man1;\ + cp -a man/man3/cmark.3 $(PKGDIR)/man/man3;\ + cp -a data/CaseFolding-3.2.0.txt $(PKGDIR)/data/;\ cp CMakeLists.txt $(PKGDIR); \ perl -ne '$$p++ if /^### JavaScript/; print if (!$$p)' Makefile > $(PKGDIR)/Makefile; \ cp -a Makefile.nmake nmake.bat $(PKGDIR); \ - cp -a README.md COPYING spec.txt $(PKGDIR)/; \ + cp -a README.md COPYING spec.txt spec.html $(PKGDIR)/; \ tar czf $(TARBALL) $(PKGDIR); \ zip -q -r $(ZIPARCHIVE) $(PKGDIR); \ rm -rf $(PKGDIR) ; \ diff --git a/Makefile.nmake b/Makefile.nmake index 54034c4..3f3bbce 100644 --- a/Makefile.nmake +++ b/Makefile.nmake @@ -29,9 +29,6 @@ clean: $(SRCDIR)\case_fold_switch.inc: $(DATADIR)\CaseFolding-3.2.0.txt perl mkcasefold.pl < $? > $@ -man\man1\cmark.1: man\cmark.1.md - pandoc $? -o $@ -s -t man - test: $(SPEC) all @pushd $(BUILDDIR) && $(MAKE) /nologo test ARGS="-V" && popd @@ -41,17 +38,14 @@ distclean: clean ### Spec ### -spec.md: $(SPEC) - perl spec2md.pl < $? > $@ +spec.html: spec.txt template.html $(PROG) + python3 makespec.py html > $@ -spec.html: spec.md template.html - pandoc --no-highlight --number-sections --template template.html -s --toc -S $? | \ - perl -pe "s/a href=\"@([^"]*)\"/a id=\"\\1\" href=\"#\\1\" class=\"definition\"/g" | \ - perl -pe "s/\\x{2423}/ <\\/span>/g" \ - > $@ +spec.md: spec.txt + python3 makespec.py markdown > $@ spec.pdf: spec.md template.tex specfilter.hs - pandoc -s $? --template template.tex \ - --filter specfilter.hs -o $@ --latex-engine=xelatex --toc \ + pandoc -s $< --template template.tex \ + --filter ./specfilter.hs -o $@ --latex-engine=xelatex --toc \ --number-sections -V documentclass=report -V tocdepth=2 \ -V classoption=twosides diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt index 1540812..443996e 100644 --- a/man/CMakeLists.txt +++ b/man/CMakeLists.txt @@ -1,32 +1,8 @@ if (NOT MSVC) - set(PYTHON python) - set(LIBRARY "libcmark") - - add_custom_target(man ALL - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/cmark.3 - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/cmark.1 - ) - - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cmark.3 - DEPENDS ${CMAKE_SOURCE_DIR}/src/cmark.h - DEPENDS ${LIBRARY} - COMMAND ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/make_man_page.py - ${CMAKE_SOURCE_DIR}/src/cmark.h > - ${CMAKE_CURRENT_BINARY_DIR}/cmark.3 - ) - - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cmark.1 - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/man1/cmark.1 - COMMAND cp - ${CMAKE_CURRENT_SOURCE_DIR}/man1/cmark.1 - ${CMAKE_CURRENT_BINARY_DIR}/cmark.1 - ) - - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmark.1 + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/man1/cmark.1 DESTINATION share/man/man1) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmark.3 + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/man3/cmark.3 DESTINATION share/man/man3) -endif(NOT MSVC) \ No newline at end of file +endif(NOT MSVC) + diff --git a/man/make_man_page.py b/man/make_man_page.py index 2202309..fe968b4 100644 --- a/man/make_man_page.py +++ b/man/make_man_page.py @@ -21,9 +21,9 @@ from ctypes import CDLL, c_char_p, c_long, c_void_p sysname = platform.system() if sysname == 'Darwin': - cmark = CDLL("../src/libcmark.dylib") + cmark = CDLL("build/src/libcmark.dylib") else: - cmark = CDLL("../src/libcmark.so") + cmark = CDLL("build/src/libcmark.so") parse_document = cmark.cmark_parse_document parse_document.restype = c_void_p diff --git a/man/man3/cmark.3 b/man/man3/cmark.3 new file mode 100644 index 0000000..5df89c3 --- /dev/null +++ b/man/man3/cmark.3 @@ -0,0 +1,520 @@ +.TH cmark 3 "January 11, 2015" "LOCAL" "Library Functions Manual" +.SH +NAME +.PP +\f[B]cmark\f[] \- CommonMark parsing, manipulating, and rendering + +.SH +DESCRIPTION +.SS +Simple Interface + +.PP +.nf +\fC +.RS 0n +#define CMARK_VERSION "0.1" +.RE +\f[] +.fi + +.PP +Current version of library. + +.PP +\fIchar *\f[] \fBcmark_markdown_to_html\f[](\fIconst char *text\f[], \fIint len\f[]) + +.PP +Convert \f[I]text\f[] (assumed to be a UTF\-8 encoded string with length +\f[I]len\f[] from CommonMark Markdown to HTML, returning a null\-terminated, +UTF\-8\-encoded string. + +.SS +Node Structure + +.SS +Creating and Destroying Nodes + +.PP +\fIcmark_node*\f[] \fBcmark_node_new\f[](\fIcmark_node_type type\f[]) + +.PP +Creates a new node of type \f[I]type\f[]\&. Note that the node may have +other required properties, which it is the caller's responsibility +to assign. + +.PP +\fIvoid\f[] \fBcmark_node_free\f[](\fIcmark_node *node\f[]) + +.PP +Frees the memory allocated for a node. + +.SS +Tree Traversal + +.PP +\fIcmark_node*\f[] \fBcmark_node_next\f[](\fIcmark_node *node\f[]) + +.PP +Returns the next node in the sequence after \f[I]node\f[], or NULL if +there is none. + +.PP +\fIcmark_node*\f[] \fBcmark_node_previous\f[](\fIcmark_node *node\f[]) + +.PP +Returns the previous node in the sequence after \f[I]node\f[], or NULL if +there is none. + +.PP +\fIcmark_node*\f[] \fBcmark_node_parent\f[](\fIcmark_node *node\f[]) + +.PP +Returns the parent of \f[I]node\f[], or NULL if there is none. + +.PP +\fIcmark_node*\f[] \fBcmark_node_first_child\f[](\fIcmark_node *node\f[]) + +.PP +Returns the first child of \f[I]node\f[], or NULL if \f[I]node\f[] has no children. + +.PP +\fIcmark_node*\f[] \fBcmark_node_last_child\f[](\fIcmark_node *node\f[]) + +.PP +Returns the last child of \f[I]node\f[], or NULL if \f[I]node\f[] has no children. + +.SS +Iterator +.PP +An iterator will walk through a tree of nodes, starting from a root +node, returning one node at a time, together with information about +whether the node is being entered or exited. The iterator will +first descend to a child node, if there is one. When there is no +child, the iterator will go to the next sibling. When there is no +next sibling, the iterator will return to the parent (but with +a \f[I]cmark_event_type\f[] of \f[C]CMARK_EVENT_EXIT\f[]). The iterator will +return \f[C]CMARK_EVENT_DONE\f[] when it reaches the root node again. +One natural application is an HTML renderer, where an \f[C]ENTER\f[] event +outputs an open tag and an \f[C]EXIT\f[] event outputs a close tag. +An iterator might also be used to transform an AST in some systematic +way, for example, turning all level\-3 headers into regular paragraphs. +.IP +.nf +\f[C] +void +usage_example(cmark_node *root) { + cmark_event_type ev_type; + cmark_iter *iter = cmark_iter_new(root); + + while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { + cmark_node *cur = cmark_iter_get_node(iter); + // Do something with `cur` and `ev_type` + } + + cmark_iter_free(iter); +} +\f[] +.fi +.PP +Iterators will never return \f[C]EXIT\f[] events for leaf nodes, which are nodes +of type: +.IP \[bu] 2 +CMARK_NODE_HTML +.IP \[bu] 2 +CMARK_NODE_HRULE +.IP \[bu] 2 +CMARK_NODE_CODE_BLOCK +.IP \[bu] 2 +CMARK_NODE_TEXT +.IP \[bu] 2 +CMARK_NODE_SOFTBREAK +.IP \[bu] 2 +CMARK_NODE_LINEBREAK +.IP \[bu] 2 +CMARK_NODE_CODE +.IP \[bu] 2 +CMARK_NODE_INLINE_HTML +.PP +Nodes must only be modified after an \f[C]EXIT\f[] event, or an \f[C]ENTER\f[] event for +leaf nodes. + +.PP +\fIcmark_iter*\f[] \fBcmark_iter_new\f[](\fIcmark_node *root\f[]) + +.PP +Creates a new iterator starting at \f[I]root\f[]\&. The current node and event +type are undefined until \f[C]cmark_iter_next\f[] is called for the first time. + +.PP +\fIvoid\f[] \fBcmark_iter_free\f[](\fIcmark_iter *iter\f[]) + +.PP +Frees the memory allocated for an iterator. + +.PP +\fIcmark_event_type\f[] \fBcmark_iter_next\f[](\fIcmark_iter *iter\f[]) + +.PP +Advances to the next node and returns the event type (\f[C]CMARK_EVENT_ENTER\f[], +\f[C]CMARK_EVENT_EXIT\f[] or \f[C]CMARK_EVENT_DONE\f[]). + +.PP +\fIcmark_node*\f[] \fBcmark_iter_get_node\f[](\fIcmark_iter *iter\f[]) + +.PP +Returns the current node. + +.PP +\fIcmark_event_type\f[] \fBcmark_iter_get_event_type\f[](\fIcmark_iter *iter\f[]) + +.PP +Returns the current event type. + +.PP +\fIvoid\f[] \fBcmark_iter_reset\f[](\fIcmark_iter *iter\f[], \fIcmark_node *current\f[], \fIcmark_event_type event_type\f[]) + +.PP +Resets the iterator so that the current node is \f[I]current\f[] and +the event type is \f[I]event_type\f[]\&. The new current node must be a +descendant of the root node or the root node itself. + +.SS +Accessors + +.PP +\fIcmark_node_type\f[] \fBcmark_node_get_type\f[](\fIcmark_node *node\f[]) + +.PP +Returns the type of \f[I]node\f[], or \f[C]CMARK_NODE_NONE\f[] on error. + +.PP +\fIconst char*\f[] \fBcmark_node_get_type_string\f[](\fIcmark_node *node\f[]) + +.PP +Like \f[I]cmark_node_get_type\f[], but returns a string representation +of the type, or \f[C]""\f[]\&. + +.PP +\fIconst char*\f[] \fBcmark_node_get_literal\f[](\fIcmark_node *node\f[]) + +.PP +Returns the string contents of \f[I]node\f[], or NULL if none. + +.PP +\fIint\f[] \fBcmark_node_set_literal\f[](\fIcmark_node *node\f[], \fIconst char *content\f[]) + +.PP +Sets the string contents of \f[I]node\f[]\&. Returns 1 on success, +0 on failure. + +.PP +\fIint\f[] \fBcmark_node_get_header_level\f[](\fIcmark_node *node\f[]) + +.PP +Returns the header level of \f[I]node\f[], or 0 if \f[I]node\f[] is not a header. + +.PP +\fIint\f[] \fBcmark_node_set_header_level\f[](\fIcmark_node *node\f[], \fIint level\f[]) + +.PP +Sets the header level of \f[I]node\f[], returning 1 on success and 0 on error. + +.PP +\fIcmark_list_type\f[] \fBcmark_node_get_list_type\f[](\fIcmark_node *node\f[]) + +.PP +Returns the list type of \f[I]node\f[], or \f[C]CMARK_NO_LIST\f[] if \f[I]node\f[] +is not a list. + +.PP +\fIint\f[] \fBcmark_node_set_list_type\f[](\fIcmark_node *node\f[], \fIcmark_list_type type\f[]) + +.PP +Sets the list type of \f[I]node\f[], returning 1 on success and 0 on error. + +.PP +\fIcmark_delim_type\f[] \fBcmark_node_get_list_delim\f[](\fIcmark_node *node\f[]) + +.PP +Returns the list delimiter type of \f[I]node\f[], or \f[C]CMARK_NO_DELIM\f[] if \f[I]node\f[] +is not a list. + +.PP +\fIint\f[] \fBcmark_node_set_list_delim\f[](\fIcmark_node *node\f[], \fIcmark_delim_type delim\f[]) + +.PP +Sets the list delimiter type of \f[I]node\f[], returning 1 on success and 0 +on error. + +.PP +\fIint\f[] \fBcmark_node_get_list_start\f[](\fIcmark_node *node\f[]) + +.PP +Returns starting number of \f[I]node\f[], if it is an ordered list, otherwise 0. + +.PP +\fIint\f[] \fBcmark_node_set_list_start\f[](\fIcmark_node *node\f[], \fIint start\f[]) + +.PP +Sets starting number of \f[I]node\f[], if it is an ordered list. Returns 1 +on success, 0 on failure. + +.PP +\fIint\f[] \fBcmark_node_get_list_tight\f[](\fIcmark_node *node\f[]) + +.PP +Returns 1 if \f[I]node\f[] is a tight list, 0 otherwise. + +.PP +\fIint\f[] \fBcmark_node_set_list_tight\f[](\fIcmark_node *node\f[], \fIint tight\f[]) + +.PP +Sets the "tightness" of a list. Returns 1 on success, 0 on failure. + +.PP +\fIconst char*\f[] \fBcmark_node_get_fence_info\f[](\fIcmark_node *node\f[]) + +.PP +Returns the info string from a fenced code block, or NULL if none. + +.PP +\fIint\f[] \fBcmark_node_set_fence_info\f[](\fIcmark_node *node\f[], \fIconst char *info\f[]) + +.PP +Sets the info string in a fenced code block, returning 1 on +success and 0 on failure. + +.PP +\fIconst char*\f[] \fBcmark_node_get_url\f[](\fIcmark_node *node\f[]) + +.PP +Gets the URL of a link or image \f[I]node\f[], or NULL if none. + +.PP +\fIint\f[] \fBcmark_node_set_url\f[](\fIcmark_node *node\f[], \fIconst char *url\f[]) + +.PP +Sets the URL of a link or image \f[I]node\f[]\&. Returns 1 on success, +0 on failure. + +.PP +\fIconst char*\f[] \fBcmark_node_get_title\f[](\fIcmark_node *node\f[]) + +.PP +Gets the title of a link or image \f[I]node\f[], or NULL if none. + +.PP +\fIint\f[] \fBcmark_node_set_title\f[](\fIcmark_node *node\f[], \fIconst char *title\f[]) + +.PP +Sets the title of a link or image \f[I]node\f[]\&. Returns 1 on success, +0 on failure. + +.PP +\fIint\f[] \fBcmark_node_get_start_line\f[](\fIcmark_node *node\f[]) + +.PP +Returns the line on which \f[I]node\f[] begins. + +.PP +\fIint\f[] \fBcmark_node_get_start_column\f[](\fIcmark_node *node\f[]) + +.PP +Returns the column at which \f[I]node\f[] begins. + +.PP +\fIint\f[] \fBcmark_node_get_end_line\f[](\fIcmark_node *node\f[]) + +.PP +Returns the line on which \f[I]node\f[] ends. + +.PP +\fIint\f[] \fBcmark_node_get_end_column\f[](\fIcmark_node *node\f[]) + +.PP +Returns the column at which \f[I]node\f[] ends. + +.SS +Tree Manipulation + +.PP +\fIvoid\f[] \fBcmark_node_unlink\f[](\fIcmark_node *node\f[]) + +.PP +Unlinks a \f[I]node\f[], removing it from the tree, but not freeing its +memory. (Use \f[I]cmark_node_free\f[] for that.) + +.PP +\fIint\f[] \fBcmark_node_insert_before\f[](\fIcmark_node *node\f[], \fIcmark_node *sibling\f[]) + +.PP +Inserts \f[I]sibling\f[] before \f[I]node\f[]\&. Returns 1 on success, 0 on failure. + +.PP +\fIint\f[] \fBcmark_node_insert_after\f[](\fIcmark_node *node\f[], \fIcmark_node *sibling\f[]) + +.PP +Inserts \f[I]sibling\f[] after \f[I]node\f[]\&. Returns 1 on success, 0 on failure. + +.PP +\fIint\f[] \fBcmark_node_prepend_child\f[](\fIcmark_node *node\f[], \fIcmark_node *child\f[]) + +.PP +Adds \f[I]child\f[] to the beginning of the children of \f[I]node\f[]\&. +Returns 1 on success, 0 on failure. + +.PP +\fIint\f[] \fBcmark_node_append_child\f[](\fIcmark_node *node\f[], \fIcmark_node *child\f[]) + +.PP +Adds \f[I]child\f[] to the end of the children of \f[I]node\f[]\&. +Returns 1 on success, 0 on failure. + +.PP +\fIvoid\f[] \fBcmark_consolidate_text_nodes\f[](\fIcmark_node *root\f[]) + +.PP +Consolidates adjacent text nodes. + +.SS +Parsing +.PP +Simple interface: +.IP +.nf +\f[C] +cmark_node *document = cmark_parse_document("Hello *world*", 12); +\f[] +.fi +.PP +Streaming interface: +.IP +.nf +\f[C] +cmark_parser *parser = cmark_parser_new(); +FILE *fp = fopen("myfile.md", "r"); +while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) { + cmark_parser_feed(parser, buffer, bytes); + if (bytes < sizeof(buffer)) { + break; + } +} +document = cmark_parser_finish(parser); +cmark_parser_free(parser); +\f[] +.fi + +.PP +\fIcmark_parser *\f[] \fBcmark_parser_new\f[](\fI\f[]) + +.PP +Creates a new parser object. + +.PP +\fIvoid\f[] \fBcmark_parser_free\f[](\fIcmark_parser *parser\f[]) + +.PP +Frees memory allocated for a parser object. + +.PP +\fIvoid\f[] \fBcmark_parser_feed\f[](\fIcmark_parser *parser\f[], \fIconst char *buffer\f[], \fIsize_t len\f[]) + +.PP +Feeds a string of length \f[I]len\f[] to \f[I]parser\f[]\&. + +.PP +\fIcmark_node *\f[] \fBcmark_parser_finish\f[](\fIcmark_parser *parser\f[]) + +.PP +Finish parsing and return a pointer to a tree of nodes. + +.PP +\fIcmark_node *\f[] \fBcmark_parse_document\f[](\fIconst char *buffer\f[], \fIsize_t len\f[]) + +.PP +Parse a CommonMark document in \f[I]buffer\f[] of length \f[I]len\f[]\&. +Returns a pointer to a tree of nodes. + +.PP +\fIcmark_node *\f[] \fBcmark_parse_file\f[](\fIFILE *f\f[]) + +.PP +Parse a CommonMark document in file \f[I]f\f[], returning a pointer to +a tree of nodes. + +.SS +Rendering + +.PP +\fIchar *\f[] \fBcmark_render_xml\f[](\fIcmark_node *root\f[], \fIlong options\f[]) + +.PP +Render a \f[I]node\f[] tree as XML. + +.PP +\fIchar *\f[] \fBcmark_render_html\f[](\fIcmark_node *root\f[], \fIlong options\f[]) + +.PP +Render a \f[I]node\f[] tree as an HTML fragment. It is up to the user +to add an appropriate header and footer. + +.PP +\fIchar *\f[] \fBcmark_render_man\f[](\fIcmark_node *root\f[], \fIlong options\f[]) + +.PP +Render a \f[I]node\f[] tree as a groff man page, without the header. + +.PP +.nf +\fC +.RS 0n +#define CMARK_OPT_DEFAULT 0 +.RE +\f[] +.fi + +.PP +Default writer options. + +.PP +.nf +\fC +.RS 0n +#define CMARK_OPT_SOURCEPOS 1 +.RE +\f[] +.fi + +.PP +Include a \f[C]data\-sourcepos\f[] attribute on all block elements. + +.PP +.nf +\fC +.RS 0n +#define CMARK_OPT_HARDBREAKS 2 +.RE +\f[] +.fi + +.PP +Render \f[C]softbreak\f[] elements as hard line breaks. + +.PP +.nf +\fC +.RS 0n +#define CMARK_OPT_NORMALIZE 4 +.RE +\f[] +.fi + +.PP +Normalize tree by consolidating adjacent text nodes. + +.SH +AUTHORS +.PP +John MacFarlane, Vicent Marti, Kārlis Gaņģis, Nick Wellnhofer. + -- cgit v1.2.3