From d771d4546edcd8ece04a85368eb140c18f121b8b Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 19 May 2017 12:15:53 -0400 Subject: [PATCH 01/50] Clean slate --- .appveyor.yml | 59 --- .gitignore | 8 +- .travis.yml | 59 +-- BUILDING.md | 65 --- LICENSE.md | 35 +- Makefile | 4 + README.md | 16 +- glide.lock | 18 + glide.yaml | 2 + parser.go | 1 + parser_test.go | 18 + pkg/PKGBUILD | 23 - pkg/README.md | 10 - pkg/_service | 11 - pkg/debian.changelog | 23 - pkg/debian.compat | 1 - pkg/debian.control | 20 - pkg/debian.rules | 3 - pkg/protoc-gen-doc.dsc | 16 - pkg/protoc-gen-doc.spec | 58 --- protoc-gen-doc-win32-zip.pri | 51 -- protoc-gen-doc.pro | 59 --- protoc-gen-doc.qrc | 8 - src/main.cpp | 905 ----------------------------------- src/mustache.cpp | 549 --------------------- src/mustache.h | 276 ----------- 26 files changed, 73 insertions(+), 2225 deletions(-) delete mode 100644 .appveyor.yml delete mode 100644 BUILDING.md create mode 100644 Makefile create mode 100644 glide.lock create mode 100644 glide.yaml create mode 100644 parser.go create mode 100644 parser_test.go delete mode 100644 pkg/PKGBUILD delete mode 100644 pkg/README.md delete mode 100644 pkg/_service delete mode 100644 pkg/debian.changelog delete mode 100644 pkg/debian.compat delete mode 100644 pkg/debian.control delete mode 100644 pkg/debian.rules delete mode 100644 pkg/protoc-gen-doc.dsc delete mode 100644 pkg/protoc-gen-doc.spec delete mode 100644 protoc-gen-doc-win32-zip.pri delete mode 100644 protoc-gen-doc.pro delete mode 100644 protoc-gen-doc.qrc delete mode 100644 src/main.cpp delete mode 100644 src/mustache.cpp delete mode 100644 src/mustache.h diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index f456ba94..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,59 +0,0 @@ -platform: x86 - -cache: - - C:\protobuf-2.6.1 - - C:\upx391w - -init: - - call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x86 - - set PROTOBUF_PREFIX=C:\protobuf-2.6.1 - - set PATH=%PROTOBUF_PREFIX%\vsprojects\Release;%PATH% - - set PATH=C:\Qt\5.5\msvc2013\bin;%PATH% - - set PATH=C:\upx391w;%PATH% - -install: - # Download and build libprotobuf/libprotoc/protoc, if not cached. - - if not exist C:\protobuf-2.6.1 ( - cd C:\ && - curl -L -O https://github.com/google/protobuf/releases/download/v2.6.1/protobuf-2.6.1.zip && - 7z x protobuf-2.6.1.zip && - cd protobuf-2.6.1\vsprojects && - devenv protobuf.sln /Upgrade && - msbuild protobuf.sln /t:libprotobuf;libprotoc;protoc /p:Configuration="Release" /p:Platform="Win32" /p:BuildProjectReferences=false) - - # Download and extract UPX executable packer, if not cached. - - if not exist C:\upx391w ( - cd C:\ && - curl -L -O http://upx.sourceforge.net/download/upx391w.zip && - 7z x upx391w.zip) - -build_script: - - cd "%APPVEYOR_BUILD_FOLDER%" - - qmake CONFIG-=debug - - nmake zip - -test_script: - - cd "%APPVEYOR_BUILD_FOLDER%"/examples - - nmake /F NMakefile clean - - nmake /F NMakefile - - C:\MinGW\msys\1.0\bin\test -s doc/example.html - - C:\MinGW\msys\1.0\bin\test -s doc/example.md - - C:\MinGW\msys\1.0\bin\test -s doc/example.docbook - - C:\MinGW\msys\1.0\bin\test -s doc/example.json - -artifacts: - path: '*.zip' - name: protoc-gen-doc-win32-zip - -deploy: - tag: $(appveyor_repo_tag_name) - release: protoc-gen-doc $(appveyor_repo_tag_name) - description: 'DRAFT' - provider: GitHub - auth_token: - secure: JUtSd7iVOXwTUe295uQ0AL+qf3QVGwvz04eq4avhsLWorhdND+1tIXQuq99TnFd9 - artifact: protoc-gen-doc-win32-zip - draft: true - prerelease: false - on: - appveyor_repo_tag: true diff --git a/.gitignore b/.gitignore index 20e31721..fc5642b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -protoc-gen-doc -/Makefile -*.o -*.pro.user* -qrc_* -.qmake.stash +/protoc-gen-doc +/vendor diff --git a/.travis.yml b/.travis.yml index 7197c565..47cf4e42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,54 +1,15 @@ -language: cpp +language: go +sudo: false -addons: - apt: - packages: - - libqt5core5a - - qt5-qmake - - qt5-default - - libprotobuf-dev - - libprotoc-dev - - protobuf-compiler - - docbook-xsl - - fop - - libservlet2.4-java - -matrix: - include: - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - QMAKESPEC=linux-g++ - - DOCBOOK_XSL=/usr/share/xml/docbook/stylesheet/docbook-xsl/fo/docbook.xsl - - os: linux - dist: trusty - sudo: required - compiler: clang - env: - - QMAKESPEC=linux-clang - - DOCBOOK_XSL=/usr/share/xml/docbook/stylesheet/docbook-xsl/fo/docbook.xsl - - os: osx - compiler: clang - env: DOCBOOK_XSL=/usr/local/opt/docbook-xsl/docbook-xsl/fo/docbook.xsl - -before_install: - - '[[ "$TRAVIS_OS_NAME" != "osx" ]] || brew update' +go: + - 1.8.x + - master install: - - '[[ "$TRAVIS_OS_NAME" != "osx" ]] || brew install qt5 protobuf fop docbook-xsl' - - '[[ "$TRAVIS_OS_NAME" != "osx" ]] || brew link --force qt5' - - '[[ "$TRAVIS_OS_NAME" != "osx" ]] || export PROTOBUF_PREFIX=$(brew --prefix protobuf)' + - curl https://glide.sh/get | sh script: - - qmake - - make - - cd examples - - make clean - - make - - test -s doc/example.html - - test -s doc/example.md - - test -s doc/example.docbook - - test -s doc/example.pdf - - test -s doc/example.json + - make test + +notifications: + email: false diff --git a/BUILDING.md b/BUILDING.md deleted file mode 100644 index b08ecaeb..00000000 --- a/BUILDING.md +++ /dev/null @@ -1,65 +0,0 @@ -# Building the Plugin - -## Prerequisites - -* Protocol Buffers library from Google -* QtCore from Qt 5 - -On Debian/Ubuntu, these packages can be installed with: - - apt install qt5-qmake qt5-default libprotobuf-dev protobuf-compiler libprotoc-dev - -## Linux and BSD - -At a terminal command prompt, run - - $ qmake - $ make - -in the top-level directory to build the plugin. This will produce the plugin -executable (`protoc-gen-doc`). There's no install step, just copy the executable to -where you want it. - -## Windows - -Start a Qt/MSVC command prompt, load `vcvarsall.bat` and then run - - > set PROTOBUF_PREFIX=/path/to/protobuf-2.6.1 - > qmake - > nmake - -in the top-level directory to build the plugin. This will produce the plugin -executable (`release\protoc-gen-doc.exe`). `PROTOBUF_PREFIX` is the path to where the -protobuf library was built. You can create a standalone ZIP distribution with `nmake -zip`. MSVC is currently the only supported compiler on Windows. Building with MinGW -should work, but the `zip` target is not available. I'll try to fix this in the -future. - -## Mac OS X - -### Install Build Tools - -If you do not have Homebrew, install it first, see [here](http://brew.sh) for instructions. - -Then, at a Terminal prompt, run: -``` -brew update -brew install qt5 protobuf -brew link --force qt5 -export PROTOBUF_PREFIX=$(brew --prefix protobuf) -git clone https://github.com/estan/protoc-gen-doc.git -cd protoc-gen-doc -qmake -make -``` - -in the top-level directory to build the plugin. This will produce the plugin -executable (`protoc-gen-doc`). `PROTOBUF_PREFIX` is the path to where the protobuf -library was installed. There's no install step, just copy the executable to where you -want it, or specify the path to `protoc-gen-doc` with --plugin. - -Note that on Mac OS X, the protobuf library should be built with with clang -(`CC=clang` and `CXX=clang++`), or you'll get linker errors. - -If you need even more detailed instructions, you can look at the Travis build file (https://github.com/estan/protoc-gen-doc/blob/master/.travis.yml). The tool is built and tested regularly on Mac OS X, and that file contains the exact steps. - diff --git a/LICENSE.md b/LICENSE.md index 6b7c5b99..7baa276e 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,22 +1,21 @@ -Copyright 2014, 2015, 2016 Elvis Stansvik +MIT License -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Copyright (c) 2017 David Muto (pseudomuto) -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..5accc24f --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: test + +test: + @go test -v -cover . diff --git a/README.md b/README.md index 89be2892..5cfe5305 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # protoc-gen-doc [![Travis Build Status][travis-svg]][travis-ci] -[![Appveyor Build Status][appveyor-svg]][appveyor-ci] This is a documentation generator plugin for the Google Protocol Buffers compiler (`protoc`). The plugin can generate HTML, DocBook and Markdown documentation from @@ -9,14 +8,7 @@ comments in your `.proto` files, as well as a raw JSON representation. ## Installation -* [Debian][obs] -* [Ubuntu][obs] -* [openSUSE][obs] -* [Fedora][obs] -* [Arch Linux][obs] -* [CentOS 7][centos] (x86_64 only) -* [Windows][releases] -* [Build From Source](BUILDING.md) +`go get -u github.com/pseudomuto/protoc-gen-doc` ## Writing Documentation @@ -96,9 +88,3 @@ Look in [examples/Makefile](examples/Makefile) to see how these outputs were bui [travis-ci]: https://travis-ci.org/pseudomuto/protoc-gen-doc "protoc-gen-doc at Travis CI" -[appveyor-svg]: - https://ci.appveyor.com/api/projects/status/ukg7t5qwql70rpmo?svg=true - "Appveyor CI build status SVG" -[appveyor-ci]: - https://ci.appveyor.com/project/pseudomuto/protoc-gen-doc - "protoc-gen-doc at Appveyor CI" diff --git a/glide.lock b/glide.lock new file mode 100644 index 00000000..14c36ae7 --- /dev/null +++ b/glide.lock @@ -0,0 +1,18 @@ +hash: 5b79c13ee13cd926ce49b884477a60de809dccfb01b2f1618c5e6d153d4beca6 +updated: 2017-05-19T12:08:50.915936064-04:00 +imports: [] +testImports: +- name: github.com/davecgh/go-spew + version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/stretchr/testify + version: 4d4bfba8f1d1027c4fdbe371823030df51419987 + subpackages: + - assert + - require + - suite diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 00000000..bdbe7277 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,2 @@ +package: github.com/pseudomuto/protoc-gen-doc +import: [] diff --git a/parser.go b/parser.go new file mode 100644 index 00000000..ccb7fe58 --- /dev/null +++ b/parser.go @@ -0,0 +1 @@ +package protoc_gen_doc diff --git a/parser_test.go b/parser_test.go new file mode 100644 index 00000000..e3196431 --- /dev/null +++ b/parser_test.go @@ -0,0 +1,18 @@ +package protoc_gen_doc_test + +import ( + "github.com/stretchr/testify/suite" + "testing" +) + +type ParserTest struct { + suite.Suite +} + +func TestParser(t *testing.T) { + suite.Run(t, new(ParserTest)) +} + +func (assert *ParserTest) TestTheTruth() { + assert.True(true) +} diff --git a/pkg/PKGBUILD b/pkg/PKGBUILD deleted file mode 100644 index fe69b608..00000000 --- a/pkg/PKGBUILD +++ /dev/null @@ -1,23 +0,0 @@ -# Maintainer: Elvis Stansvik -pkgname=protoc-gen-doc -pkgver=0.9 -pkgrel=1 -pkgdesc='Documentation generator plugin for Google Protocol Buffers' -arch=('i686' 'x86_64') -url='https://github.com/estan/protoc-gen-doc' -license=('BSD') -depends=('qt5-base' 'protobuf') -makedepends=('pkg-config' 'qt5-tools') -source=(v${pkgver}.tar.gz) -md5sums=('SKIP') - -build() { - cd $srcdir/$pkgname-$pkgver - qmake PREFIX=/usr - make -} - -package() { - cd $pkgname-$pkgver - make install INSTALL_ROOT=$pkgdir -} diff --git a/pkg/README.md b/pkg/README.md deleted file mode 100644 index a8450525..00000000 --- a/pkg/README.md +++ /dev/null @@ -1,10 +0,0 @@ -Packaging Files -=============== - -This directory contains files used for creating Linux distribution -packages on the Open Build Service. - - * `debian.*` and `protoc-gen.doc.dsc` are for Debian / Ubuntu. - * `protoc-gen-doc.spec` is for openSUSE / Fedora. - * `PKGBUILD` is for Arch Linux. - * `_service` is the source services file for OBS. diff --git a/pkg/_service b/pkg/_service deleted file mode 100644 index 150eecc2..00000000 --- a/pkg/_service +++ /dev/null @@ -1,11 +0,0 @@ - - - https - github.com - estan/protoc-gen-doc/archive/v0.9.tar.gz - - - *.tar.gz - */pkg/*.spec */pkg/*.dsc */pkg/debian.* */pkg/PKGBUILD - - diff --git a/pkg/debian.changelog b/pkg/debian.changelog deleted file mode 100644 index ce895697..00000000 --- a/pkg/debian.changelog +++ /dev/null @@ -1,23 +0,0 @@ -protoc-gen-doc (0.6-1) stable; urgency=low - - * Initial release with Ubuntu packages. - - -- Elvis Stansvik Wed, 8 Apr 2015 18:50:38 +0100 - -protoc-gen-doc (0.7-1) stable; urgency=low - - * Update to version 0.7. - - -- Elvis Stansvik Thu, 7 Jan 2016 17:22:04 +0100 - -protoc-gen-doc (0.8-1) stable; urgency=low - - * Update to version 0.8. - - -- Elvis Stansvik Fri, 26 Feb 2016 11:05:10 +0100 - -protoc-gen-doc (0.9-1) stable; urgency=low - - * Update to version 0.9. - - -- Elvis Stansvik Sun, 26 Feb 2017 11:22:10 +0100 diff --git a/pkg/debian.compat b/pkg/debian.compat deleted file mode 100644 index ec635144..00000000 --- a/pkg/debian.compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/pkg/debian.control b/pkg/debian.control deleted file mode 100644 index bbe1a6c7..00000000 --- a/pkg/debian.control +++ /dev/null @@ -1,20 +0,0 @@ -Source: protoc-gen-doc -Section: utils -Maintainer: Elvis Stansvik -Build-Depends: debhelper (>= 9), - qt5-qmake, - qt5-default, - qtbase5-dev, - libprotobuf-dev (>= 2.5.0), - libprotoc-dev (>= 2.5.0), - pkg-config -Standards-Version: 3.9.1 -Homepage: https://github.com/estan/protoc-gen-doc - -Package: protoc-gen-doc -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Description: Documentation generator plugin for Google Protocol Buffers - Documentation generator plugin for the Google Protocol Buffers compiler - (protoc). The plugin can generate HTML, DocBook or Markdown documentation - from comments in your .proto files. diff --git a/pkg/debian.rules b/pkg/debian.rules deleted file mode 100644 index cbe925d7..00000000 --- a/pkg/debian.rules +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/make -f -%: - dh $@ diff --git a/pkg/protoc-gen-doc.dsc b/pkg/protoc-gen-doc.dsc deleted file mode 100644 index 955e3dd4..00000000 --- a/pkg/protoc-gen-doc.dsc +++ /dev/null @@ -1,16 +0,0 @@ -Format: 1.0 -Source: protoc-gen-doc -Version: 0.9-1 -Binary: protoc-gen-doc -Maintainer: Elvis Stansvik -Architecture: any -Build-Depends: debhelper (>= 9), - qt5-qmake, - qt5-default, - qtbase5-dev, - libprotobuf-dev (>= 2.5.0), - libprotoc-dev (>= 2.5.0), - pkg-config -Files: - 1234 1234 protoc-gen-doc_0.9.orig.tar.gz - 1234 1234 protoc-gen-doc_0.9-1.diff.tar.gz diff --git a/pkg/protoc-gen-doc.spec b/pkg/protoc-gen-doc.spec deleted file mode 100644 index 6a4ea4fd..00000000 --- a/pkg/protoc-gen-doc.spec +++ /dev/null @@ -1,58 +0,0 @@ -# -# spec file for package protoc-gen-doc -# -# Copyright (c) 2015, 2016 Elvis Stansvik -# -# All modifications and additions to the file contributed by third parties -# remain the property of their copyright owners, unless otherwise agreed -# upon. The license for this file, and modifications and additions to the -# file, is the same license as for the pristine package itself (unless the -# license for the pristine package is not an Open Source License, in which -# case the license is the MIT License). An "Open Source License" is a -# license that conforms to the Open Source Definition (Version 1.9) -# published by the Open Source Initiative. - -Name: protoc-gen-doc -Version: 0.9 -Release: 1%{?dist} -Summary: Documentation generator plugin for Google Protocol Buffers -License: BSD-2-Clause -Url: http://github.com/estan/protoc-gen-doc -Source0: https://github.com/estan/protoc-gen-doc/archive/v%{version}.tar.gz -BuildRequires: pkgconfig(Qt5Core) -BuildRequires: protobuf-devel - -%description -Documentation generator plugin for the Google Protocol Buffers compiler -(protoc). The plugin can generate HTML, DocBook or Markdown documentation -from comments in your .proto files. - -%global debug_package %{nil} - -%prep -%setup -q -n protoc-gen-doc-%{version} - -%build -%if 0%{?suse_version} -%qmake5 PREFIX=%{buildroot}/%{_prefix} -%else -qmake-qt5 PREFIX=%{buildroot}/%{_prefix} -%endif -make %{?_smp_mflags} - -%install -make install - -%files -%defattr(-,root,root) -%{_bindir}/protoc-gen-doc - -%changelog -* Sun Feb 26 2017 Elvis Stansvik - 0.9-1 -- Update to version 0.9. -* Fri Feb 26 2016 Elvis Stansvik - 0.8-1 -- Update to version 0.8. -* Thu Jan 7 2016 Elvis Stansvik - 0.7-1 -- Update to version 0.7. -* Wed Apr 8 2015 Elvis Stansvik - 0.6-1 -- Initial RPM package. diff --git a/protoc-gen-doc-win32-zip.pri b/protoc-gen-doc-win32-zip.pri deleted file mode 100644 index 3a1fca7e..00000000 --- a/protoc-gen-doc-win32-zip.pri +++ /dev/null @@ -1,51 +0,0 @@ -# VS_V is the NNN-format Visual Studio version number. -win32-msvc2010:VS_V = 100 -win32-msvc2012:VS_V = 110 -win32-msvc2013:VS_V = 120 - -# Find the Visual Studio redistributable folder. -VS_TOOLS = $$getenv(VS$${VS_V}COMNTOOLS) -VS_REDIST = $${VS_TOOLS}/../../VC/redist/x86/Microsoft.VC$${VS_V}.CRT - -!exists($${VS_REDIST}) { - error("Could not find Visual C++ redistributable directory!") -} - -# List of files to bundle. -FILES += "release/$${TARGET}.exe" -FILES += "$${VS_REDIST}/msvcr$${VS_V}.dll" -FILES += "$${VS_REDIST}/msvcp$${VS_V}.dll" -FILES += "$$[QT_INSTALL_BINS]/Qt5Core.dll" - -# Settings for the zip target. -GIT_VERSION=$$system(git \ - --git-dir $$shell_quote($$PWD/.git) \ - --work-tree $$shell_quote($$PWD) \ - describe --always --tags) -ZIP_DIR = $${TARGET}-$${GIT_VERSION}-win32 -ZIP_FILE = $${ZIP_DIR}.zip - -# TARGET: zip -# -# 1. Installs FILES to ZIP_DIR -# 2. Compresses all .exe and .dll files with UPX -# 3. Compresses ZIP_DIR into ZIP_DIR.zip with 7zip -# -zip.target = zip -zip.depends = first zipclean -zip.commands = mkdir $${ZIP_DIR} -for (FILE, FILES) { - zip.commands += && copy $$shell_quote($$shell_path($${FILE})) $${ZIP_DIR} -} -zip.commands += && upx --mono -q $${ZIP_DIR}\*.exe $${ZIP_DIR}\*.dll -zip.commands += && 7z a -r $${ZIP_FILE} $${ZIP_DIR} - -# TARGET: cleanzip -# -# Removes ZIP_DIR and ZIP_DIR.zip, if they exist. -# -zipclean.target = zipclean -zipclean.commands = IF EXIST $${ZIP_DIR} rmdir /s /q $${ZIP_DIR} && \ - IF EXIST $${ZIP_FILE} del $${ZIP_FILE} - -QMAKE_EXTRA_TARGETS += zip zipclean diff --git a/protoc-gen-doc.pro b/protoc-gen-doc.pro deleted file mode 100644 index 38a35cea..00000000 --- a/protoc-gen-doc.pro +++ /dev/null @@ -1,59 +0,0 @@ -TEMPLATE = app -VERSION = 0.9 - -CONFIG += console c++11 -CONFIG -= app_bundle -QT -= gui - -HEADERS += src/mustache.h -SOURCES += src/mustache.cpp src/main.cpp -RESOURCES += protoc-gen-doc.qrc - -isEmpty(PREFIX):PREFIX = /usr/local -target.path = $$PREFIX/bin -INSTALLS += target - -lessThan(QT_MAJOR_VERSION, 5):error(This program requires Qt 5.x.) - -linux { - # Use pkg-config to find libprotobuf. - CONFIG += link_pkgconfig - PKGCONFIG = protobuf - - LIBS += -lprotoc # Has no .pc, so add manually. -} - -msvc|mac { - # Get location of protobuf/protoc libraries. - PROTOBUF_PREFIX = $$getenv(PROTOBUF_PREFIX) - isEmpty(PROTOBUF_PREFIX) { - error(You must set the PROTOBUF_PREFIX environment variable!) - } -} - -msvc { - # Add protobuf/protoc paths to INCLUDEPATH and LIBS. - INCLUDEPATH += "$${PROTOBUF_PREFIX}\src" - release:LIBS += "$${PROTOBUF_PREFIX}\vsprojects\Release\libprotobuf.lib" - release:LIBS += "$${PROTOBUF_PREFIX}\vsprojects\Release\libprotoc.lib" - debug:LIBS += "$${PROTOBUF_PREFIX}\vsprojects\Debug\libprotobuf.lib" - debug:LIBS += "$${PROTOBUF_PREFIX}\vsprojects\Debug\libprotoc.lib" - - # Maintain Windows XP compatibility on Visual Studio 2012 and higher. - QMAKE_LFLAGS += /SUBSYSTEM:CONSOLE,5.01 - - # Add zip target in release mode. - release:include(protoc-gen-doc-win32-zip.pri) -} - -mac { - # Add protobuf/protoc paths to INCLUDEPATH and LIBS. - INCLUDEPATH += "$${PROTOBUF_PREFIX}/include" - LIBS += -L$${PROTOBUF_PREFIX}/lib -lprotobuf -lprotoc -} - -# Increase g++ warnings. -*g++*:QMAKE_CXXFLAGS += -Werror -Wall -Wextra - -# Silence clang warnings in old Qt code. -*clang*:lessThan(QT_VERSION, 5.0.3):QMAKE_CXXFLAGS += -Wno-deprecated-register diff --git a/protoc-gen-doc.qrc b/protoc-gen-doc.qrc deleted file mode 100644 index eb76b08b..00000000 --- a/protoc-gen-doc.qrc +++ /dev/null @@ -1,8 +0,0 @@ - - - templates/html.mustache - templates/docbook.mustache - templates/markdown.mustache - templates/scalar_value_types.json - - diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 2d9e2086..00000000 --- a/src/main.cpp +++ /dev/null @@ -1,905 +0,0 @@ -/* - Copyright 2014, 2015, 2016 Elvis Stansvik - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -*/ - -#include "mustache.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace gp = google::protobuf; -namespace ms = Mustache; - -/** - * Context class for the documentation generator. - */ -class DocGeneratorContext { -public: - QString template_; /**< Mustache template, or QString() for raw JSON output */ - QString outputFileName; /**< Output filename. */ - bool noExclude; /**< Ignore @exclude directives? */ - QVariantList files; /**< List of files to render. */ -}; - -/// Documentation generator context instance. -static DocGeneratorContext generatorContext; - -/** - * Returns the "long" name of the message, enum, field or extension described by - * @p descriptor. - * - * The long name is the name of the message, field, enum or extension, preceeded - * by the names of its enclosing types, separated by dots. E.g. for "Baz" it could - * be "Foo.Bar.Baz". - */ -template -static QString longName(const T *descriptor) -{ - if (!descriptor) { - return QString(); - } else if (!descriptor->containing_type()) { - return QString::fromStdString(descriptor->name()); - } - return longName(descriptor->containing_type()) + "." + - QString::fromStdString(descriptor->name()); -} - -// Specialization for T = FieldDescriptor, since we want to follow extension_scope() -// if it's an extension, not containing_type(). -template<> -QString longName(const gp::FieldDescriptor *fieldDescriptor) { - if (fieldDescriptor->is_extension()) { - return longName(fieldDescriptor->extension_scope()) + "." + - QString::fromStdString(fieldDescriptor->name()); - } else { - return longName(fieldDescriptor->containing_type()) + "." + - QString::fromStdString(fieldDescriptor->name()); - } -} - -/** - * Returns true if the variant @p v1 is less than @p v2. - * - * It is assumed that both variants contain a QVariantHash with either - * a "message_long_name", a "message_long_name" or a "extension_long_name" - * key. This comparator is used when sorting the message, enum and - * extension lists for a file. - */ -static inline bool longNameLessThan(const QVariant &v1, const QVariant &v2) -{ - if (v1.toHash()["message_long_name"].toString() < v2.toHash()["message_long_name"].toString()) - return true; - if (v1.toHash()["enum_long_name"].toString() < v2.toHash()["enum_long_name"].toString()) - return true; - return v1.toHash()["extension_long_name"].toString() < v2.toHash()["extension_long_name"].toString(); -} - -/** - * Returns the description of the item described by @p descriptor. - * - * The item can be a message, enum, enum value, extension, field, service or - * service method. - * - * The description is taken as the leading comments followed by the trailing - * comments. If present, a single space is removed from the start of each line. - * Whitespace is trimmed from the final result before it is returned. - * - * If the described item should be excluded from the generated documentation, - * @p exclude is set to true. Otherwise it is set to false. - */ -template -static QString descriptionOf(const T *descriptor, bool &excluded) -{ - QString description; - - gp::SourceLocation sourceLocation; - descriptor->GetSourceLocation(&sourceLocation); - - // Check for leading documentation comments. - QString leading = QString::fromStdString(sourceLocation.leading_comments); - if (leading.startsWith('*') || leading.startsWith('/')) { - leading = leading.mid(1); - leading.replace(QRegularExpression("^ ", QRegularExpression::MultilineOption), ""); - description += leading; - } - - // Check for trailing documentation comments. - QString trailing = QString::fromStdString(sourceLocation.trailing_comments); - if (trailing.startsWith('*') || trailing.startsWith('/')) { - trailing = trailing.mid(1); - trailing.replace(QRegularExpression("^ ", QRegularExpression::MultilineOption), ""); - description += trailing; - } - - // Check if item should be excluded. - description = description.trimmed(); - excluded = false; - if (description.startsWith("@exclude")) { - description = description.mid(8); - excluded = !generatorContext.noExclude; - } - - return description; -} - -/** - * Returns the description of the file described by @p fileDescriptor. - * - * If the first non-whitespace characters in the file is a block of consecutive - * single-line (///) documentation comments, or a multi-line documentation comment, - * the contents of that block of comments or comment is taken as the description of - * the file. If a line inside a multi-line comment starts with "* ", " *" or " * " - * then that prefix is stripped from the line before it is added to the description. - * - * If the file has no description, QString() is returned. If an error occurs, - * @p error is set to point to an error message and QString() is returned. - * - * If the described file should be excluded from the generated documentation, - * @p exclude is set to true. Otherwise it is set to false. - */ -static QString descriptionOf(const gp::FileDescriptor *fileDescriptor, std::string *error, bool &excluded) -{ - // Since there's no API in gp::FileDescriptor for getting the "file - // level" comment, we open the file and extract this out ourselves. - - // Open file. - const QString fileName = QString::fromStdString(fileDescriptor->name()); - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - *error = QString("%1: %2").arg(fileName).arg(file.errorString()).toStdString(); - return QString(); - } - - // Extract the description. - QTextStream stream(&file); - QString description; - while (!stream.atEnd()) { - QString line = stream.readLine().trimmed(); - if (line.isEmpty()) { - continue; - } else if (line.startsWith("///")) { - while (!stream.atEnd() && line.startsWith("///")) { - description += line.mid(line.startsWith("/// ") ? 4 : 3) + '\n'; - line = stream.readLine().trimmed(); - } - description = description.left(description.size() - 1); - } else if (line.startsWith("/**") && !line.startsWith("/***/")) { - line = line.mid(2); - int start, end; - while ((end = line.indexOf("*/")) == -1) { - start = 0; - if (line.startsWith("*")) ++start; - if (line.startsWith("* ")) ++start; - description += line.mid(start) + '\n'; - line = stream.readLine().trimmed(); - } - start = 0; - if (line.startsWith("*") && !line.startsWith("*/")) ++start; - if (line.startsWith("* ")) ++start; - description += line.mid(start, end - start); - } - break; - } - - // Check if the file should be excluded. - description = description.trimmed(); - excluded = false; - if (description.startsWith("@exclude")) { - description = description.mid(8); - excluded = !generatorContext.noExclude; - } - - return description; -} - -/** - * Returns the name of the scalar field type @p type. - */ -static QString scalarTypeName(gp::FieldDescriptor::Type type) -{ - switch (type) { - case gp::FieldDescriptor::TYPE_BOOL: - return "bool"; - case gp::FieldDescriptor::TYPE_BYTES: - return "bytes"; - case gp::FieldDescriptor::TYPE_DOUBLE: - return "double"; - case gp::FieldDescriptor::TYPE_FIXED32: - return "fixed32"; - case gp::FieldDescriptor::TYPE_FIXED64: - return "fixed64"; - case gp::FieldDescriptor::TYPE_FLOAT: - return "float"; - case gp::FieldDescriptor::TYPE_INT32: - return "int32"; - case gp::FieldDescriptor::TYPE_INT64: - return "int64"; - case gp::FieldDescriptor::TYPE_SFIXED32: - return "sfixed32"; - case gp::FieldDescriptor::TYPE_SFIXED64: - return "sfixed64"; - case gp::FieldDescriptor::TYPE_SINT32: - return "sint32"; - case gp::FieldDescriptor::TYPE_SINT64: - return "sint64"; - case gp::FieldDescriptor::TYPE_STRING: - return "string"; - case gp::FieldDescriptor::TYPE_UINT32: - return "uint32"; - case gp::FieldDescriptor::TYPE_UINT64: - return "uint64"; - default: - return ""; - } -} - -/** - * Returns the name of the field label @p label. - */ -static QString labelName(gp::FieldDescriptor::Label label) -{ - switch(label) { - case gp::FieldDescriptor::LABEL_OPTIONAL: - return "optional"; - case gp::FieldDescriptor::LABEL_REPEATED: - return "repeated"; - case gp::FieldDescriptor::LABEL_REQUIRED: - return "required"; - default: - return ""; - } -} - -/** - * Returns the default value for the field described by @p fieldDescriptor. - * - * The field must be of scalar or enum type. If the field has no default value, - * QString() is returned. - */ -static QString defaultValue(const gp::FieldDescriptor *fieldDescriptor) -{ - if (fieldDescriptor->has_default_value()) { - switch (fieldDescriptor->cpp_type()) { - case gp::FieldDescriptor::CPPTYPE_STRING: { - std::string value = fieldDescriptor->default_value_string(); - if (fieldDescriptor->type() == gp::FieldDescriptor::TYPE_STRING) { - return QString("\"%1\"").arg(QString::fromStdString(value)); - } else if (fieldDescriptor->type() == gp::FieldDescriptor::TYPE_BYTES) { - return QString("0x%1").arg(QString::fromUtf8( - QByteArray(value.c_str()).toHex())); - } else { - return "Unknown"; - } - } - case gp::FieldDescriptor::CPPTYPE_BOOL: - return fieldDescriptor->default_value_bool() ? "true" : "false"; - case gp::FieldDescriptor::CPPTYPE_FLOAT: - return QString::number(fieldDescriptor->default_value_float()); - case gp::FieldDescriptor::CPPTYPE_DOUBLE: - return QString::number(fieldDescriptor->default_value_double()); - case gp::FieldDescriptor::CPPTYPE_INT32: - return QString::number(fieldDescriptor->default_value_int32()); - case gp::FieldDescriptor::CPPTYPE_INT64: - return QString::number(fieldDescriptor->default_value_int64()); - case gp::FieldDescriptor::CPPTYPE_UINT32: - return QString::number(fieldDescriptor->default_value_uint32()); - case gp::FieldDescriptor::CPPTYPE_UINT64: - return QString::number(fieldDescriptor->default_value_uint64()); - case gp::FieldDescriptor::CPPTYPE_ENUM: - return QString::fromStdString(fieldDescriptor->default_value_enum()->name()); - default: - return "Unknown"; - } - } else { - return QString(); - } -} - -/** - * Add field to variant list. - * - * Adds the field described by @p fieldDescriptor to the variant list @p fields. - */ -static void addField(const gp::FieldDescriptor *fieldDescriptor, QVariantList *fields) -{ - bool excluded = false; - QString description = descriptionOf(fieldDescriptor, excluded); - - if (excluded) { - return; - } - - QVariantHash field; - - // Add basic info. - field["field_name"] = QString::fromStdString(fieldDescriptor->name()); - field["field_description"] = description; - field["field_label"] = labelName(fieldDescriptor->label()); - field["field_default_value"] = defaultValue(fieldDescriptor); - - // Add type information. - gp::FieldDescriptor::Type type = fieldDescriptor->type(); - if (type == gp::FieldDescriptor::TYPE_MESSAGE || type == gp::FieldDescriptor::TYPE_GROUP) { - // Field is of message / group type. - const gp::Descriptor *descriptor = fieldDescriptor->message_type(); - field["field_type"] = QString::fromStdString(descriptor->name()); - field["field_long_type"] = longName(descriptor); - field["field_full_type"] = QString::fromStdString(descriptor->full_name()); - } else if (type == gp::FieldDescriptor::TYPE_ENUM) { - // Field is of enum type. - const gp::EnumDescriptor *descriptor = fieldDescriptor->enum_type(); - field["field_type"] = QString::fromStdString(descriptor->name()); - field["field_long_type"] = longName(descriptor); - field["field_full_type"] = QString::fromStdString(descriptor->full_name()); - } else { - // Field is of scalar type. - QString typeName(scalarTypeName(type)); - field["field_type"] = typeName; - field["field_long_type"] = typeName; - field["field_full_type"] = typeName; - } - - fields->append(field); -} - -/** - * Add extension to variant list. - * - * Adds the extension described by @p fieldDescriptor to the variant list @p extensions. - */ -static void addExtension(const gp::FieldDescriptor *fieldDescriptor, QVariantList *extensions) -{ - bool excluded = false; - QString description = descriptionOf(fieldDescriptor, excluded); - - if (excluded) { - return; - } - - QVariantHash extension; - - // Add basic info. - extension["extension_name"] = QString::fromStdString(fieldDescriptor->name()); - extension["extension_full_name"] = QString::fromStdString(fieldDescriptor->full_name()); - extension["extension_long_name"] = longName(fieldDescriptor); - extension["extension_description"] = description; - extension["extension_label"] = labelName(fieldDescriptor->label()); - extension["extension_number"] = QString::number(fieldDescriptor->number()); - extension["extension_default_value"] = defaultValue(fieldDescriptor); - - if (fieldDescriptor->is_extension()) { - const gp::Descriptor *descriptor = fieldDescriptor->extension_scope(); - if (descriptor != NULL) { - extension["extension_scope_type"] = QString::fromStdString(descriptor->name()); - extension["extension_scope_long_type"] = longName(descriptor); - extension["extension_scope_full_type"] = QString::fromStdString(descriptor->full_name()); - } - - descriptor = fieldDescriptor->containing_type(); - if (descriptor != NULL) { - extension["extension_containing_type"] = QString::fromStdString(descriptor->name()); - extension["extension_containing_long_type"] = longName(descriptor); - extension["extension_containing_full_type"] = QString::fromStdString(descriptor->full_name()); - } - } - - // Add type information. - gp::FieldDescriptor::Type type = fieldDescriptor->type(); - if (type == gp::FieldDescriptor::TYPE_MESSAGE || type == gp::FieldDescriptor::TYPE_GROUP) { - // Extension is of message / group type. - const gp::Descriptor *descriptor = fieldDescriptor->message_type(); - extension["extension_type"] = QString::fromStdString(descriptor->name()); - extension["extension_long_type"] = longName(descriptor); - extension["extension_full_type"] = QString::fromStdString(descriptor->full_name()); - } else if (type == gp::FieldDescriptor::TYPE_ENUM) { - // Extension is of enum type. - const gp::EnumDescriptor *descriptor = fieldDescriptor->enum_type(); - extension["extension_type"] = QString::fromStdString(descriptor->name()); - extension["extension_long_type"] = longName(descriptor); - extension["extension_full_type"] = QString::fromStdString(descriptor->full_name()); - } else { - // Extension is of scalar type. - QString typeName(scalarTypeName(type)); - extension["extension_type"] = typeName; - extension["extension_long_type"] = typeName; - extension["extension_full_type"] = typeName; - } - - extensions->append(extension); -} - -/** - * Adds the enum described by @p enumDescriptor to the variant list @p enums. - */ -static void addEnum(const gp::EnumDescriptor *enumDescriptor, QVariantList *enums) -{ - bool excluded = false; - QString description = descriptionOf(enumDescriptor, excluded); - - if (excluded) { - return; - } - - QVariantHash enum_; - - // Add basic info. - enum_["enum_name"] = QString::fromStdString(enumDescriptor->name()); - enum_["enum_long_name"] = longName(enumDescriptor); - enum_["enum_full_name"] = QString::fromStdString(enumDescriptor->full_name()); - enum_["enum_description"] = description; - - // Add enum values. - QVariantList values; - for (int i = 0; i < enumDescriptor->value_count(); ++i) { - const gp::EnumValueDescriptor *valueDescriptor = enumDescriptor->value(i); - - bool excluded = false; - QString description = descriptionOf(valueDescriptor, excluded); - - if (excluded) { - continue; - } - - QVariantHash value; - value["value_name"] = QString::fromStdString(valueDescriptor->name()); - value["value_number"] = valueDescriptor->number(); - value["value_description"] = description; - values.append(value); - } - enum_["enum_values"] = values; - - enums->append(enum_); -} - -/** - * Add messages to variant list. - * - * Adds the message described by @p descriptor and all its nested messages and - * enums to the variant list @p messages and @p enums, respectively. - */ -static void addMessages(const gp::Descriptor *descriptor, - QVariantList *messages, - QVariantList *enums) -{ - bool excluded = false; - QString description = descriptionOf(descriptor, excluded); - - if (excluded) { - return; - } - - QVariantHash message; - - // Add basic info. - message["message_name"] = QString::fromStdString(descriptor->name()); - message["message_long_name"] = longName(descriptor); - message["message_full_name"] = QString::fromStdString(descriptor->full_name()); - message["message_description"] = description; - - // Add fields. - QVariantList fields; - for (int i = 0; i < descriptor->field_count(); ++i) { - addField(descriptor->field(i), &fields); - } - message["message_has_fields"] = !fields.isEmpty(); - message["message_fields"] = fields; - - // Add nested extensions. - QVariantList extensions; - for (int i = 0; i < descriptor->extension_count(); ++i) { - addExtension(descriptor->extension(i), &extensions); - } - message["message_has_extensions"] = !extensions.isEmpty(); - message["message_extensions"] = extensions; - - messages->append(message); - - // Add nested messages and enums. - for (int i = 0; i < descriptor->nested_type_count(); ++i) { - addMessages(descriptor->nested_type(i), messages, enums); - } - for (int i = 0; i < descriptor->enum_type_count(); ++i) { - addEnum(descriptor->enum_type(i), enums); - } -} - -/** - * Add services to variant list. - * - * Adds the service described by @p serviceDescriptor and all its methods to the - * variant list @p services. - */ -static void addService(const gp::ServiceDescriptor *serviceDescriptor, QVariantList *services) -{ - bool excluded = false; - QString description = descriptionOf(serviceDescriptor, excluded); - - if (excluded) { - return; - } - - QVariantHash service; - - // Add basic info. - service["service_name"] = QString::fromStdString(serviceDescriptor->name()); - service["service_full_name"] = QString::fromStdString(serviceDescriptor->full_name()); - service["service_description"] = description; - - // Add methods. - QVariantList methods; - for (int i = 0; i < serviceDescriptor->method_count(); ++i) { - const gp::MethodDescriptor *methodDescriptor = serviceDescriptor->method(i); - - bool excluded = false; - QString description = descriptionOf(methodDescriptor, excluded); - - if (excluded) { - continue; - } - - QVariantHash method; - method["method_name"] = QString::fromStdString(methodDescriptor->name()); - method["method_description"] = description; - - // Add type for method input - method["method_request_type"] = QString::fromStdString(methodDescriptor->input_type()->name()); - method["method_request_full_type"] = QString::fromStdString(methodDescriptor->input_type()->full_name()); - method["method_request_long_type"] = longName(methodDescriptor->input_type()); - - // Add type for method output - method["method_response_type"] = QString::fromStdString(methodDescriptor->output_type()->name()); - method["method_response_full_type"] = QString::fromStdString(methodDescriptor->output_type()->full_name()); - method["method_response_long_type"] = longName(methodDescriptor->output_type()); - - methods.append(method); - } - service["service_methods"] = methods; - - services->append(service); -} - -/** - * Add file to variant list. - * - * Adds the file described by @p fileDescriptor to the variant list @p files. - * If an error occurs, @p error is set to point to an error message and the - * function returns immediately. - */ -static void addFile(const gp::FileDescriptor *fileDescriptor, QVariantList *files, std::string *error) -{ - bool excluded = false; - QString description = descriptionOf(fileDescriptor, error, excluded); - - if (excluded) { - return; - } - - QVariantHash file; - - // Add basic info. - file["file_name"] = QFileInfo(QString::fromStdString(fileDescriptor->name())).fileName(); - file["file_description"] = description; - file["file_package"] = QString::fromStdString(fileDescriptor->package()); - - QVariantList messages; - QVariantList enums; - QVariantList services; - QVariantList extensions; - - // Add messages. - for (int i = 0; i < fileDescriptor->message_type_count(); ++i) { - addMessages(fileDescriptor->message_type(i), &messages, &enums); - } - std::sort(messages.begin(), messages.end(), &longNameLessThan); - file["file_messages"] = messages; - - // Add enums. - for (int i = 0; i < fileDescriptor->enum_type_count(); ++i) { - addEnum(fileDescriptor->enum_type(i), &enums); - } - std::sort(enums.begin(), enums.end(), &longNameLessThan); - file["file_enums"] = enums; - - // Add services. - for (int i = 0; i < fileDescriptor->service_count(); ++i) { - addService(fileDescriptor->service(i), &services); - } - std::sort(services.begin(), services.end(), &longNameLessThan); - file["file_has_services"] = !services.isEmpty(); - file["file_services"] = services; - - // Add file-level extensions - for (int i = 0; i < fileDescriptor->extension_count(); ++i) { - addExtension(fileDescriptor->extension(i), &extensions); - } - std::sort(extensions.begin(), extensions.end(), &longNameLessThan); - file["file_has_extensions"] = !extensions.isEmpty(); - file["file_extensions"] = extensions; - - files->append(file); -} - -/** - * Return a formatted template rendering error. - * - * @param template_ Template in which the error occurred. - * @param renderer Template renderer that failed. - * @return Formatted single-line error. - */ -static std::string formattedError(const QString &template_, const ms::Renderer &renderer) -{ - QString location = template_; - if (!renderer.errorPartial().isEmpty()) { - location += " in partial " + renderer.errorPartial(); - } - return QString("%1:%2: %3") - .arg(location) - .arg(renderer.errorPos()) - .arg(renderer.error()).toStdString(); -} - -/** - * Returns the list of formats that are supported out of the box. - */ -static QStringList supportedFormats() -{ - QStringList formats; - QStringList filter = QStringList() << "*.mustache"; - QFileInfoList entries = QDir(":/templates").entryInfoList(filter); - for (const QFileInfo &entry : entries) { - formats.append(entry.baseName()); - } - return formats; -} - -/** - * Returns a usage help string. - */ -static QString usage() -{ - return QString( - "Usage: --doc_out=%1|,[,no-exclude]:") - .arg(supportedFormats().join("|")); -} - -/** - * Returns the template specified by @p name. - * - * The @p name parameter may be either a template file name, or the name of a - * supported format ("html", "docbook", ...). If an error occured, @p error is - * set to point to an error message and QString() returned. - */ -static QString readTemplate(const QString &name, std::string *error) -{ - QString builtInFileName = QString(":/templates/%1.mustache").arg(name); - QString fileName = supportedFormats().contains(name) ? builtInFileName : name; - QFile file(fileName); - - if (!file.open(QIODevice::ReadOnly)) { - *error = QString("%1: %2").arg(fileName).arg(file.errorString()).toStdString(); - return QString(); - } else { - return file.readAll(); - } -} - -/** - * Parses the plugin parameter string. - * - * @param parameter Plugin parameter string. - * @param error Pointer to error if parsing failed. - * @return true on success, otherwise false. - */ -static bool parseParameter(const std::string ¶meter, std::string *error) -{ - QStringList tokens = QString::fromStdString(parameter).split(","); - - if (tokens.size() != 2 && tokens.size() != 3) { - *error = usage().toStdString(); - return false; - } - - bool noExclude = false; - if (tokens.size() == 3) { - if (tokens.at(2) == "no-exclude") { - noExclude = true; - } else { - *error = usage().toStdString(); - return false; - } - } - - if (tokens.at(0) != "json") { - generatorContext.template_ = readTemplate(tokens.at(0), error); - } - generatorContext.outputFileName = tokens.at(1); - generatorContext.noExclude = noExclude; - - return true; -} - -/** - * Template filter for breaking paragraphs into HTML `

` elements. - * - * Renders @p text with @p renderer in @p context and returns the result with - * paragraphs enclosed in `

..

`. - * - */ -static QString pFilter(const QString &text, ms::Renderer* renderer, ms::Context* context) -{ - QRegularExpression re("(\\n|\\r|\\r\\n)\\s*(\\n|\\r|\\r\\n)"); - return "

" + renderer->render(text, context).split(re).join("

") + "

"; -} - -/** - * Template filter for breaking paragraphs into DocBook `` elements. - * - * Renders @p text with @p renderer in @p context and returns the result with - * paragraphs enclosed in `..`. - * - */ -static QString paraFilter(const QString &text, ms::Renderer* renderer, ms::Context* context) -{ - QRegularExpression re("(\\n|\\r|\\r\\n)\\s*(\\n|\\r|\\r\\n)"); - return "" + renderer->render(text, context).split(re).join("") + ""; -} - -/** - * Template filter for removing line breaks. - * - * Renders @p text with @p renderer in @p context and returns the result with - * all occurrances of `\r\n`, `\n`, `\r` removed in that order. - */ -static QString nobrFilter(const QString &text, ms::Renderer* renderer, ms::Context* context) -{ - QString result = renderer->render(text, context); - result.remove("\r\n"); - result.remove("\r"); - result.remove("\n"); - return result; -} - -/** - * Renders the list of files. - * - * Renders files to the directory specified in @p context. If an error occurred, - * @p error is set to point to an error message and no output is written. - * - * @param context Compiler generator context specifying the output directory. - * @param error Pointer to error if rendering failed. - * @return true on success, otherwise false. - */ -static bool render(gp::compiler::GeneratorContext *context, std::string *error) -{ - QVariantHash args; - QString result; - - if (generatorContext.template_.isEmpty()) { - // Raw JSON output. - QJsonDocument document = QJsonDocument::fromVariant(generatorContext.files); - if (document.isNull()) { - *error = "Failed to create JSON document"; - return false; - } - result = QString(document.toJson()); - } else { - // Render using template. - - // Add filters. - args["p"] = QVariant::fromValue(ms::QtVariantContext::fn_t(pFilter)); - args["para"] = QVariant::fromValue(ms::QtVariantContext::fn_t(paraFilter)); - args["nobr"] = QVariant::fromValue(ms::QtVariantContext::fn_t(nobrFilter)); - - // Add files list. - args["files"] = generatorContext.files; - - // Add scalar value types table. - QString fileName(":/templates/scalar_value_types.json"); - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - *error = QString("%1: %2").arg(fileName).arg(file.errorString()).toStdString(); - return false; - } - QJsonDocument document(QJsonDocument::fromJson(file.readAll())); - args["scalar_value_types"] = document.array().toVariantList(); - - // Render template. - ms::Renderer renderer; - ms::QtVariantContext variantContext(args); - result = renderer.render(generatorContext.template_, &variantContext); - - // Check for errors. - if (!renderer.error().isEmpty()) { - *error = formattedError(generatorContext.template_, renderer); - return false; - } - } - - // Write output. - std::string outputFileName = generatorContext.outputFileName.toStdString(); - gp::io::ZeroCopyOutputStream *stream = context->Open(outputFileName); - gp::io::Printer printer(stream, '$'); - printer.PrintRaw(result.toStdString()); - - return true; -} - -/** - * Documentation generator class. - */ -class DocGenerator : public gp::compiler::CodeGenerator -{ - /// Implements google::protobuf::compiler::CodeGenerator. - bool Generate( - const gp::FileDescriptor *fileDescriptor, - const std::string ¶meter, - gp::compiler::GeneratorContext *context, - std::string *error) const - { - std::vector parsedFiles; - context->ListParsedFiles(&parsedFiles); - const bool isFirst = fileDescriptor == parsedFiles.front(); - const bool isLast = fileDescriptor == parsedFiles.back(); - - if (isFirst) { - // Parse the plugin parameter. - if (!parseParameter(parameter, error)) { - return false; - } - } - - // Parse the file. - addFile(fileDescriptor, &generatorContext.files, error); - if (!error->empty()) { - return false; - } - - if (isLast) { - // Render output. - if (!render(context, error)) { - return false; - } - } - - return true; - } -}; - -int main(int argc, char *argv[]) -{ - // Instantiate and invoke the generator plugin. - DocGenerator generator; - return google::protobuf::compiler::PluginMain(argc, argv, &generator); -} diff --git a/src/mustache.cpp b/src/mustache.cpp deleted file mode 100644 index e724fc30..00000000 --- a/src/mustache.cpp +++ /dev/null @@ -1,549 +0,0 @@ -/* - Copyright 2012, Robert Knight - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -*/ - -#include "mustache.h" - -#include -#include -#include -#include - -using namespace Mustache; - -QString Mustache::renderTemplate(const QString& templateString, const QVariantHash& args) -{ - Mustache::QtVariantContext context(args); - Mustache::Renderer renderer; - return renderer.render(templateString, &context); -} - -QString escapeHtml(const QString& input) -{ - QString escaped(input); - for (int i=0; i < escaped.count();) { - const char* replacement = 0; - ushort ch = escaped.at(i).unicode(); - if (ch == '&') { - replacement = "&"; - } else if (ch == '<') { - replacement = "<"; - } else if (ch == '>') { - replacement = ">"; - } else if (ch == '"') { - replacement = """; - } - if (replacement) { - escaped.replace(i, 1, QLatin1String(replacement)); - i += strlen(replacement); - } else { - ++i; - } - } - return escaped; -} - -QString unescapeHtml(const QString& escaped) -{ - QString unescaped(escaped); - unescaped.replace(QLatin1String("<"), QLatin1String("<")); - unescaped.replace(QLatin1String(">"), QLatin1String(">")); - unescaped.replace(QLatin1String("&"), QLatin1String("&")); - unescaped.replace(QLatin1String("""), QLatin1String("\"")); - return unescaped; -} - -Context::Context(PartialResolver* resolver) - : m_partialResolver(resolver) -{} - -PartialResolver* Context::partialResolver() const -{ - return m_partialResolver; -} - -QString Context::partialValue(const QString& key) const -{ - if (!m_partialResolver) { - return QString(); - } - return m_partialResolver->getPartial(key); -} - -bool Context::canEval(const QString&) const -{ - return false; -} - -QString Context::eval(const QString& key, const QString& _template, Renderer* renderer) -{ - Q_UNUSED(key); - Q_UNUSED(_template); - Q_UNUSED(renderer); - - return QString(); -} - -QtVariantContext::QtVariantContext(const QVariant& root, PartialResolver* resolver) - : Context(resolver) -{ - m_contextStack << root; -} - -QVariant variantMapValue(const QVariant& value, const QString& key) -{ - if (value.userType() == QVariant::Map) { - return value.toMap().value(key); - } else { - return value.toHash().value(key); - } -} - -QVariant variantMapValueForKeyPath(const QVariant& value, const QStringList keyPath) -{ - if (keyPath.count() > 1) { - QVariant firstValue = variantMapValue(value, keyPath.first()); - return firstValue.isNull() ? QVariant() : variantMapValueForKeyPath(firstValue, keyPath.mid(1)); - } else if (!keyPath.isEmpty()) { - return variantMapValue(value, keyPath.first()); - } - return QVariant(); -} - -QVariant QtVariantContext::value(const QString& key) const -{ - if (key == "." && !m_contextStack.isEmpty()) { - return m_contextStack.last(); - } - QStringList keyPath = key.split("."); - for (int i = m_contextStack.count()-1; i >= 0; i--) { - QVariant value = variantMapValueForKeyPath(m_contextStack.at(i), keyPath); - if (!value.isNull()) { - return value; - } - } - return QVariant(); -} - -bool QtVariantContext::isFalse(const QString& key) const -{ - QVariant value = this->value(key); - switch (value.userType()) { - case QVariant::Bool: - return !value.toBool(); - case QVariant::List: - return value.toList().isEmpty(); - case QVariant::Hash: - return value.toHash().isEmpty(); - case QVariant::Map: - return value.toMap().isEmpty(); - default: - return value.toString().isEmpty(); - } -} - -QString QtVariantContext::stringValue(const QString& key) const -{ - if (isFalse(key)) { - return QString(); - } - return value(key).toString(); -} - -void QtVariantContext::push(const QString& key, int index) -{ - QVariant mapItem = value(key); - if (index == -1) { - m_contextStack << mapItem; - } else { - QVariantList list = mapItem.toList(); - m_contextStack << list.value(index, QVariant()); - } -} - -void QtVariantContext::pop() -{ - m_contextStack.pop(); -} - -int QtVariantContext::listCount(const QString& key) const -{ - if (value(key).userType() == QVariant::List) { - return value(key).toList().count(); - } - return 0; -} - -bool QtVariantContext::canEval(const QString& key) const -{ - return value(key).canConvert(); -} - -QString QtVariantContext::eval(const QString& key, const QString& _template, Renderer* renderer) -{ - QVariant fn = value(key); - if (fn.isNull()) { - return QString(); - } - return fn.value()(_template, renderer, this); -} - -PartialMap::PartialMap(const QHash& partials) - : m_partials(partials) -{} - -QString PartialMap::getPartial(const QString& name) -{ - return m_partials.value(name); -} - -PartialFileLoader::PartialFileLoader(const QString& basePath) - : m_basePath(basePath) -{} - -QString PartialFileLoader::getPartial(const QString& name) -{ - if (!m_cache.contains(name)) { - QString path = m_basePath + '/' + name + ".mustache"; - QFile file(path); - if (file.open(QIODevice::ReadOnly)) { - QTextStream stream(&file); - m_cache.insert(name, stream.readAll()); - } - } - return m_cache.value(name); -} - -Renderer::Renderer() - : m_errorPos(-1) - , m_defaultTagStartMarker("{{") - , m_defaultTagEndMarker("}}") -{ -} - -QString Renderer::error() const -{ - return m_error; -} - -int Renderer::errorPos() const -{ - return m_errorPos; -} - -QString Renderer::errorPartial() const -{ - return m_errorPartial; -} - -QString Renderer::render(const QString& _template, Context* context) -{ - m_error.clear(); - m_errorPos = -1; - m_errorPartial.clear(); - - m_tagStartMarker = m_defaultTagStartMarker; - m_tagEndMarker = m_defaultTagEndMarker; - - return render(_template, 0, _template.length(), context); -} - -QString Renderer::render(const QString& _template, int startPos, int endPos, Context* context) -{ - QString output; - int lastTagEnd = startPos; - - while (m_errorPos == -1) { - Tag tag = findTag(_template, lastTagEnd, endPos); - if (tag.type == Tag::Null) { - output += _template.midRef(lastTagEnd, endPos - lastTagEnd); - break; - } - output += _template.midRef(lastTagEnd, tag.start - lastTagEnd); - switch (tag.type) { - case Tag::Value: - { - QString value = context->stringValue(tag.key); - if (tag.escapeMode == Tag::Escape) { - value = escapeHtml(value); - } else if (tag.escapeMode == Tag::Unescape) { - value = unescapeHtml(value); - } - output += value; - lastTagEnd = tag.end; - } - break; - case Tag::SectionStart: - { - Tag endTag = findEndTag(_template, tag, endPos); - if (endTag.type == Tag::Null) { - if (m_errorPos == -1) { - setError("No matching end tag found for section", tag.start); - } - } else { - int listCount = context->listCount(tag.key); - if (listCount > 0) { - for (int i=0; i < listCount; i++) { - context->push(tag.key, i); - output += render(_template, tag.end, endTag.start, context); - context->pop(); - } - } else if (context->canEval(tag.key)) { - output += context->eval(tag.key, _template.mid(tag.end, endTag.start - tag.end), this); - } else if (!context->isFalse(tag.key)) { - context->push(tag.key); - output += render(_template, tag.end, endTag.start, context); - context->pop(); - } - lastTagEnd = endTag.end; - } - } - break; - case Tag::InvertedSectionStart: - { - Tag endTag = findEndTag(_template, tag, endPos); - if (endTag.type == Tag::Null) { - if (m_errorPos == -1) { - setError("No matching end tag found for inverted section", tag.start); - } - } else { - if (context->isFalse(tag.key)) { - output += render(_template, tag.end, endTag.start, context); - } - lastTagEnd = endTag.end; - } - } - break; - case Tag::SectionEnd: - setError("Unexpected end tag", tag.start); - lastTagEnd = tag.end; - break; - case Tag::Partial: - { - QString tagStartMarker = m_tagStartMarker; - QString tagEndMarker = m_tagEndMarker; - - m_tagStartMarker = m_defaultTagStartMarker; - m_tagEndMarker = m_defaultTagEndMarker; - - m_partialStack.push(tag.key); - - QString partial = context->partialValue(tag.key); - output += render(partial, 0, partial.length(), context); - lastTagEnd = tag.end; - - m_partialStack.pop(); - - m_tagStartMarker = tagStartMarker; - m_tagEndMarker = tagEndMarker; - } - break; - case Tag::SetDelimiter: - lastTagEnd = tag.end; - break; - case Tag::Comment: - lastTagEnd = tag.end; - break; - case Tag::Null: - break; - } - } - - return output; -} - -void Renderer::setError(const QString& error, int pos) -{ - Q_ASSERT(!error.isEmpty()); - Q_ASSERT(pos >= 0); - - m_error = error; - m_errorPos = pos; - - if (!m_partialStack.isEmpty()) - { - m_errorPartial = m_partialStack.top(); - } -} - -Tag Renderer::findTag(const QString& content, int pos, int endPos) -{ - int tagStartPos = content.indexOf(m_tagStartMarker, pos); - if (tagStartPos == -1 || tagStartPos >= endPos) { - return Tag(); - } - - int tagEndPos = content.indexOf(m_tagEndMarker, tagStartPos + m_tagStartMarker.length()); - if (tagEndPos == -1) { - return Tag(); - } - tagEndPos += m_tagEndMarker.length(); - - Tag tag; - tag.type = Tag::Value; - tag.start = tagStartPos; - tag.end = tagEndPos; - - pos = tagStartPos + m_tagStartMarker.length(); - endPos = tagEndPos - m_tagEndMarker.length(); - - QChar typeChar = content.at(pos); - - if (typeChar == '#') { - tag.type = Tag::SectionStart; - tag.key = readTagName(content, pos+1, endPos); - } else if (typeChar == '^') { - tag.type = Tag::InvertedSectionStart; - tag.key = readTagName(content, pos+1, endPos); - } else if (typeChar == '/') { - tag.type = Tag::SectionEnd; - tag.key = readTagName(content, pos+1, endPos); - } else if (typeChar == '!') { - tag.type = Tag::Comment; - } else if (typeChar == '>') { - tag.type = Tag::Partial; - tag.key = readTagName(content, pos+1, endPos); - } else if (typeChar == '=') { - tag.type = Tag::SetDelimiter; - readSetDelimiter(content, pos+1, tagEndPos - m_tagEndMarker.length()); - } else { - if (typeChar == '&') { - tag.escapeMode = Tag::Unescape; - ++pos; - } else if (typeChar == '{') { - tag.escapeMode = Tag::Raw; - ++pos; - int endTache = content.indexOf('}', pos); - if (endTache == tag.end - m_tagEndMarker.length()) { - ++tag.end; - } else { - endPos = endTache; - } - } - tag.type = Tag::Value; - tag.key = readTagName(content, pos, endPos); - } - - if (tag.type != Tag::Value) { - expandTag(tag, content); - } - - return tag; -} - -QString Renderer::readTagName(const QString& content, int pos, int endPos) -{ - QString name; - name.reserve(endPos - pos); - while (content.at(pos).isSpace()) { - ++pos; - } - while (!content.at(pos).isSpace() && pos < endPos) { - name += content.at(pos); - ++pos; - } - return name; -} - -void Renderer::readSetDelimiter(const QString& content, int pos, int endPos) -{ - QString startMarker; - QString endMarker; - - while (content.at(pos).isSpace() && pos < endPos) { - ++pos; - } - - while (!content.at(pos).isSpace() && pos < endPos) { - if (content.at(pos) == '=') { - setError("Custom delimiters may not contain '='.", pos); - return; - } - startMarker += content.at(pos); - ++pos; - } - - while (content.at(pos).isSpace() && pos < endPos) { - ++pos; - } - - while (!content.at(pos).isSpace() && pos < endPos - 1) { - if (content.at(pos) == '=') { - setError("Custom delimiters may not contain '='.", pos); - return; - } - endMarker += content.at(pos); - ++pos; - } - - m_tagStartMarker = startMarker; - m_tagEndMarker = endMarker; -} - -Tag Renderer::findEndTag(const QString& content, const Tag& startTag, int endPos) -{ - int tagDepth = 1; - int pos = startTag.end; - - while (true) { - Tag nextTag = findTag(content, pos, endPos); - if (nextTag.type == Tag::Null) { - return nextTag; - } else if (nextTag.type == Tag::SectionStart || nextTag.type == Tag::InvertedSectionStart) { - ++tagDepth; - } else if (nextTag.type == Tag::SectionEnd) { - --tagDepth; - if (tagDepth == 0) { - if (nextTag.key != startTag.key) { - setError("Tag start/end key mismatch", nextTag.start); - return Tag(); - } - return nextTag; - } - } - pos = nextTag.end; - } - - return Tag(); -} - -void Renderer::setTagMarkers(const QString& startMarker, const QString& endMarker) -{ - m_defaultTagStartMarker = startMarker; - m_defaultTagEndMarker = endMarker; -} - -void Renderer::expandTag(Tag& tag, const QString& content) -{ - int start = tag.start; - int end = tag.end; - - // Move start to beginning of line. - while (start > 0 && content.at(start - 1) != QLatin1Char('\n')) { - --start; - if (!content.at(start).isSpace()) { - return; // Not standalone. - } - } - - // Move end to one past end of line. - while (end <= content.size() && content.at(end - 1) != QLatin1Char('\n')) { - if (end < content.size() && !content.at(end).isSpace()) { - return; // Not standalone. - } - ++end; - } - - tag.start = start; - tag.end = end; -} diff --git a/src/mustache.h b/src/mustache.h deleted file mode 100644 index fdd0cc2d..00000000 --- a/src/mustache.h +++ /dev/null @@ -1,276 +0,0 @@ -/* - Copyright 2012, Robert Knight - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -*/ - -#pragma once - -#include -#include -#include - -#if __cplusplus >= 201103L -#include /* for std::function */ -#endif - -namespace Mustache -{ - -class PartialResolver; -class Renderer; - -/** Context is an interface that Mustache::Renderer::render() uses to - * fetch substitutions for template tags. - */ -class Context -{ -public: - /** Create a context. @p resolver is used to fetch the expansions for any {{>partial}} tags - * which appear in a template. - */ - explicit Context(PartialResolver* resolver = 0); - virtual ~Context() {} - - /** Returns a string representation of the value for @p key in the current context. - * This is used to replace a Mustache value tag. - */ - virtual QString stringValue(const QString& key) const = 0; - - /** Returns true if the value for @p key is 'false' or an empty list. - * 'False' values typically include empty strings, the boolean value false etc. - * - * When processing a section Mustache tag, the section is not rendered if the key - * is false, or for an inverted section tag, the section is only rendered if the key - * is false. - */ - virtual bool isFalse(const QString& key) const = 0; - - /** Returns the number of items in the list value for @p key or 0 if - * the value for @p key is not a list. - */ - virtual int listCount(const QString& key) const = 0; - - /** Set the current context to the value for @p key. - * If index is >= 0, set the current context to the @p index'th value - * in the list value for @p key. - */ - virtual void push(const QString& key, int index = -1) = 0; - - /** Exit the current context. */ - virtual void pop() = 0; - - /** Returns the partial template for a given @p key. */ - QString partialValue(const QString& key) const; - - /** Returns the partial resolver passed to the constructor. */ - PartialResolver* partialResolver() const; - - /** Returns true if eval() should be used to render section tags using @p key. - * If canEval() returns true for a key, the renderer will pass the literal, unrendered - * block of text for the section to eval() and replace the section with the result. - * - * canEval() and eval() are equivalents for callable objects (eg. lambdas) in other - * Mustache implementations. - * - * The default implementation always returns false. - */ - virtual bool canEval(const QString& key) const; - - /** Callback used to render a template section with the given @p key. - * @p renderer will substitute the original section tag with the result of eval(). - * - * The default implementation returns an empty string. - */ - virtual QString eval(const QString& key, const QString& _template, Renderer* renderer); - -private: - PartialResolver* m_partialResolver; -}; - -/** A context implementation which wraps a QVariantHash or QVariantMap. */ -class QtVariantContext : public Context -{ -public: - /** Construct a QtVariantContext which wraps a dictionary in a QVariantHash - * or a QVariantMap. - */ -#if __cplusplus >= 201103L - typedef std::function fn_t; -#else - typedef QString (*fn_t)(const QString&, Mustache::Renderer*, Mustache::Context*); -#endif - explicit QtVariantContext(const QVariant& root, PartialResolver* resolver = 0); - - virtual QString stringValue(const QString& key) const; - virtual bool isFalse(const QString& key) const; - virtual int listCount(const QString& key) const; - virtual void push(const QString& key, int index = -1); - virtual void pop(); - virtual bool canEval(const QString& key) const; - virtual QString eval(const QString& key, const QString& _template, Mustache::Renderer* renderer); - -private: - QVariant value(const QString& key) const; - - QStack m_contextStack; -}; - -/** Interface for fetching template partials. */ -class PartialResolver -{ -public: - virtual ~PartialResolver() {} - - /** Returns the partial template with a given @p name. */ - virtual QString getPartial(const QString& name) = 0; -}; - -/** A simple partial fetcher which returns templates from a map of (partial name -> template) - */ -class PartialMap : public PartialResolver -{ -public: - explicit PartialMap(const QHash& partials); - - virtual QString getPartial(const QString& name); - -private: - QHash m_partials; -}; - -/** A partial fetcher when loads templates from '.mustache' files - * in a given directory. - * - * Once a partial has been loaded, it is cached for future use. - */ -class PartialFileLoader : public PartialResolver -{ -public: - explicit PartialFileLoader(const QString& basePath); - - virtual QString getPartial(const QString& name); - -private: - QString m_basePath; - QHash m_cache; -}; - -/** Holds properties of a tag in a mustache template. */ -struct Tag -{ - enum Type - { - Null, - Value, /// A {{key}} or {{{key}}} tag - SectionStart, /// A {{#section}} tag - InvertedSectionStart, /// An {{^inverted-section}} tag - SectionEnd, /// A {{/section}} tag - Partial, /// A {{^partial}} tag - Comment, /// A {{! comment }} tag - SetDelimiter /// A {{=<% %>=}} tag - }; - - enum EscapeMode - { - Escape, - Unescape, - Raw - }; - - Tag() - : type(Null) - , start(0) - , end(0) - , escapeMode(Escape) - {} - - Type type; - QString key; - int start; - int end; - EscapeMode escapeMode; -}; - -/** Renders Mustache templates, replacing mustache tags with - * values from a provided context. - */ -class Renderer -{ -public: - Renderer(); - - /** Render a Mustache template, using @p context to fetch - * the values used to replace Mustache tags. - */ - QString render(const QString& _template, Context* context); - - /** Returns a message describing the last error encountered by the previous - * render() call. - */ - QString error() const; - - /** Returns the position in the template where the last error occurred - * when rendering the template or -1 if no error occurred. - * - * If the error occurred in a partial template, the returned position is the offset - * in the partial template. - */ - int errorPos() const; - - /** Returns the name of the partial where the error occurred, or an empty string - * if the error occurred in the main template. - */ - QString errorPartial() const; - - /** Sets the default tag start and end markers. - * This can be overridden within a template. - */ - void setTagMarkers(const QString& startMarker, const QString& endMarker); - -private: - QString render(const QString& _template, int startPos, int endPos, Context* context); - - Tag findTag(const QString& content, int pos, int endPos); - Tag findEndTag(const QString& content, const Tag& startTag, int endPos); - void setError(const QString& error, int pos); - - void readSetDelimiter(const QString& content, int pos, int endPos); - static QString readTagName(const QString& content, int pos, int endPos); - - /** Expands @p tag to fill the line, but only if it is standalone. - * - * The start position is moved to the beginning of the line. The end position is - * moved to one past the end of the line. If @p tag is not standalone, it is - * left unmodified. - * - * A tag is standalone if it is the only non-whitespace token on the the line. - */ - static void expandTag(Tag& tag, const QString& content); - - QStack m_partialStack; - QString m_error; - int m_errorPos; - QString m_errorPartial; - - QString m_tagStartMarker; - QString m_tagEndMarker; - - QString m_defaultTagStartMarker; - QString m_defaultTagEndMarker; -}; - -/** A convenience function which renders a template using the given data. */ -QString renderTemplate(const QString& templateString, const QVariantHash& args); - -}; - -Q_DECLARE_METATYPE(Mustache::QtVariantContext::fn_t) From 99076c8d39c3b1b3f0b3fc43462e670afec5b0c9 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 19 May 2017 13:48:44 -0400 Subject: [PATCH 02/50] Pull glide from github --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 47cf4e42..06289dab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,9 @@ go: - master install: - - curl https://glide.sh/get | sh + - go get -v github.com/Masterminds/glide + - cd $GOPATH/src/github.com/Masterminds/glide && git checkout tags/v0.12.3 && go install && cd - + - glide install script: - make test From bff7178a4bdd7e06eb2a23f4d19f230ecfe8a7ff Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Sun, 18 Jun 2017 10:34:05 -0400 Subject: [PATCH 03/50] Handling proto3 files --- .gitignore | 2 + Makefile | 9 +- fixtures/proto3/Vehicle.proto | 104 +++++++++++ fixtures/proto3_generator_request.dat | Bin 0 -> 4661 bytes generator.go | 5 + glide.lock | 12 +- glide.yaml | 5 +- parser.go | 1 - parser/comments.go | 58 +++++++ parser/models.go | 139 +++++++++++++++ parser/models_test.go | 51 ++++++ parser/parser.go | 241 ++++++++++++++++++++++++++ parser/parser_test.go | 164 ++++++++++++++++++ parser_test.go | 18 -- test/cmd/gen_fixtures/main.go | 21 +++ test/protoc_stubs.go | 21 +++ 16 files changed, 825 insertions(+), 26 deletions(-) create mode 100644 fixtures/proto3/Vehicle.proto create mode 100644 fixtures/proto3_generator_request.dat create mode 100644 generator.go delete mode 100644 parser.go create mode 100644 parser/comments.go create mode 100644 parser/models.go create mode 100644 parser/models_test.go create mode 100644 parser/parser.go create mode 100644 parser/parser_test.go delete mode 100644 parser_test.go create mode 100644 test/cmd/gen_fixtures/main.go create mode 100644 test/protoc_stubs.go diff --git a/.gitignore b/.gitignore index fc5642b5..aa2aac1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +/gen_fixtures /protoc-gen-doc +/test/*.dat /vendor diff --git a/Makefile b/Makefile index 5accc24f..377b3211 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ -.PHONY: test +.PHONY: generate test -test: - @go test -v -cover . +generate: + @go generate + +test: generate + @go test -cover $(shell go list ./... | grep -v -E 'test|tools|vendor') diff --git a/fixtures/proto3/Vehicle.proto b/fixtures/proto3/Vehicle.proto new file mode 100644 index 00000000..cf971e70 --- /dev/null +++ b/fixtures/proto3/Vehicle.proto @@ -0,0 +1,104 @@ +/** + * Messages describing manufacturers / vehicles. + */ +syntax = "proto3"; + +package com.example; + +/** + * The vehicle service. + * + * Manages vehicles and such... + */ +service VehicleService { + // Returns the set of models. + rpc GetModels(EmptyMessage) returns (stream Model); + + rpc AddModels(stream Model) returns (stream Model); // creates models + + /** + * Looks up a vehicle by id. + */ + rpc GetVehicle(FindVehicleById) returns (Vehicle); +} + +/** + * A request message for finding vehicles. + */ +message FindVehicleById { + int32 id = 1; // The id of the vehicle to find. +} + +/** + * Represents a vehicle model. + */ +message Model { + string id = 1; // The unique model ID. + string model_code = 2; // The car model code, e.g. "PZ003". + string model_name = 3; // The car model name, e.g. "Z3". + + sint32 daily_hire_rate_dollars = 4; // Dollars per day. + sint32 daily_hire_rate_cents = 5; // Cents per day. + + Type type = 6; // The type of this model +} + +// An empty message. +message EmptyMessage { +} + +// The type of model. +enum Type { + COUPE = 0; // The type is coupe. + SEDAN = 1; // The type is sedan. +} + +/** + * Represents a manufacturer of cars. + */ +message Manufacturer { + /** + * Manufacturer category. A manufacturer may be either inhouse or external. + */ + enum Category { + CATEGORY_INHOUSE = 0; // The manufacturer is inhouse. + CATEGORY_EXTERNAL = 1; // The manufacturer is external. + } + + int32 id = 1; /** The unique manufacturer ID. */ + string code = 2; // A manufacturer code, e.g. "DKL4P". + string details = 3; // Manufacturer details (minimum orders etc.). + + /** Manufacturer category. */ + Category category = 4; +} + +/** + * Represents a vehicle that can be hired. + */ +message Vehicle { + /** + * Represents a vehicle category. E.g. "Sedan" or "Truck". + */ + message Category { + string code = 1; /// Category code. E.g. "S". + string description = 2; /// Category name. E.g. "Sedan". + } + + int32 id = 1; /** Unique vehicle ID. */ + Model model = 2; /** Vehicle model. */ + string reg_number = 3; /** Vehicle registration number. */ + sint32 mileage = 4; /** Current vehicle mileage, if known. */ + Category category = 5; /** Vehicle category. */ + + // Doc comments for fields can come before or + // after the field definition. And just like + // comments for messages / enums, they can be + // multi-paragraph: + + + // rates + repeated sint32 rates = 6; + + map properties = 7; // bag of properties related to the vehicle. +} diff --git a/fixtures/proto3_generator_request.dat b/fixtures/proto3_generator_request.dat new file mode 100644 index 0000000000000000000000000000000000000000..cff2bb25db05a8b07e05a48b3a7de73fc57dd363 GIT binary patch literal 4661 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 cR7hbbg9OUEntry" fields + assert.field(msg.Fields[6], "properties", "bag of properties related to the vehicle.", "Vehicle.PropertiesEntry", "repeated") +} + +func (assert *ParserTest) TestNestedMessageProperties() { + msg := subject.GetFile("Vehicle.proto").GetMessage("Vehicle.Category") + assert.Equal("Vehicle.Category", msg.Name) + assert.Equal("com.example", msg.Package) + assert.Equal("com.example.Vehicle.Category", msg.FullName()) + assert.Equal("Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", msg.Comment) + assert.True(msg.IsProto3) + assert.Equal(2, len(msg.Fields)) + + assert.field(msg.Fields[0], "code", "Category code. E.g. \"S\".", "string", "") + assert.field(msg.Fields[1], "description", "Category name. E.g. \"Sedan\".", "string", "") +} + +func (assert *ParserTest) field(field *parser.Field, name, comment, typeName, label string) { + assert.Equal(name, field.Name) + assert.Equal(comment, field.Comment) + assert.Equal(typeName, field.Type) + assert.Equal(label, field.Label) + assert.Equal("", field.DefaultValue) +} + +func (assert *ParserTest) TestServiceProperties() { + service := subject.GetFile("Vehicle.proto").GetService("VehicleService") + assert.Equal("VehicleService", service.Name) + assert.Equal("com.example", service.Package) + assert.Equal("com.example.VehicleService", service.FullName()) + assert.Equal("The vehicle service.\n\nManages vehicles and such...", service.Comment) + assert.True(service.IsProto3) + assert.Equal(3, len(service.Methods)) + + names := []string{"GetModels", "AddModels", "GetVehicle"} + comments := []string{"Returns the set of models.", "creates models", "Looks up a vehicle by id."} + clientStreams := []bool{false, true, false} + serverStreams := []bool{true, true, false} + requestTypes := []string{"com.example.EmptyMessage", "com.example.Model", "com.example.FindVehicleById"} + responseTypes := []string{"com.example.Model", "com.example.Model", "com.example.Vehicle"} + + for idx, method := range service.Methods { + assert.Equal(names[idx], method.Name) + assert.Equal(comments[idx], method.Comment) + assert.Equal(clientStreams[idx], method.ClientStreaming) + assert.Equal(serverStreams[idx], method.ServerStreaming) + assert.Equal(requestTypes[idx], method.RequestType) + assert.Equal(responseTypes[idx], method.ResponseType) + assert.Equal("com.example", method.Package) + assert.True(method.IsProto3) + } +} diff --git a/parser_test.go b/parser_test.go deleted file mode 100644 index e3196431..00000000 --- a/parser_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package protoc_gen_doc_test - -import ( - "github.com/stretchr/testify/suite" - "testing" -) - -type ParserTest struct { - suite.Suite -} - -func TestParser(t *testing.T) { - suite.Run(t, new(ParserTest)) -} - -func (assert *ParserTest) TestTheTruth() { - assert.True(true) -} diff --git a/test/cmd/gen_fixtures/main.go b/test/cmd/gen_fixtures/main.go new file mode 100644 index 00000000..dee0322f --- /dev/null +++ b/test/cmd/gen_fixtures/main.go @@ -0,0 +1,21 @@ +// Functions like a normal code generator, except that it won't output anything. Is it used to store the +// CodeGeneratorRequest sent in by protoc for use in unit tests. +// +// Example: +// protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=. fixtures/proto3/*.proto +package main + +import ( + "io/ioutil" + "log" + "os" +) + +func main() { + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalf("Could not read contents from stdin") + } + + ioutil.WriteFile("fixtures/proto3_generator_request.dat", data, 0666) +} diff --git a/test/protoc_stubs.go b/test/protoc_stubs.go new file mode 100644 index 00000000..752612fe --- /dev/null +++ b/test/protoc_stubs.go @@ -0,0 +1,21 @@ +package test + +import ( + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/plugin" + "io/ioutil" +) + +func MakeCodeGeneratorRequest() (*plugin_go.CodeGeneratorRequest, error) { + data, err := ioutil.ReadFile("../fixtures/proto3_generator_request.dat") + if err != nil { + return nil, err + } + + req := new(plugin_go.CodeGeneratorRequest) + if err = proto.Unmarshal(data, req); err != nil { + return nil, err + } + + return req, nil +} From df15f6be1c1902937617f2ba452a9afa69329b7f Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 23 Jun 2017 00:53:26 -0400 Subject: [PATCH 04/50] Handle proto2 parsing --- fixtures/Booking.proto | 72 +++++ fixtures/{proto3 => }/Vehicle.proto | 0 ...ator_request.dat => generator_request.dat} | Bin 4661 -> 7517 bytes generator.go | 2 +- parser/models.go | 31 +- parser/parser.go | 214 +------------ parser/parser_test.go | 132 +------- parser/proto.go | 296 ++++++++++++++++++ parser/proto2_test.go | 156 +++++++++ parser/proto3_test.go | 158 ++++++++++ test/cmd/gen_fixtures/main.go | 2 +- test/protoc_stubs.go | 2 +- 12 files changed, 717 insertions(+), 348 deletions(-) create mode 100644 fixtures/Booking.proto rename fixtures/{proto3 => }/Vehicle.proto (100%) rename fixtures/{proto3_generator_request.dat => generator_request.dat} (60%) create mode 100644 parser/proto.go create mode 100644 parser/proto2_test.go create mode 100644 parser/proto3_test.go diff --git a/fixtures/Booking.proto b/fixtures/Booking.proto new file mode 100644 index 00000000..5f774117 --- /dev/null +++ b/fixtures/Booking.proto @@ -0,0 +1,72 @@ +/** + * Booking related messages. + * + * This file is really just an example. The data model is completely + * fictional. + */ +syntax = "proto2"; + +package com.example; + +/** + * Service for handling vehicle bookings. + */ +service BookingService { + /// Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned. + rpc BookVehicle (Booking) returns (BookingStatus); +} + +/** + * Represents the status of a vehicle booking. + */ +message BookingStatus { + /** + * A flag for the status result. + */ + enum StatusCode { + OK = 200; // OK result. + BAD_REQUEST = 400; // BAD result. + } + + required int32 id = 1; /// Unique booking status ID. + required string description = 2; /// Booking status description. E.g. "Active". + optional StatusCode status_code = 3; /// The status of this status? + + extensions 100 to max; +} + +extend BookingStatus { + /** The country the booking occurred in. */ + optional string country = 100 [default = "china"]; +} + +/** + * The type of booking. + */ +enum BookingType { + IMMEDIATE = 100; // Immediate booking. + FUTURE = 101; // Future booking. +} + +/** + * Represents the booking of a vehicle. + * + * Vehicles are some cool shit. But drive carefully! + */ +message Booking { + required int32 vehicle_id = 1; /// ID of booked vehicle. + required int32 customer_id = 2; /// Customer that booked the vehicle. + required BookingStatus status = 3; /// Status of the booking. + + /** Has booking confirmation been sent? */ + required bool confirmation_sent = 4; + + /** Has payment been received? */ + optional bool payment_received = 5 [default = false]; + + // Nested extentions are also a thing. + + extend BookingStatus { + optional string optional_field_1 = 101; // An optional field to be used however you please. + } +} diff --git a/fixtures/proto3/Vehicle.proto b/fixtures/Vehicle.proto similarity index 100% rename from fixtures/proto3/Vehicle.proto rename to fixtures/Vehicle.proto diff --git a/fixtures/proto3_generator_request.dat b/fixtures/generator_request.dat similarity index 60% rename from fixtures/proto3_generator_request.dat rename to fixtures/generator_request.dat index cff2bb25db05a8b07e05a48b3a7de73fc57dd363..895c20b8a1b214b69b4271172825a27f3fa52742 100644 GIT binary patch delta 2927 zcmaJ@%TgOh6xFmw53YDfW57aSs~H0#a(QISi496ofQ?9q%W*6qt5m7RQVS*-X^3Wo zO*WIt7bNTK@&{RDmk-DfWSRfSGNO0-{o^zkwzpee%&}p$}z4Jyx;ntox z&NtTJ%(xgjo-_IWQ(?*fd2u4&Jg4P4PTvsk%>IQf^xqYliOd{(rZ;l6Sz=bVsBpHK zYGI{u%5{a?8Cn;fsH>GJ*BQB<(-*@w_N6DO7R8@%pib$K4SZmmxq9jUU1&uowfj`)ID?)w z?3*$^ZC4DuqUu9G+sa?xImU+ekn$JiRUgatP;`WKDZ0fJ;+1WrdZz7))?BpRigxP< zly`#2n|8Zr3A@{Vq`ik%QNY?%%b$&v4yDec>Ly|sR?ErNh0tbb>geUm=E2eaNi*No zh|-6rC#S7uUesUHY@94HytFz(+YNQU3}dU!|5sE>H#zySiJ&eTA5*5|j0WED>b=~% zl6xc7IkyI8E7thz&pf3yHJ$iGp|_+=Ysqv%EA^|yBMd$iw&{s3?+e#8&xC6b`TXSE za(U0P1;_tTn6`bzzee`CIp9(88#p9**Yr%@NBPvj5=mobIAx$kYeZh zWS4~EZtM#K2p@y6G^E@@EY|#SxXxgGzfNn!QYhdDiHAz5i29WS!o$Fn z~)%f%1(uPl_7lcL*OJpV< zlVRm5Ot!~%0OhB^Q1-|67)!F>F04dYvOTtewGm(;o(*6v)%bC6VM%`^z$E3es}v?% zqcQeHwnigYj4aC5Xe^aO$!81z4FEh203htJjnYPye`>n1SwwyEaKQW)11`tzE{`lG zfwnV7OTwF-++4f|w4Hp|hzd(e`%$wilBB=Ko|f)}Xwi!z1c+Z9V-OXm_r@S9K=#HU zD6ZsQKBl7rWbfWuL_kIN?5#hECG^kU!;jF&C==Y){sB8o*4O#x!j(6(c;~?}+@)d3 z;ico?Ho@@0zJ>wJH}wN=HIOExXDqNKDawOGHPD8D4%SU)T^BQrTCRS(RS;^1HwV3J@^VyNm=!BD$7 ImHUSv0AeE-xc~qF diff --git a/generator.go b/generator.go index a306cdd2..80e7e567 100644 --- a/generator.go +++ b/generator.go @@ -1,5 +1,5 @@ package protoc_gen_doc //go:generate go build ./test/cmd/gen_fixtures -//go:generate protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=. fixtures/proto3/Vehicle.proto +//go:generate protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=. fixtures/Booking.proto fixtures/Vehicle.proto //go:generate rm gen_fixtures diff --git a/parser/models.go b/parser/models.go index 988377da..2f222b26 100644 --- a/parser/models.go +++ b/parser/models.go @@ -27,15 +27,20 @@ func (po *parsedObject) FullName() string { type File struct { parsedObject - Enums []*Enum - Messages []*Message - Services []*Service + Enums []*Enum + Extensions []*Extension + Messages []*Message + Services []*Service } func (pf *File) getCommentContainers() []commentContainer { containers := []commentContainer{} containers = append(containers, pf) + for _, ext := range pf.Extensions { + containers = append(containers, ext) + } + for _, enum := range pf.Enums { containers = append(containers, enum) for _, value := range enum.Values { @@ -48,6 +53,10 @@ func (pf *File) getCommentContainers() []commentContainer { for _, field := range msg.Fields { containers = append(containers, field) } + + for _, ext := range msg.Extensions { + containers = append(containers, ext) + } } for _, svc := range pf.Services { @@ -117,8 +126,9 @@ type ServiceMethod struct { type Message struct { parsedObject - Fields []*Field - enums []*descriptor.EnumDescriptorProto + Extensions []*Extension + Fields []*Field + enums []*descriptor.EnumDescriptorProto } type Field struct { @@ -128,6 +138,17 @@ type Field struct { DefaultValue string } +type Extension struct { + Field + Number int32 + ContainingType string + ScopeType string +} + +func (ext *Extension) FullName() string { + return fmt.Sprintf("%s.%s", ext.ContainingType, ext.Name) +} + type Enum struct { parsedObject Values []*EnumValue diff --git a/parser/parser.go b/parser/parser.go index 04dd258d..cd3b6a25 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1,32 +1,7 @@ package parser import ( - "fmt" - "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/protoc-gen-go/plugin" - "path" - "strconv" - "strings" -) - -const ( - // tag numbers in FileDescriptorProto - packagePath = 2 // package - messagePath = 4 // message_type - enumPath = 5 // enum_type - servicePath = 6 // service - syntaxPath = 12 // syntax - - // tag numbers in DescriptorProto - messageFieldPath = 2 // field - messageMessagePath = 3 // nested_type - messageEnumPath = 4 // enum_type - - // tag numbers in EnumDescriptorProto - enumValuePath = 2 // value - - // tag numbers in ServiceDescriptorProto - serviceMethodPath = 2 // method ) type ParseResult struct { @@ -47,195 +22,8 @@ func ParseCodeRequest(req *plugin_go.CodeGeneratorRequest) *ParseResult { result := new(ParseResult) for _, file := range req.GetProtoFile() { - result.Files = append(result.Files, parseFile(file)) + result.Files = append(result.Files, parseProtoFile(file)) } return result } - -func parseFile(fd *descriptor.FileDescriptorProto) *File { - pf := &File{ - parsedObject: parsedObject{ - Name: path.Base(fd.GetName()), - Package: fd.GetPackage(), - IsProto3: fd.GetSyntax() == "proto3", - path: strconv.Itoa(syntaxPath), - }, - Messages: parseMessages(fd), - Services: parseServices(fd), - } - - pf.Enums = parseEnums(fd, pf.Messages) - - comments := newCommentExtractor(fd) - comments.extractComments(pf.getCommentContainers()...) - - return pf -} - -func parseServices(fd *descriptor.FileDescriptorProto) []*Service { - descriptors := fd.GetService() - services := make([]*Service, 0, len(descriptors)) - - pkg, isProto3 := fd.GetPackage(), fd.GetSyntax() == "proto3" - - for idx, descriptor := range descriptors { - path := fmt.Sprintf("%d,%d", servicePath, idx) - - services = append(services, &Service{ - parsedObject: parsedObject{ - Name: descriptor.GetName(), - Package: pkg, - IsProto3: isProto3, - path: path, - }, - Methods: parseServiceMethods(descriptor, pkg, path, isProto3), - }) - } - - return services -} - -func parseServiceMethods(sd *descriptor.ServiceDescriptorProto, pkg, path string, isProto3 bool) []*ServiceMethod { - descriptors := sd.GetMethod() - methods := make([]*ServiceMethod, 0, len(descriptors)) - - for idx, method := range descriptors { - methods = append(methods, &ServiceMethod{ - parsedObject: parsedObject{ - Name: method.GetName(), - Package: pkg, - IsProto3: isProto3, - path: fmt.Sprintf("%s,%d,%d", path, serviceMethodPath, idx), - }, - ClientStreaming: method.GetClientStreaming(), - ServerStreaming: method.GetServerStreaming(), - RequestType: strings.TrimPrefix(method.GetInputType(), "."), - ResponseType: strings.TrimPrefix(method.GetOutputType(), "."), - }) - } - - return methods -} - -func parseMessages(fd *descriptor.FileDescriptorProto) []*Message { - descriptors := fd.GetMessageType() - msgs := make([]*Message, 0, len(descriptors)+10) - - for idx, msg := range descriptors { - msgs = parseDescriptor(msgs, msg, nil, fd, idx) - } - - return msgs -} - -func parseDescriptor(sl []*Message, d *descriptor.DescriptorProto, p *Message, fd *descriptor.FileDescriptorProto, idx int) []*Message { - sl = append(sl, &Message{ - parsedObject: parsedObject{ - Name: d.GetName(), - Package: fd.GetPackage(), - IsProto3: fd.GetSyntax() == "proto3", - path: fmt.Sprintf("%d,%d", messagePath, idx), - }, - enums: d.GetEnumType(), - }) - - this := sl[len(sl)-1] - if p != nil { - this.Name = fmt.Sprintf("%s.%s", p.Name, this.Name) - this.path = fmt.Sprintf("%s,%d,%d", p.path, messageMessagePath, idx) - } - - parseFields(this, d.GetField()) - - for i, nested := range d.GetNestedType() { - sl = parseDescriptor(sl, nested, this, fd, i) - } - - return sl -} - -func parseFields(msg *Message, fields []*descriptor.FieldDescriptorProto) { - typeName := func(name string) string { - if strings.HasPrefix(name, ".") { - return strings.TrimPrefix(name, fmt.Sprintf(".%s.", msg.Package)) - } - - return strings.ToLower(strings.TrimPrefix(name, "TYPE_")) - } - - for idx, field := range fields { - msg.Fields = append(msg.Fields, &Field{ - parsedObject: parsedObject{ - Name: field.GetName(), - Package: msg.Package, - IsProto3: msg.IsProto3, - path: fmt.Sprintf("%s,%d,%d", msg.path, messageFieldPath, idx), - }, - Type: typeName(field.GetTypeName()), - }) - - f := msg.Fields[len(msg.Fields)-1] - - if f.Type == "" { - f.Type = typeName(field.GetType().String()) - } - - if f.IsProto3 && field.GetLabel() == descriptor.FieldDescriptorProto_LABEL_REPEATED { - f.Label = "repeated" - } - } -} - -func parseEnums(fd *descriptor.FileDescriptorProto, msgs []*Message) []*Enum { - enums := make([]*Enum, 0, len(fd.GetEnumType())+10) - - for idx, enum := range fd.GetEnumType() { - enums = append(enums, parseEnumDescriptor(enum, nil, fd, idx)) - } - - for _, msg := range msgs { - for idx, enum := range msg.enums { - enums = append(enums, parseEnumDescriptor(enum, msg, fd, idx)) - } - } - - return enums -} - -func parseEnumDescriptor(e *descriptor.EnumDescriptorProto, p *Message, fd *descriptor.FileDescriptorProto, idx int) *Enum { - enum := &Enum{ - parsedObject: parsedObject{ - Name: e.GetName(), - Package: fd.GetPackage(), - IsProto3: fd.GetSyntax() == "proto3", - path: fmt.Sprintf("%d,%d", enumPath, idx), - }, - } - - if p != nil { - enum.Name = fmt.Sprintf("%s.%s", p.Name, enum.Name) - enum.path = fmt.Sprintf("%s,%d,%d", p.path, messageEnumPath, idx) - } - - enum.Values = parseEnumValues(e.GetValue(), enum) - return enum -} - -func parseEnumValues(vd []*descriptor.EnumValueDescriptorProto, enum *Enum) []*EnumValue { - values := make([]*EnumValue, 0, len(vd)) - - for idx, val := range vd { - values = append(values, &EnumValue{ - parsedObject: parsedObject{ - Name: val.GetName(), - Package: enum.Package, - IsProto3: enum.IsProto3, - path: fmt.Sprintf("%s,%d,%d", enum.path, enumValuePath, idx), - }, - Number: int32(idx), - }) - } - - return values -} diff --git a/parser/parser_test.go b/parser/parser_test.go index 1f0ba6a7..83bd2496 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -30,135 +30,13 @@ func (assert *ParserTest) SetupSuite() { } func (assert *ParserTest) TestGetFile() { - assert.NotNil(subject.GetFile("Vehicle.proto")) - assert.Nil(subject.GetFile("Unknown.proto")) -} - -func (assert *ParserTest) TestFileProperties() { file := subject.GetFile("Vehicle.proto") - assert.Equal("Vehicle.proto", file.Name) - assert.Equal("com.example", file.Package) - assert.Equal("Messages describing manufacturers / vehicles.", file.Comment) + assert.NotNil(file) assert.True(file.IsProto3) - for _, msg := range []string{"EmptyMessage", "Manufacturer", "Model", "Vehicle", "Vehicle.Category"} { - assert.True(file.HasMessage(msg)) - } - - for _, enum := range []string{"Manufacturer.Category", "Type"} { - assert.True(file.HasEnum(enum)) - } -} - -func (assert *ParserTest) TestEnumProperties() { - enum := subject.GetFile("Vehicle.proto").GetEnum("Type") - assert.Equal("Type", enum.Name) - assert.Equal("com.example", enum.Package) - assert.Equal("com.example.Type", enum.FullName()) - assert.Equal("The type of model.", enum.Comment) - assert.True(enum.IsProto3) - - value := enum.Values[0] - assert.Equal("COUPE", value.Name) - assert.Equal(int32(0), value.Number) - assert.Equal("com.example", value.Package) - assert.Equal("The type is coupe.", value.Comment) - assert.True(value.IsProto3) - - value = enum.Values[1] - assert.Equal("SEDAN", value.Name) - assert.Equal(int32(1), value.Number) - assert.Equal("com.example", value.Package) - assert.Equal("The type is sedan.", value.Comment) - assert.True(value.IsProto3) -} - -func (assert *ParserTest) TestNestedEnumProperties() { - enum := subject.GetFile("Vehicle.proto").GetEnum("Manufacturer.Category") - assert.Equal("Manufacturer.Category", enum.Name) - assert.Equal("com.example", enum.Package) - assert.Equal("com.example.Manufacturer.Category", enum.FullName()) - assert.Equal("Manufacturer category. A manufacturer may be either inhouse or external.", enum.Comment) - assert.True(enum.IsProto3) - - value := enum.Values[0] - assert.Equal("CATEGORY_INHOUSE", value.Name) - assert.Equal(int32(0), value.Number) - assert.Equal("com.example", value.Package) - assert.Equal("The manufacturer is inhouse.", value.Comment) - assert.True(value.IsProto3) - - value = enum.Values[1] - assert.Equal("CATEGORY_EXTERNAL", value.Name) - assert.Equal(int32(1), value.Number) - assert.Equal("com.example", value.Package) - assert.Equal("The manufacturer is external.", value.Comment) - assert.True(value.IsProto3) -} + file = subject.GetFile("Booking.proto") + assert.NotNil(file) + assert.False(file.IsProto3) -func (assert *ParserTest) TestMessageProperties() { - msg := subject.GetFile("Vehicle.proto").GetMessage("Vehicle") - assert.Equal("Vehicle", msg.Name) - assert.Equal("com.example", msg.Package) - assert.Equal("com.example.Vehicle", msg.FullName()) - assert.Equal("Represents a vehicle that can be hired.", msg.Comment) - assert.True(msg.IsProto3) - assert.Equal(7, len(msg.Fields)) - - assert.field(msg.Fields[0], "id", "Unique vehicle ID.", "int32", "") - assert.field(msg.Fields[1], "model", "Vehicle model.", "Model", "") - assert.field(msg.Fields[4], "category", "Vehicle category.", "Vehicle.Category", "") - assert.field(msg.Fields[5], "rates", "rates", "sint32", "repeated") - - // maps are just repeated "Entry" fields - assert.field(msg.Fields[6], "properties", "bag of properties related to the vehicle.", "Vehicle.PropertiesEntry", "repeated") -} - -func (assert *ParserTest) TestNestedMessageProperties() { - msg := subject.GetFile("Vehicle.proto").GetMessage("Vehicle.Category") - assert.Equal("Vehicle.Category", msg.Name) - assert.Equal("com.example", msg.Package) - assert.Equal("com.example.Vehicle.Category", msg.FullName()) - assert.Equal("Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", msg.Comment) - assert.True(msg.IsProto3) - assert.Equal(2, len(msg.Fields)) - - assert.field(msg.Fields[0], "code", "Category code. E.g. \"S\".", "string", "") - assert.field(msg.Fields[1], "description", "Category name. E.g. \"Sedan\".", "string", "") -} - -func (assert *ParserTest) field(field *parser.Field, name, comment, typeName, label string) { - assert.Equal(name, field.Name) - assert.Equal(comment, field.Comment) - assert.Equal(typeName, field.Type) - assert.Equal(label, field.Label) - assert.Equal("", field.DefaultValue) -} - -func (assert *ParserTest) TestServiceProperties() { - service := subject.GetFile("Vehicle.proto").GetService("VehicleService") - assert.Equal("VehicleService", service.Name) - assert.Equal("com.example", service.Package) - assert.Equal("com.example.VehicleService", service.FullName()) - assert.Equal("The vehicle service.\n\nManages vehicles and such...", service.Comment) - assert.True(service.IsProto3) - assert.Equal(3, len(service.Methods)) - - names := []string{"GetModels", "AddModels", "GetVehicle"} - comments := []string{"Returns the set of models.", "creates models", "Looks up a vehicle by id."} - clientStreams := []bool{false, true, false} - serverStreams := []bool{true, true, false} - requestTypes := []string{"com.example.EmptyMessage", "com.example.Model", "com.example.FindVehicleById"} - responseTypes := []string{"com.example.Model", "com.example.Model", "com.example.Vehicle"} - - for idx, method := range service.Methods { - assert.Equal(names[idx], method.Name) - assert.Equal(comments[idx], method.Comment) - assert.Equal(clientStreams[idx], method.ClientStreaming) - assert.Equal(serverStreams[idx], method.ServerStreaming) - assert.Equal(requestTypes[idx], method.RequestType) - assert.Equal(responseTypes[idx], method.ResponseType) - assert.Equal("com.example", method.Package) - assert.True(method.IsProto3) - } + assert.Nil(subject.GetFile("Unknown.proto")) } diff --git a/parser/proto.go b/parser/proto.go new file mode 100644 index 00000000..5a0b6377 --- /dev/null +++ b/parser/proto.go @@ -0,0 +1,296 @@ +package parser + +import ( + "fmt" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + "path" + "strconv" + "strings" +) + +const ( + // tag numbers in FileDescriptorProto + packagePath = 2 // package + messagePath = 4 // message_type + enumPath = 5 // enum_type + servicePath = 6 // service + extensionPath = 7 // extension + syntaxPath = 12 // syntax + + // tag numbers in DescriptorProto + messageFieldPath = 2 // field + messageMessagePath = 3 // nested_type + messageEnumPath = 4 // enum_type + messageExtensionPath = 6 // extension + + // tag numbers in EnumDescriptorProto + enumValuePath = 2 // value + + // tag numbers in ServiceDescriptorProto + serviceMethodPath = 2 // method +) + +type extensionContainer interface { + GetExtension() []*descriptor.FieldDescriptorProto +} + +type protoParser interface { + Comments() *commentExtractor + Enums() []*Enum + Extensions() []*Extension + FileName() string + IsProto3() bool + Messages() []*Message + Package() string + Services() []*Service +} + +func parseProtoFile(fd *descriptor.FileDescriptorProto) *File { + pp := newProtoParser(fd) + + file := &File{ + parsedObject: parsedObject{ + Name: pp.FileName(), + Package: pp.Package(), + IsProto3: pp.IsProto3(), + path: strconv.Itoa(syntaxPath), + }, + Enums: pp.Enums(), + Extensions: pp.Extensions(), + Messages: pp.Messages(), + Services: pp.Services(), + } + + pp.Comments().extractComments(file.getCommentContainers()...) + + return file +} + +type protoFileParser struct { + fd *descriptor.FileDescriptorProto +} + +func newProtoParser(fd *descriptor.FileDescriptorProto) protoParser { + return &protoFileParser{fd: fd} +} + +func (pp *protoFileParser) Comments() *commentExtractor { + return newCommentExtractor(pp.fd) +} + +func (pp *protoFileParser) Enums() []*Enum { + descriptors := pp.fd.GetEnumType() + enums := make([]*Enum, 0, len(descriptors)+10) + + for idx, enum := range descriptors { + enums = append(enums, pp.parseEnumDescriptor(enum, nil, idx)) + } + + for _, msg := range pp.Messages() { + for idx, enum := range msg.enums { + enums = append(enums, pp.parseEnumDescriptor(enum, msg, idx)) + } + } + + return enums +} + +func (pp *protoFileParser) parseEnumDescriptor(e *descriptor.EnumDescriptorProto, p *Message, idx int) *Enum { + enum := &Enum{ + parsedObject: parsedObject{ + Name: e.GetName(), + Package: pp.Package(), + IsProto3: pp.IsProto3(), + path: fmt.Sprintf("%d,%d", enumPath, idx), + }, + } + + if p != nil { + enum.Name = fmt.Sprintf("%s.%s", p.Name, enum.Name) + enum.path = fmt.Sprintf("%s,%d,%d", p.path, messageEnumPath, idx) + } + + enum.Values = pp.parseEnumValues(e.GetValue(), enum) + return enum +} + +func (pp *protoFileParser) parseEnumValues(vd []*descriptor.EnumValueDescriptorProto, enum *Enum) []*EnumValue { + values := make([]*EnumValue, 0, len(vd)) + + for idx, val := range vd { + values = append(values, &EnumValue{ + parsedObject: parsedObject{ + Name: val.GetName(), + Package: enum.Package, + IsProto3: enum.IsProto3, + path: fmt.Sprintf("%s,%d,%d", enum.path, enumValuePath, idx), + }, + Number: val.GetNumber(), + }) + } + + return values +} + +func (pp *protoFileParser) Extensions() []*Extension { + return pp.parseExtensions(pp.fd, "", strconv.Itoa(extensionPath)) +} + +func (pp *protoFileParser) FileName() string { + return path.Base(pp.fd.GetName()) +} + +func (pp *protoFileParser) IsProto3() bool { + return pp.fd.GetSyntax() == "proto3" +} + +func (pp *protoFileParser) Messages() []*Message { + descriptors := pp.fd.GetMessageType() + messages := make([]*Message, 0, len(descriptors)) + + for idx, msg := range descriptors { + messages = pp.parseDescriptor(messages, msg, nil, idx) + } + + return messages +} + +func (pp *protoFileParser) parseDescriptor(sl []*Message, d *descriptor.DescriptorProto, p *Message, idx int) []*Message { + sl = append(sl, &Message{ + parsedObject: parsedObject{ + Name: d.GetName(), + Package: pp.Package(), + IsProto3: pp.IsProto3(), + path: fmt.Sprintf("%d,%d", messagePath, idx), + }, + enums: d.GetEnumType(), + }) + + this := sl[len(sl)-1] + if p != nil { // nested? + this.Name = fmt.Sprintf("%s.%s", p.Name, this.Name) + this.path = fmt.Sprintf("%s,%d,%d", p.path, messageMessagePath, idx) + } + + this.Fields = pp.parseFields(d.GetField(), this.path) + this.Extensions = pp.parseExtensions(d, this.FullName(), fmt.Sprintf("%s,%d", this.path, messageExtensionPath)) + + // parse nested message types + for i, nested := range d.GetNestedType() { + sl = pp.parseDescriptor(sl, nested, this, i) + } + + return sl +} + +func (pp *protoFileParser) parseFields(fdp []*descriptor.FieldDescriptorProto, basePath string) []*Field { + typeName := func(name string) string { + if strings.HasPrefix(name, ".") { + return strings.TrimPrefix(name, fmt.Sprintf(".%s.", pp.Package())) + } + + return strings.ToLower(strings.TrimPrefix(name, "TYPE_")) + } + + fields := make([]*Field, 0, len(fdp)) + + for idx, field := range fdp { + fields = append(fields, &Field{ + parsedObject: parsedObject{ + Name: field.GetName(), + Package: pp.Package(), + IsProto3: pp.IsProto3(), + path: fmt.Sprintf("%s,%d,%d", basePath, messageFieldPath, idx), + }, + Label: pp.labelName(field.GetLabel()), + Type: typeName(field.GetTypeName()), + }) + + f := fields[len(fields)-1] + + if f.Type == "" { + f.Type = typeName(field.GetType().String()) + } + } + + return fields +} + +func (pp *protoFileParser) parseExtensions(ec extensionContainer, scopeType, basePath string) []*Extension { + descriptors := ec.GetExtension() + extensions := make([]*Extension, 0, len(descriptors)) + + for idx, ext := range descriptors { + extensions = append(extensions, &Extension{ + Field: Field{ + parsedObject: parsedObject{ + Name: ext.GetName(), + Package: pp.Package(), + IsProto3: pp.IsProto3(), + path: fmt.Sprintf("%s,%d", basePath, idx), + }, + DefaultValue: ext.GetDefaultValue(), + }, + ContainingType: strings.TrimPrefix(ext.GetExtendee(), "."), + ScopeType: scopeType, + Number: ext.GetNumber(), + }) + } + + return extensions +} + +func (pp *protoFileParser) labelName(fd descriptor.FieldDescriptorProto_Label) string { + if pp.IsProto3() && fd != descriptor.FieldDescriptorProto_LABEL_REPEATED { + return "" + } + + return strings.ToLower(strings.TrimPrefix(fd.String(), "LABEL_")) +} + +func (pp *protoFileParser) Package() string { + return pp.fd.GetPackage() +} + +func (pp *protoFileParser) Services() []*Service { + descriptors := pp.fd.GetService() + services := make([]*Service, 0, len(descriptors)) + + for idx, descriptor := range descriptors { + path := fmt.Sprintf("%d,%d", servicePath, idx) + + services = append(services, &Service{ + parsedObject: parsedObject{ + Name: descriptor.GetName(), + Package: pp.Package(), + IsProto3: pp.IsProto3(), + path: path, + }, + Methods: pp.parseServiceMethods(descriptor, path), + }) + } + + return services +} + +func (pp *protoFileParser) parseServiceMethods(sd *descriptor.ServiceDescriptorProto, basePath string) []*ServiceMethod { + descriptors := sd.GetMethod() + methods := make([]*ServiceMethod, 0, len(descriptors)) + + for idx, method := range descriptors { + methods = append(methods, &ServiceMethod{ + parsedObject: parsedObject{ + Name: method.GetName(), + Package: pp.Package(), + IsProto3: pp.IsProto3(), + path: fmt.Sprintf("%s,%d,%d", basePath, serviceMethodPath, idx), + }, + ClientStreaming: method.GetClientStreaming(), + ServerStreaming: method.GetServerStreaming(), + RequestType: strings.TrimPrefix(method.GetInputType(), "."), + ResponseType: strings.TrimPrefix(method.GetOutputType(), "."), + }) + } + + return methods +} diff --git a/parser/proto2_test.go b/parser/proto2_test.go new file mode 100644 index 00000000..95cda075 --- /dev/null +++ b/parser/proto2_test.go @@ -0,0 +1,156 @@ +package parser_test + +import ( + "github.com/pseudomuto/protoc-gen-doc/parser" + "github.com/pseudomuto/protoc-gen-doc/test" + "github.com/stretchr/testify/suite" + "testing" +) + +var ( + proto2File *parser.File +) + +type Proto2ParserTest struct { + suite.Suite +} + +func TestProto2Parser(t *testing.T) { + suite.Run(t, new(Proto2ParserTest)) +} + +func (assert *Proto2ParserTest) SetupSuite() { + codeGenRequest, err := test.MakeCodeGeneratorRequest() + assert.Nil(err) + + proto2File = parser.ParseCodeRequest(codeGenRequest).GetFile("Booking.proto") +} + +func (assert *Proto2ParserTest) TestFileProperties() { + assert.Equal("Booking.proto", proto2File.Name) + assert.Equal("com.example", proto2File.Package) + assert.Equal("Booking related messages.\n\nThis file is really just an example. The data model is completely\nfictional.", proto2File.Comment) + assert.False(proto2File.IsProto3) + assert.Equal(1, len(proto2File.Extensions)) + + for _, msg := range []string{"Booking", "BookingStatus"} { + assert.True(proto2File.HasMessage(msg)) + } + + for _, enum := range []string{"BookingType", "BookingStatus.StatusCode"} { + assert.True(proto2File.HasEnum(enum)) + } +} + +func (assert *Proto2ParserTest) TestFileExtensionProperties() { + ext := proto2File.Extensions[0] + assert.Equal(int32(100), ext.Number) + assert.Equal("country", ext.Name) + assert.Equal("com.example", ext.Package) + assert.Equal("com.example.BookingStatus.country", ext.FullName()) + assert.Equal("The country the booking occurred in.", ext.Comment) + assert.Equal("china", ext.DefaultValue) + assert.Equal("com.example.BookingStatus", ext.ContainingType) + assert.Equal("", ext.ScopeType) + assert.False(ext.IsProto3) +} + +func (assert *Proto2ParserTest) TestEnumProperties() { + enum := proto2File.GetEnum("BookingType") + assert.Equal("BookingType", enum.Name) + assert.Equal("com.example", enum.Package) + assert.Equal("com.example.BookingType", enum.FullName()) + assert.Equal("The type of booking.", enum.Comment) + assert.False(enum.IsProto3) + + value := enum.Values[0] + assert.Equal("IMMEDIATE", value.Name) + assert.Equal(int32(100), value.Number) + assert.Equal("com.example", value.Package) + assert.Equal("Immediate booking.", value.Comment) + assert.False(value.IsProto3) + + value = enum.Values[1] + assert.Equal("FUTURE", value.Name) + assert.Equal(int32(101), value.Number) + assert.Equal("com.example", value.Package) + assert.Equal("Future booking.", value.Comment) + assert.False(value.IsProto3) +} + +func (assert *Proto2ParserTest) TestNestedEnumProperties() { + enum := proto2File.GetEnum("BookingStatus.StatusCode") + assert.Equal("BookingStatus.StatusCode", enum.Name) + assert.Equal("com.example", enum.Package) + assert.Equal("com.example.BookingStatus.StatusCode", enum.FullName()) + assert.Equal("A flag for the status result.", enum.Comment) + assert.False(enum.IsProto3) + + value := enum.Values[0] + assert.Equal("OK", value.Name) + assert.Equal(int32(200), value.Number) + assert.Equal("com.example", value.Package) + assert.Equal("OK result.", value.Comment) + assert.False(value.IsProto3) + + value = enum.Values[1] + assert.Equal("BAD_REQUEST", value.Name) + assert.Equal(int32(400), value.Number) + assert.Equal("com.example", value.Package) + assert.Equal("BAD result.", value.Comment) + assert.False(value.IsProto3) +} + +func (assert *Proto2ParserTest) TestMessageProperties() { + msg := proto2File.GetMessage("BookingStatus") + assert.Equal("BookingStatus", msg.Name) + assert.Equal("com.example", msg.Package) + assert.Equal("com.example.BookingStatus", msg.FullName()) + assert.Equal("Represents the status of a vehicle booking.", msg.Comment) + assert.False(msg.IsProto3) + assert.Equal(3, len(msg.Fields)) + + assert.field(msg.Fields[0], "id", "Unique booking status ID.", "int32", "required") + assert.field(msg.Fields[2], "status_code", "The status of this status?", "BookingStatus.StatusCode", "optional") +} + +func (assert *Proto2ParserTest) TestMessageExtensionProperties() { + ext := proto2File.GetMessage("Booking").Extensions[0] + assert.Equal(int32(101), ext.Number) + assert.Equal("optional_field_1", ext.Name) + assert.Equal("com.example", ext.Package) + assert.Equal("com.example.BookingStatus.optional_field_1", ext.FullName()) + assert.Equal("An optional field to be used however you please.", ext.Comment) + assert.Equal("", ext.DefaultValue) + assert.Equal("com.example.BookingStatus", ext.ContainingType) + assert.Equal("com.example.Booking", ext.ScopeType) + assert.False(ext.IsProto3) +} + +func (assert *Proto2ParserTest) TestServiceProperties() { + service := proto2File.GetService("BookingService") + assert.Equal("BookingService", service.Name) + assert.Equal("com.example", service.Package) + assert.Equal("com.example.BookingService", service.FullName()) + assert.Equal("Service for handling vehicle bookings.", service.Comment) + assert.False(service.IsProto3) + assert.Equal(1, len(service.Methods)) + + method := service.Methods[0] + assert.Equal("BookVehicle", method.Name) + assert.Equal("Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.", method.Comment) + assert.False(method.ClientStreaming) + assert.False(method.ServerStreaming) + assert.Equal("com.example.Booking", method.RequestType) + assert.Equal("com.example.BookingStatus", method.ResponseType) + assert.Equal("com.example", method.Package) + assert.False(method.IsProto3) +} + +func (assert *Proto2ParserTest) field(field *parser.Field, name, comment, typeName, label string) { + assert.Equal(name, field.Name) + assert.Equal(comment, field.Comment) + assert.Equal(typeName, field.Type) + assert.Equal(label, field.Label) + assert.Equal("", field.DefaultValue) +} diff --git a/parser/proto3_test.go b/parser/proto3_test.go new file mode 100644 index 00000000..923b8f48 --- /dev/null +++ b/parser/proto3_test.go @@ -0,0 +1,158 @@ +package parser_test + +import ( + "github.com/pseudomuto/protoc-gen-doc/parser" + "github.com/pseudomuto/protoc-gen-doc/test" + "github.com/stretchr/testify/suite" + "testing" +) + +var ( + proto3File *parser.File +) + +type Proto3ParserTest struct { + suite.Suite +} + +func TestProto3Parser(t *testing.T) { + suite.Run(t, new(Proto3ParserTest)) +} + +func (assert *Proto3ParserTest) SetupSuite() { + codeGenRequest, err := test.MakeCodeGeneratorRequest() + assert.Nil(err) + + proto3File = parser.ParseCodeRequest(codeGenRequest).GetFile("Vehicle.proto") +} + +func (assert *Proto3ParserTest) TestFileProperties() { + assert.Equal("Vehicle.proto", proto3File.Name) + assert.Equal("com.example", proto3File.Package) + assert.Equal("Messages describing manufacturers / vehicles.", proto3File.Comment) + assert.True(proto3File.IsProto3) + assert.Equal(0, len(proto3File.Extensions)) + + for _, msg := range []string{"EmptyMessage", "Manufacturer", "Model", "Vehicle", "Vehicle.Category"} { + assert.True(proto3File.HasMessage(msg)) + } + + for _, enum := range []string{"Manufacturer.Category", "Type"} { + assert.True(proto3File.HasEnum(enum)) + } +} + +func (assert *Proto3ParserTest) TestEnumProperties() { + enum := proto3File.GetEnum("Type") + assert.Equal("Type", enum.Name) + assert.Equal("com.example", enum.Package) + assert.Equal("com.example.Type", enum.FullName()) + assert.Equal("The type of model.", enum.Comment) + assert.True(enum.IsProto3) + + value := enum.Values[0] + assert.Equal("COUPE", value.Name) + assert.Equal(int32(0), value.Number) + assert.Equal("com.example", value.Package) + assert.Equal("The type is coupe.", value.Comment) + assert.True(value.IsProto3) + + value = enum.Values[1] + assert.Equal("SEDAN", value.Name) + assert.Equal(int32(1), value.Number) + assert.Equal("com.example", value.Package) + assert.Equal("The type is sedan.", value.Comment) + assert.True(value.IsProto3) +} + +func (assert *Proto3ParserTest) TestNestedEnumProperties() { + enum := proto3File.GetEnum("Manufacturer.Category") + assert.Equal("Manufacturer.Category", enum.Name) + assert.Equal("com.example", enum.Package) + assert.Equal("com.example.Manufacturer.Category", enum.FullName()) + assert.Equal("Manufacturer category. A manufacturer may be either inhouse or external.", enum.Comment) + assert.True(enum.IsProto3) + + value := enum.Values[0] + assert.Equal("CATEGORY_INHOUSE", value.Name) + assert.Equal(int32(0), value.Number) + assert.Equal("com.example", value.Package) + assert.Equal("The manufacturer is inhouse.", value.Comment) + assert.True(value.IsProto3) + + value = enum.Values[1] + assert.Equal("CATEGORY_EXTERNAL", value.Name) + assert.Equal(int32(1), value.Number) + assert.Equal("com.example", value.Package) + assert.Equal("The manufacturer is external.", value.Comment) + assert.True(value.IsProto3) +} + +func (assert *Proto3ParserTest) TestMessageProperties() { + msg := proto3File.GetMessage("Vehicle") + assert.Equal("Vehicle", msg.Name) + assert.Equal("com.example", msg.Package) + assert.Equal("com.example.Vehicle", msg.FullName()) + assert.Equal("Represents a vehicle that can be hired.", msg.Comment) + assert.True(msg.IsProto3) + assert.Equal(7, len(msg.Fields)) + assert.Equal(0, len(msg.Extensions)) + + assert.field(msg.Fields[0], "id", "Unique vehicle ID.", "int32", "") + assert.field(msg.Fields[1], "model", "Vehicle model.", "Model", "") + assert.field(msg.Fields[4], "category", "Vehicle category.", "Vehicle.Category", "") + assert.field(msg.Fields[5], "rates", "rates", "sint32", "repeated") + + // maps are just repeated "Entry" fields + assert.field(msg.Fields[6], "properties", "bag of properties related to the vehicle.", "Vehicle.PropertiesEntry", "repeated") +} + +func (assert *Proto3ParserTest) TestNestedMessageProperties() { + msg := proto3File.GetMessage("Vehicle.Category") + assert.Equal("Vehicle.Category", msg.Name) + assert.Equal("com.example", msg.Package) + assert.Equal("com.example.Vehicle.Category", msg.FullName()) + assert.Equal("Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", msg.Comment) + assert.True(msg.IsProto3) + assert.Equal(2, len(msg.Fields)) + assert.Equal(0, len(msg.Extensions)) + + assert.field(msg.Fields[0], "code", "Category code. E.g. \"S\".", "string", "") + assert.field(msg.Fields[1], "description", "Category name. E.g. \"Sedan\".", "string", "") +} + +func (assert *Proto3ParserTest) field(field *parser.Field, name, comment, typeName, label string) { + assert.Equal(name, field.Name) + assert.Equal(comment, field.Comment) + assert.Equal(typeName, field.Type) + assert.Equal(label, field.Label) + assert.Equal("", field.DefaultValue) +} + +func (assert *Proto3ParserTest) TestServiceProperties() { + service := proto3File.GetService("VehicleService") + assert.Equal("VehicleService", service.Name) + assert.Equal("com.example", service.Package) + assert.Equal("com.example.VehicleService", service.FullName()) + assert.Equal("The vehicle service.\n\nManages vehicles and such...", service.Comment) + assert.True(service.IsProto3) + assert.Equal(3, len(service.Methods)) + + names := []string{"GetModels", "AddModels", "GetVehicle"} + comments := []string{"Returns the set of models.", "creates models", "Looks up a vehicle by id."} + clientStreams := []bool{false, true, false} + serverStreams := []bool{true, true, false} + requestTypes := []string{"com.example.EmptyMessage", "com.example.Model", "com.example.FindVehicleById"} + responseTypes := []string{"com.example.Model", "com.example.Model", "com.example.Vehicle"} + + for idx, method := range service.Methods { + assert.Equal(names[idx], method.Name) + assert.Equal(comments[idx], method.Comment) + assert.Equal(clientStreams[idx], method.ClientStreaming) + assert.Equal(serverStreams[idx], method.ServerStreaming) + assert.Equal(requestTypes[idx], method.RequestType) + assert.Equal(responseTypes[idx], method.ResponseType) + assert.Equal("com.example", method.Package) + assert.True(method.IsProto3) + } +} diff --git a/test/cmd/gen_fixtures/main.go b/test/cmd/gen_fixtures/main.go index dee0322f..713bc2fd 100644 --- a/test/cmd/gen_fixtures/main.go +++ b/test/cmd/gen_fixtures/main.go @@ -17,5 +17,5 @@ func main() { log.Fatalf("Could not read contents from stdin") } - ioutil.WriteFile("fixtures/proto3_generator_request.dat", data, 0666) + ioutil.WriteFile("fixtures/generator_request.dat", data, 0666) } diff --git a/test/protoc_stubs.go b/test/protoc_stubs.go index 752612fe..c18eacc3 100644 --- a/test/protoc_stubs.go +++ b/test/protoc_stubs.go @@ -7,7 +7,7 @@ import ( ) func MakeCodeGeneratorRequest() (*plugin_go.CodeGeneratorRequest, error) { - data, err := ioutil.ReadFile("../fixtures/proto3_generator_request.dat") + data, err := ioutil.ReadFile("../fixtures/generator_request.dat") if err != nil { return nil, err } From 1c086196628668c79e72f1dfd2891148466cdda0 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 23 Jun 2017 00:58:12 -0400 Subject: [PATCH 05/50] Add a simple benchmark test --- .travis.yml | 1 + Makefile | 3 +++ bench_test.go | 15 +++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 bench_test.go diff --git a/.travis.yml b/.travis.yml index 06289dab..f6cab33a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ install: script: - make test + - make bench notifications: email: false diff --git a/Makefile b/Makefile index 377b3211..58543bff 100644 --- a/Makefile +++ b/Makefile @@ -5,3 +5,6 @@ generate: test: generate @go test -cover $(shell go list ./... | grep -v -E 'test|tools|vendor') + +bench: + @go test -bench=. diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 00000000..20d5c8c3 --- /dev/null +++ b/bench_test.go @@ -0,0 +1,15 @@ +package protoc_gen_doc + +import ( + "github.com/pseudomuto/protoc-gen-doc/parser" + "github.com/pseudomuto/protoc-gen-doc/test" + "testing" +) + +func BenchmarkParseCodeRequest(b *testing.B) { + codeGenRequest, _ := test.MakeCodeGeneratorRequest() + + for i := 0; i < b.N; i++ { + parser.ParseCodeRequest(codeGenRequest) + } +} From da6756409521d760341cc0fe0926a0d6dc276a94 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 23 Jun 2017 17:32:47 -0400 Subject: [PATCH 06/50] Convert ParserResult to Template for rendering --- bench_test.go | 2 +- parser/models.go | 1 + parser/proto.go | 29 ++++-- parser/proto2_test.go | 2 +- parser/proto3_test.go | 6 +- template.go | 231 ++++++++++++++++++++++++++++++++++++++++++ template_test.go | 221 ++++++++++++++++++++++++++++++++++++++++ test/protoc_stubs.go | 7 +- 8 files changed, 482 insertions(+), 17 deletions(-) create mode 100644 template.go create mode 100644 template_test.go diff --git a/bench_test.go b/bench_test.go index 20d5c8c3..852c9525 100644 --- a/bench_test.go +++ b/bench_test.go @@ -1,4 +1,4 @@ -package protoc_gen_doc +package protoc_gen_doc_test import ( "github.com/pseudomuto/protoc-gen-doc/parser" diff --git a/parser/models.go b/parser/models.go index 2f222b26..d4b20802 100644 --- a/parser/models.go +++ b/parser/models.go @@ -140,6 +140,7 @@ type Field struct { type Extension struct { Field + Label string Number int32 ContainingType string ScopeType string diff --git a/parser/proto.go b/parser/proto.go index 5a0b6377..c3a55379 100644 --- a/parser/proto.go +++ b/parser/proto.go @@ -184,14 +184,6 @@ func (pp *protoFileParser) parseDescriptor(sl []*Message, d *descriptor.Descript } func (pp *protoFileParser) parseFields(fdp []*descriptor.FieldDescriptorProto, basePath string) []*Field { - typeName := func(name string) string { - if strings.HasPrefix(name, ".") { - return strings.TrimPrefix(name, fmt.Sprintf(".%s.", pp.Package())) - } - - return strings.ToLower(strings.TrimPrefix(name, "TYPE_")) - } - fields := make([]*Field, 0, len(fdp)) for idx, field := range fdp { @@ -203,19 +195,27 @@ func (pp *protoFileParser) parseFields(fdp []*descriptor.FieldDescriptorProto, b path: fmt.Sprintf("%s,%d,%d", basePath, messageFieldPath, idx), }, Label: pp.labelName(field.GetLabel()), - Type: typeName(field.GetTypeName()), + Type: fmt.Sprintf("%s.%s", pp.Package(), pp.typeName(field.GetTypeName())), }) f := fields[len(fields)-1] - if f.Type == "" { - f.Type = typeName(field.GetType().String()) + if f.Type == pp.Package()+"." { + f.Type = pp.typeName(field.GetType().String()) } } return fields } +func (pp *protoFileParser) typeName(name string) string { + if strings.HasPrefix(name, ".") { + return strings.TrimPrefix(name, fmt.Sprintf(".%s.", pp.Package())) + } + + return strings.ToLower(strings.TrimPrefix(name, "TYPE_")) +} + func (pp *protoFileParser) parseExtensions(ec extensionContainer, scopeType, basePath string) []*Extension { descriptors := ec.GetExtension() extensions := make([]*Extension, 0, len(descriptors)) @@ -230,11 +230,18 @@ func (pp *protoFileParser) parseExtensions(ec extensionContainer, scopeType, bas path: fmt.Sprintf("%s,%d", basePath, idx), }, DefaultValue: ext.GetDefaultValue(), + Type: pp.typeName(ext.GetTypeName()), }, ContainingType: strings.TrimPrefix(ext.GetExtendee(), "."), ScopeType: scopeType, + Label: pp.labelName(ext.GetLabel()), Number: ext.GetNumber(), }) + + e := extensions[len(extensions)-1] + if e.Type == "" { + e.Type = pp.typeName(ext.GetType().String()) + } } return extensions diff --git a/parser/proto2_test.go b/parser/proto2_test.go index 95cda075..6aa0200b 100644 --- a/parser/proto2_test.go +++ b/parser/proto2_test.go @@ -111,7 +111,7 @@ func (assert *Proto2ParserTest) TestMessageProperties() { assert.Equal(3, len(msg.Fields)) assert.field(msg.Fields[0], "id", "Unique booking status ID.", "int32", "required") - assert.field(msg.Fields[2], "status_code", "The status of this status?", "BookingStatus.StatusCode", "optional") + assert.field(msg.Fields[2], "status_code", "The status of this status?", "com.example.BookingStatus.StatusCode", "optional") } func (assert *Proto2ParserTest) TestMessageExtensionProperties() { diff --git a/parser/proto3_test.go b/parser/proto3_test.go index 923b8f48..db79181f 100644 --- a/parser/proto3_test.go +++ b/parser/proto3_test.go @@ -99,12 +99,12 @@ func (assert *Proto3ParserTest) TestMessageProperties() { assert.Equal(0, len(msg.Extensions)) assert.field(msg.Fields[0], "id", "Unique vehicle ID.", "int32", "") - assert.field(msg.Fields[1], "model", "Vehicle model.", "Model", "") - assert.field(msg.Fields[4], "category", "Vehicle category.", "Vehicle.Category", "") + assert.field(msg.Fields[1], "model", "Vehicle model.", "com.example.Model", "") + assert.field(msg.Fields[4], "category", "Vehicle category.", "com.example.Vehicle.Category", "") assert.field(msg.Fields[5], "rates", "rates", "sint32", "repeated") // maps are just repeated "Entry" fields - assert.field(msg.Fields[6], "properties", "bag of properties related to the vehicle.", "Vehicle.PropertiesEntry", "repeated") + assert.field(msg.Fields[6], "properties", "bag of properties related to the vehicle.", "com.example.Vehicle.PropertiesEntry", "repeated") } func (assert *Proto3ParserTest) TestNestedMessageProperties() { diff --git a/template.go b/template.go new file mode 100644 index 00000000..f56cb9b3 --- /dev/null +++ b/template.go @@ -0,0 +1,231 @@ +package protoc_gen_doc + +import ( + "fmt" + "github.com/pseudomuto/protoc-gen-doc/parser" + "strings" +) + +type Template struct { + Files []*File `json:"files"` +} + +func NewTemplate(pr *parser.ParseResult) *Template { + files := make([]*File, 0, len(pr.Files)) + + for _, f := range pr.Files { + file := &File{ + Name: f.Name, + Description: f.Comment, + Package: f.Package, + HasEnums: len(f.Enums) > 0, + HasExtensions: len(f.Extensions) > 0, + HasMessages: len(f.Messages) > 0, + HasServices: len(f.Services) > 0, + } + + for _, e := range f.Extensions { + file.Extensions = append(file.Extensions, parseFileExtension(e)) + } + + for _, m := range f.Messages { + file.Messages = append(file.Messages, parseMessage(m)) + } + + for _, s := range f.Services { + file.Services = append(file.Services, parseService(s)) + } + + files = append(files, file) + } + + return &Template{Files: files} +} + +type File struct { + Name string `json:"file_name"` + Description string `json:"file_description"` + Package string `json:"file_package"` + + Enums []*Enum `json:"file_enums"` + Extensions []*FileExtension `json:"file_extensions"` + Messages []*Message `json:"file_messages"` + Services []*Service `json:"file_services"` + + HasEnums bool `json:"file_has_enums"` + HasExtensions bool `json:"file_has_extensions"` + HasMessages bool `json:"file_has_messages"` + HasServices bool `json:"file_has_services"` +} + +type FileExtension struct { + Name string `json:"extension_name"` + LongName string `json:"extension_long_name"` + FullName string `json:"extension_full_name"` + Description string `json:"extension_description"` + Label string `json:"extension_label"` + Type string `json:"extension_type"` + LongType string `json:"extension_long_type"` + FullType string `json:"extension_full_type"` + Number int `json:"extension_number"` + DefaultValue string `json:"extension_default_value"` + ContainingType string `json:"extension_containing_type"` + ContainingLongType string `json:"extension_containing_long_type"` + ContainingFullType string `json:"extension_containing_full_type"` +} + +type Message struct { + Name string `json:"message_name"` + LongName string `json:"message_long_name"` + FullName string `json:"message_full_name"` + Description string `json:"message_description"` + + Extensions []*MessageExtension `json:"message_extensions"` + Fields []*MessageField `json:"message_fields"` + + HasExtensions bool `json:"message_has_extensions"` + HasFields bool `json:"message_has_extensions"` +} + +type MessageField struct { + Name string `json:"field_name"` + Description string `json:"field_description"` + Label string `json:"field_label"` + Type string `json:"field_type"` + LongType string `json:"field_long_type"` + FullType string `json:"field_full_type"` + DefaultValue string `json:"field_default_value"` +} + +type MessageExtension struct { + FileExtension + + ScopeType string `json:"extension_scope_type"` + ScopeLongType string `json:"extension_scope_long_type"` + ScopeFullType string `json:"extension_scope_full_type"` +} + +type Enum struct { + Name string `json:"enum_name"` + LongName string `json:"enum_long_name"` + FullName string `json:"enum_full_name"` + Description string `json:"enum_description"` +} + +type EnumValue struct { + Name string `json:"value_name"` + Number string `json:"value_number"` + Description string `json:"value_description"` +} + +type Service struct { + Name string `json:"service_name"` + LongName string `json:"service_long_name"` + FullName string `json:"service_full_name"` + Description string `json:"service_description"` + Methods []*ServiceMethod `json:"service_methods"` +} + +type ServiceMethod struct { + Name string `json:"method_name"` + Description string `json:"method_description"` + RequestType string `json:"method_request_type"` + RequestLongType string `json:"method_request_long_type"` + RequestFullType string `json:"method_request_full_type"` + ResponseType string `json:"method_response_type"` + ResponseLongType string `json:"method_response_long_type"` + ResponseFullType string `json:"method_response_full_type"` +} + +func parseFileExtension(pe *parser.Extension) *FileExtension { + return &FileExtension{ + Name: baseName(pe.Name), + LongName: strings.TrimPrefix(pe.FullName(), pe.Package+"."), + FullName: pe.FullName(), + Description: pe.Comment, + Label: pe.Label, + Type: baseName(pe.Type), + LongType: strings.TrimPrefix(pe.Type, pe.Package+"."), + FullType: pe.Type, + Number: int(pe.Number), + DefaultValue: pe.DefaultValue, + ContainingType: baseName(pe.ContainingType), + ContainingLongType: strings.TrimPrefix(pe.ContainingType, pe.Package+"."), + ContainingFullType: pe.ContainingType, + } +} + +func parseMessage(pm *parser.Message) *Message { + msg := &Message{ + Name: baseName(pm.Name), + LongName: pm.Name, + FullName: pm.FullName(), + Description: pm.Comment, + HasExtensions: len(pm.Extensions) > 0, + HasFields: len(pm.Fields) > 0, + } + + for _, ext := range pm.Extensions { + msg.Extensions = append(msg.Extensions, parseMessageExtension(ext)) + } + + for _, f := range pm.Fields { + msg.Fields = append(msg.Fields, parseMessageField(f)) + } + + return msg +} + +func parseMessageExtension(pe *parser.Extension) *MessageExtension { + return &MessageExtension{ + FileExtension: *parseFileExtension(pe), + ScopeType: baseName(pe.ScopeType), + ScopeLongType: strings.TrimPrefix(pe.ScopeType, pe.Package+"."), + ScopeFullType: pe.ScopeType, + } +} + +func parseMessageField(pf *parser.Field) *MessageField { + return &MessageField{ + Name: pf.Name, + Description: pf.Comment, + Label: pf.Label, + Type: baseName(pf.Type), + LongType: strings.TrimPrefix(pf.Type, pf.Package+"."), + FullType: pf.Type, + DefaultValue: pf.DefaultValue, + } +} + +func parseService(ps *parser.Service) *Service { + service := &Service{ + Name: ps.Name, + LongName: ps.Name, + FullName: fmt.Sprintf("%s.%s", ps.Package, ps.Name), + Description: ps.Comment, + } + + for _, sm := range ps.Methods { + service.Methods = append(service.Methods, parseServiceMethod(sm)) + } + + return service +} + +func parseServiceMethod(pm *parser.ServiceMethod) *ServiceMethod { + return &ServiceMethod{ + Name: pm.Name, + Description: pm.Comment, + RequestType: baseName(pm.RequestType), + RequestLongType: strings.TrimPrefix(pm.RequestType, pm.Package+"."), + RequestFullType: pm.RequestType, + ResponseType: baseName(pm.ResponseType), + ResponseLongType: strings.TrimPrefix(pm.ResponseType, pm.Package+"."), + ResponseFullType: pm.ResponseType, + } +} + +func baseName(name string) string { + parts := strings.Split(name, ".") + return parts[len(parts)-1] +} diff --git a/template_test.go b/template_test.go new file mode 100644 index 00000000..a9cc985a --- /dev/null +++ b/template_test.go @@ -0,0 +1,221 @@ +package protoc_gen_doc_test + +import ( + "github.com/pseudomuto/protoc-gen-doc" + "github.com/pseudomuto/protoc-gen-doc/parser" + "github.com/pseudomuto/protoc-gen-doc/test" + "github.com/stretchr/testify/suite" + "testing" +) + +var ( + template *protoc_gen_doc.Template + bookingFile *protoc_gen_doc.File + vehicleFile *protoc_gen_doc.File +) + +type TemplateTest struct { + suite.Suite +} + +func TestTemplate(t *testing.T) { + suite.Run(t, new(TemplateTest)) +} + +func (assert *TemplateTest) SetupSuite() { + codeGenRequest, err := test.MakeCodeGeneratorRequest() + assert.Nil(err) + + result := parser.ParseCodeRequest(codeGenRequest) + template = protoc_gen_doc.NewTemplate(result) + bookingFile = template.Files[0] + vehicleFile = template.Files[1] +} + +func (assert *TemplateTest) TestTemplateProperties() { + assert.Equal(2, len(template.Files)) +} + +func (assert *TemplateTest) TestFileProperties() { + assert.Equal("Booking.proto", bookingFile.Name) + assert.Equal("Booking related messages.\n\nThis file is really just an example. The data model is completely\nfictional.", bookingFile.Description) + assert.Equal("com.example", bookingFile.Package) + assert.True(bookingFile.HasEnums) + assert.True(bookingFile.HasExtensions) + assert.True(bookingFile.HasMessages) + assert.True(bookingFile.HasServices) +} + +func (assert *TemplateTest) TestFileExtensionProperties() { + ext := findExtension("BookingStatus.country", bookingFile) + assert.Equal("country", ext.Name) + assert.Equal("BookingStatus.country", ext.LongName) + assert.Equal("com.example.BookingStatus.country", ext.FullName) + assert.Equal("The country the booking occurred in.", ext.Description) + assert.Equal("optional", ext.Label) + assert.Equal("string", ext.Type) + assert.Equal("string", ext.LongType) + assert.Equal("string", ext.FullType) + assert.Equal(100, ext.Number) + assert.Equal("china", ext.DefaultValue) + assert.Equal("BookingStatus", ext.ContainingType) + assert.Equal("BookingStatus", ext.ContainingLongType) + assert.Equal("com.example.BookingStatus", ext.ContainingFullType) +} + +func (assert *TemplateTest) TestMessageProperties() { + msg := findMessage("Vehicle", vehicleFile) + assert.Equal("Vehicle", msg.Name) + assert.Equal("Vehicle", msg.LongName) + assert.Equal("com.example.Vehicle", msg.FullName) + assert.Equal("Represents a vehicle that can be hired.", msg.Description) + assert.False(msg.HasExtensions) + assert.True(msg.HasFields) +} + +func (assert *TemplateTest) TestNestedMessageProperties() { + msg := findMessage("Vehicle.Category", vehicleFile) + assert.Equal("Category", msg.Name) + assert.Equal("Vehicle.Category", msg.LongName) + assert.Equal("com.example.Vehicle.Category", msg.FullName) + assert.Equal("Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", msg.Description) + assert.False(msg.HasExtensions) + assert.True(msg.HasFields) +} + +func (assert *TemplateTest) TestMessageExtensionProperties() { + msg := findMessage("Booking", bookingFile) + assert.Equal(1, len(msg.Extensions)) + + ext := msg.Extensions[0] + assert.Equal("optional_field_1", ext.Name) + assert.Equal("BookingStatus.optional_field_1", ext.LongName) + assert.Equal("com.example.BookingStatus.optional_field_1", ext.FullName) + assert.Equal("An optional field to be used however you please.", ext.Description) + assert.Equal("optional", ext.Label) + assert.Equal("string", ext.Type) + assert.Equal("string", ext.LongType) + assert.Equal("string", ext.FullType) + assert.Equal(101, ext.Number) + assert.Equal("", ext.DefaultValue) + assert.Equal("BookingStatus", ext.ContainingType) + assert.Equal("BookingStatus", ext.ContainingLongType) + assert.Equal("com.example.BookingStatus", ext.ContainingFullType) + assert.Equal("Booking", ext.ScopeType) + assert.Equal("Booking", ext.ScopeLongType) + assert.Equal("com.example.Booking", ext.ScopeFullType) +} + +func (assert *TemplateTest) TestFieldProperties() { + msg := findMessage("BookingStatus", bookingFile) + + field := findField("id", msg) + assert.Equal("id", field.Name) + assert.Equal("Unique booking status ID.", field.Description) + assert.Equal("required", field.Label) + assert.Equal("int32", field.Type) + assert.Equal("int32", field.LongType) + assert.Equal("int32", field.FullType) + assert.Equal("", field.DefaultValue) + + field = findField("status_code", msg) + assert.Equal("status_code", field.Name) + assert.Equal("The status of this status?", field.Description) + assert.Equal("optional", field.Label) + assert.Equal("StatusCode", field.Type) + assert.Equal("BookingStatus.StatusCode", field.LongType) + assert.Equal("com.example.BookingStatus.StatusCode", field.FullType) + assert.Equal("", field.DefaultValue) + + field = findField("category", findMessage("Vehicle", vehicleFile)) + assert.Equal("category", field.Name) + assert.Equal("Vehicle category.", field.Description) + assert.Equal("", field.Label) // proto3, neither required, nor optional are valid + assert.Equal("Category", field.Type) + assert.Equal("Vehicle.Category", field.LongType) + assert.Equal("com.example.Vehicle.Category", field.FullType) + assert.Equal("", field.DefaultValue) +} + +func (assert *TemplateTest) TestServiceProperties() { + service := findService("VehicleService", vehicleFile) + assert.Equal("VehicleService", service.Name) + assert.Equal("VehicleService", service.LongName) + assert.Equal("com.example.VehicleService", service.FullName) + assert.Equal("The vehicle service.\n\nManages vehicles and such...", service.Description) + assert.Equal(3, len(service.Methods)) +} + +func (assert *TemplateTest) TestServiceMethodProperties() { + service := findService("VehicleService", vehicleFile) + + method := findServiceMethod("AddModels", service) + assert.Equal("AddModels", method.Name) + assert.Equal("creates models", method.Description) + assert.Equal("Model", method.RequestType) + assert.Equal("Model", method.RequestLongType) + assert.Equal("com.example.Model", method.RequestFullType) + assert.Equal("Model", method.ResponseType) + assert.Equal("Model", method.ResponseLongType) + assert.Equal("com.example.Model", method.ResponseFullType) + + method = findServiceMethod("GetVehicle", service) + assert.Equal("GetVehicle", method.Name) + assert.Equal("Looks up a vehicle by id.", method.Description) + assert.Equal("FindVehicleById", method.RequestType) + assert.Equal("FindVehicleById", method.RequestLongType) + assert.Equal("com.example.FindVehicleById", method.RequestFullType) + assert.Equal("Vehicle", method.ResponseType) + assert.Equal("Vehicle", method.ResponseLongType) + assert.Equal("com.example.Vehicle", method.ResponseFullType) +} + +func findService(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Service { + for _, s := range f.Services { + if s.Name == name { + return s + } + } + + return nil +} + +func findServiceMethod(name string, s *protoc_gen_doc.Service) *protoc_gen_doc.ServiceMethod { + for _, m := range s.Methods { + if m.Name == name { + return m + } + } + + return nil +} + +func findExtension(name string, f *protoc_gen_doc.File) *protoc_gen_doc.FileExtension { + for _, ext := range f.Extensions { + if ext.LongName == name { + return ext + } + } + + return nil +} + +func findMessage(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Message { + for _, m := range f.Messages { + if m.LongName == name { + return m + } + } + + return nil +} + +func findField(name string, m *protoc_gen_doc.Message) *protoc_gen_doc.MessageField { + for _, f := range m.Fields { + if f.Name == name { + return f + } + } + + return nil +} diff --git a/test/protoc_stubs.go b/test/protoc_stubs.go index c18eacc3..6ad8f277 100644 --- a/test/protoc_stubs.go +++ b/test/protoc_stubs.go @@ -4,10 +4,15 @@ import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/protoc-gen-go/plugin" "io/ioutil" + "path" + "runtime" ) func MakeCodeGeneratorRequest() (*plugin_go.CodeGeneratorRequest, error) { - data, err := ioutil.ReadFile("../fixtures/generator_request.dat") + _, filename, _, _ := runtime.Caller(0) + filepath := path.Join(path.Dir(filename), "../fixtures/generator_request.dat") + + data, err := ioutil.ReadFile(filepath) if err != nil { return nil, err } From bce883697b574b23fca23b7cc7da96c3a2740415 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 23 Jun 2017 18:04:34 -0400 Subject: [PATCH 07/50] Add scalars to template --- template.go | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 2 deletions(-) diff --git a/template.go b/template.go index f56cb9b3..aeee2e21 100644 --- a/template.go +++ b/template.go @@ -7,7 +7,8 @@ import ( ) type Template struct { - Files []*File `json:"files"` + Files []*File `json:"files"` + Scalars []*ScalarValue `json:"scalar_value_types"` } func NewTemplate(pr *parser.ParseResult) *Template { @@ -39,7 +40,7 @@ func NewTemplate(pr *parser.ParseResult) *Template { files = append(files, file) } - return &Template{Files: files} + return &Template{Files: files, Scalars: makeScalars()} } type File struct { @@ -137,6 +138,18 @@ type ServiceMethod struct { ResponseFullType string `json:"method_response_full_type"` } +type ScalarValue struct { + ProtoType string `json:"scalar_value_proto_type"` + Notes string `json:"scalar_value_notes"` + CppType string `json:"scalar_value_cpp_type"` + CSharp string `json:"scalar_value_cs_type"` + GoType string `json:"scalar_value_go_type"` + JavaType string `json:"scalar_value_java_type"` + PhpType string `json:"scalar_value_php_type"` + PythonType string `json:"scalar_value_python_type"` + RubyType string `json:"scalar_value_ruby_type"` +} + func parseFileExtension(pe *parser.Extension) *FileExtension { return &FileExtension{ Name: baseName(pe.Name), @@ -229,3 +242,173 @@ func baseName(name string) string { parts := strings.Split(name, ".") return parts[len(parts)-1] } + +func makeScalars() []*ScalarValue { + return []*ScalarValue{ + { + "double", + "", + "double", + "double", + "float64", + "double", + "float", + "float", + "Float", + }, + { + "float", + "", + "float", + "float", + "float32", + "float", + "float", + "float", + "Float", + }, + { + "int32", + "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.", + "int32", + "int", + "int32", + "int", + "integer", + "int", + "Bignum or Fixnum (as required)", + }, + { + "int64", + "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.", + "int64", + "long", + "int64", + "long", + "integer/string", + "int/long", + "Bignum", + }, + { + "uint32", + "Uses variable-length encoding.", + "uint32", + "uint", + "uint32", + "int", + "integer", + "int/long", + "Bignum or Fixnum (as required)", + }, + { + "uint64", + "Uses variable-length encoding.", + "uint64", + "ulong", + "uint64", + "long", + "integer/string", + "int/long", + "Bignum or Fixnum (as required)", + }, + { + "sint32", + "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.", + "int32", + "int", + "int32", + "int", + "integer", + "int", + "Bignum or Fixnum (as required)", + }, + { + "sint64", + "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.", + "int64", + "long", + "int64", + "long", + "integer/string", + "int/long", + "Bignum", + }, + { + "fixed32", + "Always four bytes. More efficient than uint32 if values are often greater than 2^28.", + "uint32", + "uint", + "uint32", + "int", + "integer", + "int", + "Bignum or Fixnum (as required)", + }, + { + "fixed64", + "Always eight bytes. More efficient than uint64 if values are often greater than 2^56.", + "uint64", + "ulong", + "uint64", + "long", + "integer/string", + "int/long", + "Bignum", + }, + { + "sfixed32", + "Always four bytes.", + "int32", + "int", + "int32", + "int", + "integer", + "int", + "Bignum or Fixnum (as required)", + }, + { + "sfixed64", + "Always eight bytes.", + "int64", + "long", + "int64", + "long", + "integer/string", + "int/long", + "Bignum", + }, + { + "bool", + "", + "bool", + "bool", + "bool", + "boolean", + "boolean", + "boolean", + "TrueClass/FalseClass", + }, + { + "string", + "A string must always contain UTF-8 encoded or 7-bit ASCII text.", + "string", + "string", + "string", + "String", + "string", + "str/unicode", + "String (UTF-8)", + }, + { + "bytes", + "May contain any arbitrary sequence of bytes.", + "string", + "ByteString", + "[]byte", + "ByteString", + "string", + "str", + "String (ASCII-8BIT)", + }, + } +} From c95703bc37553ade3cdc485ef9c4f90a929fe8f3 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 23 Jun 2017 18:10:22 -0400 Subject: [PATCH 08/50] Install protoc in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f6cab33a..4e30f1a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ go: install: - go get -v github.com/Masterminds/glide + - go get -u github.com/golang/protobuf/{proto,protoc-gen-go} - cd $GOPATH/src/github.com/Masterminds/glide && git checkout tags/v0.12.3 && go install && cd - - glide install From 8d0d1eba769a1de7143f9b1f94e74c28a454944d Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Sun, 25 Jun 2017 14:37:53 -0400 Subject: [PATCH 09/50] Fetch protoc for ci builds --- .travis.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4e30f1a2..068bb96f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,23 @@ language: go sudo: false +env: + - PROTOC_RELEASE="https://github.com/google/protobuf/releases/download/v3.2.0/protoc-3.2.0-linux-x86_64.zip" + - PROTOC_TARGET="${HOME}/protoc" + +cache: + - "${HOME}/protoc" + - "${HOME}/gopath/src/github.com/pseudomuto/protoc-gen-doc/vendor" + go: - 1.8.x - master install: - - go get -v github.com/Masterminds/glide + - if [ ! -d "${PROTOC_TARGET}" ]; then curl -fsSL "$PROTOC_RELEASE" > "${PROTOC_TARGET}.zip"; fi + - if [ -f "${PROTOC_TARGET}.zip" ]; then unzip "${PROTOC_TARGET}.zip" -d "${PROTOC_TARGET}"; fi - go get -u github.com/golang/protobuf/{proto,protoc-gen-go} + - go get -u github.com/Masterminds/glide - cd $GOPATH/src/github.com/Masterminds/glide && git checkout tags/v0.12.3 && go install && cd - - glide install From 5496f3ecb854cb685843ce2a3396d8a598d036a1 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Sun, 25 Jun 2017 14:39:00 -0400 Subject: [PATCH 10/50] Make env vars global in travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 068bb96f..1ade37d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,9 @@ language: go sudo: false env: - - PROTOC_RELEASE="https://github.com/google/protobuf/releases/download/v3.2.0/protoc-3.2.0-linux-x86_64.zip" - - PROTOC_TARGET="${HOME}/protoc" + global: + - PROTOC_RELEASE="https://github.com/google/protobuf/releases/download/v3.2.0/protoc-3.2.0-linux-x86_64.zip" + - PROTOC_TARGET="${HOME}/protoc" cache: - "${HOME}/protoc" From 07f833e384c3becffd27f73a3e7f2c88eeb1cffe Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Sun, 25 Jun 2017 14:41:59 -0400 Subject: [PATCH 11/50] Add /Users/pseudomuto/protoc/bin to path --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1ade37d3..643f2481 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ env: global: - PROTOC_RELEASE="https://github.com/google/protobuf/releases/download/v3.2.0/protoc-3.2.0-linux-x86_64.zip" - PROTOC_TARGET="${HOME}/protoc" + - PATH="${PROTOC_TARGET}/bin:${PATH}" cache: - "${HOME}/protoc" From 6565dac56676fd778e74a921d769da49a1771878 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Mon, 3 Jul 2017 13:01:00 -0400 Subject: [PATCH 12/50] Add enum parsing for templates --- template.go | 32 ++++++++++++++++++++++++++++---- template_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/template.go b/template.go index aeee2e21..726e706f 100644 --- a/template.go +++ b/template.go @@ -25,6 +25,10 @@ func NewTemplate(pr *parser.ParseResult) *Template { HasServices: len(f.Services) > 0, } + for _, e := range f.Enums { + file.Enums = append(file.Enums, parseEnum(e)) + } + for _, e := range f.Extensions { file.Extensions = append(file.Extensions, parseFileExtension(e)) } @@ -107,10 +111,11 @@ type MessageExtension struct { } type Enum struct { - Name string `json:"enum_name"` - LongName string `json:"enum_long_name"` - FullName string `json:"enum_full_name"` - Description string `json:"enum_description"` + Name string `json:"enum_name"` + LongName string `json:"enum_long_name"` + FullName string `json:"enum_full_name"` + Description string `json:"enum_description"` + Values []*EnumValue `json:"enum_values"` } type EnumValue struct { @@ -150,6 +155,25 @@ type ScalarValue struct { RubyType string `json:"scalar_value_ruby_type"` } +func parseEnum(pe *parser.Enum) *Enum { + enum := &Enum{ + Name: baseName(pe.Name), + LongName: strings.TrimPrefix(pe.FullName(), pe.Package+"."), + FullName: pe.FullName(), + Description: pe.Comment, + } + + for _, val := range pe.Values { + enum.Values = append(enum.Values, &EnumValue{ + Name: val.Name, + Number: fmt.Sprint(val.Number), + Description: val.Comment, + }) + } + + return enum +} + func parseFileExtension(pe *parser.Extension) *FileExtension { return &FileExtension{ Name: baseName(pe.Name), diff --git a/template_test.go b/template_test.go index a9cc985a..dbefeb30 100644 --- a/template_test.go +++ b/template_test.go @@ -46,6 +46,24 @@ func (assert *TemplateTest) TestFileProperties() { assert.True(bookingFile.HasServices) } +func (assert *TemplateTest) TestFileEnumProperties() { + enum := findEnum("BookingStatus.StatusCode", bookingFile) + assert.Equal("StatusCode", enum.Name) + assert.Equal("BookingStatus.StatusCode", enum.LongName) + assert.Equal("com.example.BookingStatus.StatusCode", enum.FullName) + assert.Equal("A flag for the status result.", enum.Description) + assert.Equal(2, len(enum.Values)) + + expectedValues := []*protoc_gen_doc.EnumValue{ + {Name: "OK", Number: "200", Description: "OK result."}, + {Name: "BAD_REQUEST", Number: "400", Description: "BAD result."}, + } + + for idx, value := range enum.Values { + assert.Equal(expectedValues[idx], value) + } +} + func (assert *TemplateTest) TestFileExtensionProperties() { ext := findExtension("BookingStatus.country", bookingFile) assert.Equal("country", ext.Name) @@ -190,6 +208,16 @@ func findServiceMethod(name string, s *protoc_gen_doc.Service) *protoc_gen_doc.S return nil } +func findEnum(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Enum { + for _, enum := range f.Enums { + if enum.LongName == name { + return enum + } + } + + return nil +} + func findExtension(name string, f *protoc_gen_doc.File) *protoc_gen_doc.FileExtension { for _, ext := range f.Extensions { if ext.LongName == name { From b43d650f5cf5139c1381f26d2e301da9354bfcbb Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Mon, 26 Jun 2017 09:55:55 -0400 Subject: [PATCH 13/50] Add support for JSON rendering --- renderer.go | 39 +++++++++++++++++++++++++++++++++++++++ renderer_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 renderer.go create mode 100644 renderer_test.go diff --git a/renderer.go b/renderer.go new file mode 100644 index 00000000..82d9d3d9 --- /dev/null +++ b/renderer.go @@ -0,0 +1,39 @@ +package protoc_gen_doc + +import ( + "encoding/json" + "io/ioutil" +) + +type RenderType int8 + +const ( + _ RenderType = iota + RenderTypeJson +) + +type Processor interface { + Apply(template *Template) ([]byte, error) +} + +func RenderTemplate(kind RenderType, template *Template, inputTemplate, outputPath string) error { + var processor Processor + + switch kind { + case RenderTypeJson: + processor = &jsonRenderer{} + } + + result, err := processor.Apply(template) + if err != nil { + return err + } + + return ioutil.WriteFile(outputPath, result, 0644) +} + +type jsonRenderer struct{} + +func (r *jsonRenderer) Apply(template *Template) ([]byte, error) { + return json.Marshal(template) +} diff --git a/renderer_test.go b/renderer_test.go new file mode 100644 index 00000000..b3d0355a --- /dev/null +++ b/renderer_test.go @@ -0,0 +1,47 @@ +package protoc_gen_doc_test + +import ( + "github.com/pseudomuto/protoc-gen-doc" + "github.com/pseudomuto/protoc-gen-doc/parser" + "github.com/pseudomuto/protoc-gen-doc/test" + "github.com/stretchr/testify/suite" + "os" + "testing" +) + +const tempTestDir = "./tmp" + +var renderTemplate *protoc_gen_doc.Template + +type RendererTest struct { + suite.Suite +} + +func TestRenderer(t *testing.T) { + suite.Run(t, new(RendererTest)) +} + +func (assert *RendererTest) SetupSuite() { + codeGenRequest, err := test.MakeCodeGeneratorRequest() + assert.Nil(err) + + assert.Nil(os.Mkdir(tempTestDir, os.ModePerm)) + + result := parser.ParseCodeRequest(codeGenRequest) + renderTemplate = protoc_gen_doc.NewTemplate(result) +} + +func (assert *RendererTest) TearDownSuite() { + assert.Nil(os.RemoveAll(tempTestDir)) +} + +func (assert *RendererTest) TestJsonRenderer() { + err := protoc_gen_doc.RenderTemplate( + protoc_gen_doc.RenderTypeJson, + renderTemplate, + "", + tempTestDir+"/output.json", + ) + + assert.Nil(err) +} From 45d28a75d6500f239c1b5f5eec4009a2c337cfb9 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Mon, 3 Jul 2017 20:09:28 -0400 Subject: [PATCH 14/50] Add docbook, html, and markdown rendering --- .gitignore | 1 + Makefile | 4 +- build/cmd/resources/main.go | 133 ++++++++++++ generator.go | 2 + glide.lock | 7 +- glide.yaml | 1 + renderer.go | 67 +++++- renderer_test.go | 33 ++- resources.go | 42 ++++ template.go | 16 +- templates/docbook.tmpl | 210 ++++++++++++++++++ templates/html.mustache | 340 ------------------------------ templates/html.tmpl | 336 +++++++++++++++++++++++++++++ templates/markdown.mustache | 98 --------- templates/markdown.tmpl | 102 +++++++++ templates/scalar_value_types.json | 167 --------------- 16 files changed, 936 insertions(+), 623 deletions(-) create mode 100644 build/cmd/resources/main.go create mode 100644 resources.go create mode 100644 templates/docbook.tmpl delete mode 100755 templates/html.mustache create mode 100644 templates/html.tmpl delete mode 100644 templates/markdown.mustache create mode 100644 templates/markdown.tmpl delete mode 100644 templates/scalar_value_types.json diff --git a/.gitignore b/.gitignore index aa2aac1a..6e6fd594 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /protoc-gen-doc /test/*.dat /vendor +/tmp/ diff --git a/Makefile b/Makefile index 58543bff..8448c6b0 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ -.PHONY: generate test +.PHONY: bench test generate: @go generate test: generate - @go test -cover $(shell go list ./... | grep -v -E 'test|tools|vendor') + @go test -cover $(shell go list ./... | grep -v -E 'build|test|tools|vendor') bench: @go test -bench=. diff --git a/build/cmd/resources/main.go b/build/cmd/resources/main.go new file mode 100644 index 00000000..2067dcdc --- /dev/null +++ b/build/cmd/resources/main.go @@ -0,0 +1,133 @@ +// This application will take a set of files in a directory and generate an "embedded" resource file containing the +// compressed contents of those files and related metadata. +package main + +import ( + "bufio" + "bytes" + "compress/gzip" + "encoding/base64" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path" +) + +const chunkSize = 80 + +var ( + inputDir string + outputFile string + packageName string +) + +func main() { + flag.StringVar(&inputDir, "in", "", "The directory to read resources from.") + flag.StringVar(&outputFile, "out", "resources.go", "The go file to write the resources to.") + flag.StringVar(&packageName, "pkg", "main", "The package for the resource module.") + flag.Parse() + + files, err := ioutil.ReadDir(inputDir) + if err != nil { + log.Fatal(err) + } + + fd, err := os.Create(outputFile) + if err != nil { + log.Fatal(err) + } + + defer fd.Close() + + w := bufio.NewWriter(fd) + defer w.Flush() + + w.WriteString(fmt.Sprintf(`// AUTOGENERATED CODE. DO NOT EDIT. +package %s + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "io" +) + +`, packageName)) + + w.WriteString("var embeddedResources = map[string]string{\n") + + for _, file := range files { + if path.Ext(file.Name()) != ".tmpl" { + continue + } + + compressed, err := compressFile(path.Join(inputDir, file.Name())) + if err != nil { + log.Fatal(err) + } + + w.WriteString(fmt.Sprintf("\t\"%s\": \"%s\",\n", file.Name(), compressed)) + } + + w.WriteString("}\n") + + w.WriteString(` +func fetchResource(name string) ([]byte, error) { + raw, ok := embeddedResources[name] + if !ok { + return nil, fmt.Errorf("Could not find resource for '%s'", name) + } + + compressed, err := base64.StdEncoding.DecodeString(raw) + if err != nil { + return nil, err + } + + var out bytes.Buffer + buf := bytes.NewBuffer(compressed) + + r, err := gzip.NewReader(buf) + if err != nil { + return nil, err + } + + if _, err := io.Copy(&out, r); err != nil { + return nil, err + } + + return out.Bytes(), nil +} +`) +} + +func compressFile(path string) (string, error) { + var gBuf bytes.Buffer + + file, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + + w := gzip.NewWriter(&gBuf) + if _, err = w.Write(file); err != nil { + return "", err + } + + if err = w.Close(); err != nil { + return "", err + } + + return format(&gBuf), nil +} + +func format(b *bytes.Buffer) string { + var bBuf bytes.Buffer + bw := base64.NewEncoder(base64.StdEncoding, &bBuf) + bw.Write(b.Bytes()) + bw.Close() + + return string(bBuf.Bytes()) +} diff --git a/generator.go b/generator.go index 80e7e567..c1fea3b1 100644 --- a/generator.go +++ b/generator.go @@ -3,3 +3,5 @@ package protoc_gen_doc //go:generate go build ./test/cmd/gen_fixtures //go:generate protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=. fixtures/Booking.proto fixtures/Vehicle.proto //go:generate rm gen_fixtures + +//go:generate go run build/cmd/resources/main.go -in templates -out resources.go -pkg protoc_gen_doc diff --git a/glide.lock b/glide.lock index 828b4685..6244155f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,12 +1,15 @@ -hash: c932df53d468fd90b3d4bbd3e1a2787f1133945e8bad773e5e42502b7a5775d5 -updated: 2017-05-19T13:53:41.192049213-04:00 +hash: be2dc3c8df5a9b9438a9d286680627d2cf561e7cb544bf1815ccc720d4785da6 +updated: 2017-06-26T10:13:22.128548248-04:00 imports: +- name: github.com/cbroglie/mustache + version: 6857e4b493bdb8d4b1931446eb41704aeb4c28cb - name: github.com/golang/protobuf version: c9c7427a2a70d2eb3bafa0ab2dc163e45f143317 subpackages: - proto - protoc-gen-go - protoc-gen-go/descriptor + - protoc-gen-go/plugin testImports: - name: github.com/davecgh/go-spew version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 diff --git a/glide.yaml b/glide.yaml index 36dfb9e1..4b8344c5 100644 --- a/glide.yaml +++ b/glide.yaml @@ -3,3 +3,4 @@ import: - package: github.com/golang/protobuf subpackages: - protoc-gen-go +- package: github.com/cbroglie/mustache diff --git a/renderer.go b/renderer.go index 82d9d3d9..86d5633f 100644 --- a/renderer.go +++ b/renderer.go @@ -1,27 +1,54 @@ package protoc_gen_doc import ( + "bytes" "encoding/json" + html_template "html/template" "io/ioutil" + text_template "text/template" ) type RenderType int8 const ( _ RenderType = iota + RenderTypeDocBook + RenderTypeHtml RenderTypeJson + RenderTypeMarkdown ) type Processor interface { Apply(template *Template) ([]byte, error) } -func RenderTemplate(kind RenderType, template *Template, inputTemplate, outputPath string) error { +func RenderTemplate(kind RenderType, template *Template, outputPath string) error { var processor Processor switch kind { + case RenderTypeDocBook: + res, err := fetchResource("docbook.tmpl") + if err != nil { + return err + } + + processor = &textRenderer{string(res)} + case RenderTypeHtml: + res, err := fetchResource("html.tmpl") + if err != nil { + return err + } + + processor = &htmlRenderer{string(res)} case RenderTypeJson: processor = &jsonRenderer{} + case RenderTypeMarkdown: + res, err := fetchResource("markdown.tmpl") + if err != nil { + return err + } + + processor = &htmlRenderer{string(res)} } result, err := processor.Apply(template) @@ -32,8 +59,44 @@ func RenderTemplate(kind RenderType, template *Template, inputTemplate, outputPa return ioutil.WriteFile(outputPath, result, 0644) } +type textRenderer struct { + inputTemplate string +} + +func (mr *textRenderer) Apply(template *Template) ([]byte, error) { + tmpl, err := text_template.New("Text Template").Parse(mr.inputTemplate) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + if err = tmpl.Execute(&buf, template); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type htmlRenderer struct { + inputTemplate string +} + +func (mr *htmlRenderer) Apply(template *Template) ([]byte, error) { + tmpl, err := html_template.New("Text Template").Parse(mr.inputTemplate) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + if err = tmpl.Execute(&buf, template); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + type jsonRenderer struct{} func (r *jsonRenderer) Apply(template *Template) ([]byte, error) { - return json.Marshal(template) + return json.MarshalIndent(template.Files, "", " ") } diff --git a/renderer_test.go b/renderer_test.go index b3d0355a..17b6f3eb 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -25,23 +25,48 @@ func (assert *RendererTest) SetupSuite() { codeGenRequest, err := test.MakeCodeGeneratorRequest() assert.Nil(err) - assert.Nil(os.Mkdir(tempTestDir, os.ModePerm)) + os.Mkdir(tempTestDir, os.ModePerm) result := parser.ParseCodeRequest(codeGenRequest) renderTemplate = protoc_gen_doc.NewTemplate(result) } -func (assert *RendererTest) TearDownSuite() { - assert.Nil(os.RemoveAll(tempTestDir)) +func (assert *RendererTest) TestDocBookRenderer() { + err := protoc_gen_doc.RenderTemplate( + protoc_gen_doc.RenderTypeDocBook, + renderTemplate, + tempTestDir+"/output.docbook", + ) + + assert.Nil(err) +} + +func (assert *RendererTest) TestHtmlRenderer() { + err := protoc_gen_doc.RenderTemplate( + protoc_gen_doc.RenderTypeHtml, + renderTemplate, + tempTestDir+"/output.html", + ) + + assert.Nil(err) } func (assert *RendererTest) TestJsonRenderer() { err := protoc_gen_doc.RenderTemplate( protoc_gen_doc.RenderTypeJson, renderTemplate, - "", tempTestDir+"/output.json", ) assert.Nil(err) } + +func (assert *RendererTest) TestMarkdownRenderer() { + err := protoc_gen_doc.RenderTemplate( + protoc_gen_doc.RenderTypeMarkdown, + renderTemplate, + tempTestDir+"/output.md", + ) + + assert.Nil(err) +} diff --git a/resources.go b/resources.go new file mode 100644 index 00000000..f6757aad --- /dev/null +++ b/resources.go @@ -0,0 +1,42 @@ +// AUTOGENERATED CODE. DO NOT EDIT. +package protoc_gen_doc + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "io" +) + +var embeddedResources = map[string]string{ + "docbook.tmpl": "H4sIAAAAAAAA/+xZ30/bMBB+719h5XGIhIkhTZNbpMGqaQKEgO3dTa6tNcfObKeAov7vk5M0v1MXWgqbeEGy7/Odfb7vOzfg04eQoQVIRQUfOh/dIwcB90VA+Wzo/LwbH352TkcDTKSmPoPRACGsqWYwupZCC18wdC78OASuiaaCYy+zDhBKEkn4DJA7pgzUcmmWKvANypgLR0niXpEQlsvKWoRwRCQxtnNQvqSRWWYg6XSKKPxfglJklocogyAaDJ0kcccxY1kAJ1tYjXwh+Kwj+ibxzQ7oFLnfiRpTYMEqvnFPJgzQVJIQhg5hrAhchMY+I0pxErZ2URpQ5raxMeNiJkUcIV8wNXQ+VZwjhM1kBL4x3tNAz4fOB8fbGnHknthBx02IngMJqjMIYSnu6zMIYeBaPo7S02IvG3RD7h4jWI+4IBNg6yGVC+0EYq+xR+y1DoL1RASNdZV6r1WD9eAVAqzbN2aU/0bmD/Cysk1KTGXnVZQNsWdgo/X+zAqTLVvcJgeyqj+HKYmZ/kVYDMslyodfUIqumpIEeNATo5Vqk8UUXk9/PdnYywhQUNVL+Vaysuqh4Oi3Bw3cqNzueXoFSkOAyggWyp7sg7Jvg9RFTrYl9leiLIirOJyAfGXud1SZNUevxP+2vzPBNaGc8lnDc2l4usZk12I9XGe7fa7UpM5eR3GwV3vjVE1lkfA43P9rZVdqlybbJnHH+5C4LbXJnO0fkJQs3zuXky2puWeS9dAqH/S1+PrvjaLMzQ+SQwYLYP09G1M+FTIkbC1r3rv6e1d/7+r/TVevcX4T4cnr4xbkgvrP+wKxv37e0csvQc/F2/jE8OKilZ0V2bv+DfyJQWlkl68bUJHgCjaAvrhG5Ve5B4HK89NQk3x2a7la5bTlPpt+qv83/HhpGCrS0vmd9NYnjMjs+Z0WXJ229gdL/3PFRscO+4kNsFt7mwutW8pv3I2k0KKfkat3hdAmgf2As4MDq5MfZEGsoOtHPRe8D9Yotxbr25wvu05aEHXG96Vl1YrSr/YVWlXG685gtMIkzIo6i6KNvJnMbQTMstcLbbG1ydUGU+s87ej4FUoOsFf82+NvAAAA//+VaunsKBkAAA==", + "html.tmpl": "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", + "markdown.tmpl": "H4sIAAAAAAAA/+RWQW/bPAy9+1fwS77D2sLJvUh8WLtiGNqiaItdimFVEyYxoEieJQcrIv/3gZJly3bcBih2mi82KYki33uiPIa7XGq5kBwu5aLYotBMp1JEMwaCbXE+0jIbTZMoGo/hkb1wBLmCCyk0Cq2i/T5nYo0wuUo5qrKM9vv/VynHn7QWzucwuWVbLMvoFJ72+8r48Wlcf59EAHWQG1SKrW0cAAC35lqKdbjuquA8XItiaefXUb6IYvvREL81CpVKEcShCmOOO+TQDNt4TcVlGWM9NhD7AfNduugU+X52/n0KTw8LxlkO3xkvEB5fM6Q0lHXGO3LGmpwn0fH01GzXuRDnswwYT9diPsrT9UaPkhmDTY6r+WhMqkgeZTabsmQ2zRKSR72WNp5colrkaUZicp4+yeGuTdW089iFa4g7HDJdweQrU1cp8iUFNGA/wVhYwMA1e0EOBoKVYCIDMT3g3tA2qwdMiB7Fh9hSZpo6wdTyov1CAp194mbbLOz0bhGuhEtcsYJrS2hZQmWeu9nhUCUCm1tbFg0YLe2aRqsNKJ+ZotdtsX3BfAicPkA1PsNANXsfBKuFlbOpk7BUpGLdHXHp/XXU3NDsvzgGFEvYVuqEOE4C0fqe8kHFGqCx97A/Amhb2SDIb0AXgNAvHqlKX/khOQXlDzQ9D8XBbhn9k3p87gnyeVCRDRct6DtqDK6QdwX5lhhvUG/k0mvyHn8VqLRn5R5VJoVCbw+y0iWga3ZtE94FlMBwZ61S6jbYyt3us26+y/moBW8cjR4bqkLcE1GdILr1+rcxQTvJ6LfKY3crNSowcHF25l3f2I7577tXvZGisg5DG2AI/RNxQPourQbaUCj2l88BMYJpAm1XxQOlXJ+KLAvHKPfQdvl7Tw3PnwAAAP//WuevgFwKAAA=", +} + +func fetchResource(name string) ([]byte, error) { + raw, ok := embeddedResources[name] + if !ok { + return nil, fmt.Errorf("Could not find resource for '%s'", name) + } + + compressed, err := base64.StdEncoding.DecodeString(raw) + if err != nil { + return nil, err + } + + var out bytes.Buffer + buf := bytes.NewBuffer(compressed) + + r, err := gzip.NewReader(buf) + if err != nil { + return nil, err + } + + if _, err := io.Copy(&out, r); err != nil { + return nil, err + } + + return out.Bytes(), nil +} diff --git a/template.go b/template.go index 726e706f..5c946309 100644 --- a/template.go +++ b/template.go @@ -52,15 +52,15 @@ type File struct { Description string `json:"file_description"` Package string `json:"file_package"` - Enums []*Enum `json:"file_enums"` - Extensions []*FileExtension `json:"file_extensions"` - Messages []*Message `json:"file_messages"` - Services []*Service `json:"file_services"` - HasEnums bool `json:"file_has_enums"` HasExtensions bool `json:"file_has_extensions"` HasMessages bool `json:"file_has_messages"` HasServices bool `json:"file_has_services"` + + Enums []*Enum `json:"file_enums"` + Extensions []*FileExtension `json:"file_extensions"` + Messages []*Message `json:"file_messages"` + Services []*Service `json:"file_services"` } type FileExtension struct { @@ -85,11 +85,11 @@ type Message struct { FullName string `json:"message_full_name"` Description string `json:"message_description"` - Extensions []*MessageExtension `json:"message_extensions"` - Fields []*MessageField `json:"message_fields"` - HasExtensions bool `json:"message_has_extensions"` HasFields bool `json:"message_has_extensions"` + + Extensions []*MessageExtension `json:"message_extensions"` + Fields []*MessageField `json:"message_fields"` } type MessageField struct { diff --git a/templates/docbook.tmpl b/templates/docbook.tmpl new file mode 100644 index 00000000..5425fe86 --- /dev/null +++ b/templates/docbook.tmpl @@ -0,0 +1,210 @@ + +
+ Protocol Documentation + {{range .Files}} +
+ {{.Name}} + {{.Description}} + {{range .Messages}} +
+ {{.LongName}} + {{.Description}} + {{if .HasFields}} + + <classname>{{.LongName}}</classname> Fields + + + + + + + + Field + Type + Label + Description + + + + {{range .Fields}} + + {{.Name}} + {{.LongType}} + {{.Label}} + {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} + + {{end}} + + +
+ {{end}} + {{if .HasExtensions}} + + <classname>{{.LongName}}</classname> Nested Extensions + + + + + + + + + Extension + Type + Base + Number + Description + + + + {{range .Extensions}} + + {{.Name}} + {{.LongType}} + {{.ContainingLongType}} + {{.Number}} + {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} + + {{end}} + + +
+ {{end}} +
+ {{end}} + {{range .Enums}} +
+ {{.LongName}} + {{.Description}} + + <classname>{{.LongName}}</classname> Values + + + + + + + Name + Number + Description + + + + {{range .Values}} + + {{.Name}} + {{.Number}} + {{.Description}} + + {{end}} + + +
+
+ {{end}} + + {{if .HasExtensions}} +
+ File-level Extensions + + + + + + + + + + Extension + Type + Base + Number + Description + + + + {{range .Extensions}} + + {{.Name}} + {{.LongType}} + {{.ContainingLongType}} + {{.Number}} + {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} + + {{end}} + + + +
+ {{end}} + + {{range .Services}} +
+ {{.Name}} + {{.Description}} + + <classname>{{.Name}}</classname> Methods + + + + + + + + Method Name + Request Type + Response Type + Description + + + + {{range .Methods}} + + {{.Name}} + {{.RequestLongType}} + {{.ResponseLongType}} + {{.Description}} + + {{end}} + + +
+
+ {{end}} +
+ {{end}} + +
+ Scalar Value Types + + + + + + + + + + .proto Type + Notes + C++ Type + Java Type + Python Type + + + + {{range .Scalars}} + + {{.ProtoType}} + {{.Notes}} + {{.CppType}} + {{.JavaType}} + {{.PythonType}} + + {{end}} + + + +
+ +
diff --git a/templates/html.mustache b/templates/html.mustache deleted file mode 100755 index ff7a99d2..00000000 --- a/templates/html.mustache +++ /dev/null @@ -1,340 +0,0 @@ - - - - - Protocol Documentation - - - - - - - - - - -

Protocol Documentation

- -

Table of Contents

- -
- -
- - {{#files}} -
-

{{file_name}}

Top -
- {{#file_description}}{{#p}}{{file_description}}{{/p}}{{/file_description}} - {{#file_messages}} -

{{message_long_name}}

- {{#p}}{{message_description}}{{/p}} - {{#message_has_fields}} - - - - - - {{#message_fields}} - - - - - - - {{/message_fields}} - -
FieldTypeLabelDescription
{{field_name}}{{field_long_type}}{{field_label}}{{#p}}{{field_description}}{{#field_default_value}} Default: {{field_default_value}}{{/field_default_value}}{{/p}}
- {{/message_has_fields}} - {{#message_has_extensions}} -
- - - - - - {{#message_extensions}} - - - - - - - - {{/message_extensions}} - -
ExtensionTypeBaseNumberDescription
{{extension_name}}{{extension_long_type}}{{extension_containing_long_type}}{{extension_number}}{{#p}}{{extension_description}}{{#extension_default_value}} Default: {{extension_default_value}}{{/extension_default_value}}{{/p}}
- {{/message_has_extensions}} - {{/file_messages}} - {{#file_enums}} -

{{enum_long_name}}

- {{#p}}{{enum_description}}{{/p}} - - - - - - {{#enum_values}} - - - - - - {{/enum_values}} - -
NameNumberDescription
{{value_name}}{{value_number}}{{#p}}{{value_description}}{{/p}}
- {{/file_enums}} - {{#file_has_extensions}} -

File-level Extensions

- - - - - - {{#file_extensions}} - - - - - - - - {{/file_extensions}} - -
ExtensionTypeBaseNumberDescription
{{extension_name}}{{extension_long_type}}{{extension_containing_long_type}}{{extension_number}}{{#p}}{{extension_description}}{{#extension_default_value}} Default: {{extension_default_value}}{{/extension_default_value}}{{/p}}
- {{/file_has_extensions}} - {{#file_services}} -

{{service_name}}

- {{#p}}{{service_description}}{{/p}} - - - - - - {{#service_methods}} - - - - - - - {{/service_methods}} - -
Method NameRequest TypeResponse TypeDescription
{{method_name}}{{method_request_long_type}}{{method_response_long_type}}{{#p}}{{method_description}}{{/p}}
- {{/file_services}} - {{/files}} - -

Scalar Value Types

- - - - - - {{#scalar_value_types}} - - - - - - - - {{/scalar_value_types}} - -
.proto TypeNotesC++ TypeJava TypePython Type
{{scalar_value_proto_type}}{{scalar_value_notes}}{{scalar_value_cpp_type}}{{scalar_value_java_type}}{{scalar_value_python_type}}
- - - diff --git a/templates/html.tmpl b/templates/html.tmpl new file mode 100644 index 00000000..f64149b2 --- /dev/null +++ b/templates/html.tmpl @@ -0,0 +1,336 @@ + + + + + Protocol Documentation + + + + + + + + + + +

Protocol Documentation

+ +

Table of Contents

+ +
+ +
+ + {{range .Files}} + {{$file_name := .Name}} +
+

{{.Name}}

Top +
+

{{.Description}}

+ + {{range .Messages}} +

{{.LongName}}

+

{{.Description}}

+ + {{if .HasFields}} + + + + + + {{range .Fields}} + + + + + + {{end}} + +
FieldTypeLabelDescription
{{.Name}}{{.LongType}}{{.Label}}

{{.Description}} {{if .DefaultValue}}Default: {{.DefaultValue}}{{end}}

+
+ {{end}} + + {{if .HasExtensions}} +
+ + + + + + {{range .Extensions}} + + + + + + + + {{end}} + +
ExtensionTypeBaseNumberDescription
{{.Name}}{{.LongType}}{{.ContainingLongType}}{{.Number}}

{{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}}

+ {{end}} + {{end}} + + {{range .Enums}} +

{{.LongName}}

+

{{.Description}}

+ + + + + + {{range .Values}} + + + + + + {{end}} + +
NameNumberDescription
{{.Name}}{{.Number}}

{{.Description}}

+ {{end}} + + {{if .HasExtensions}} +

File-level Extensions

+ + + + + + {{range .Extensions}} + + + + + + + + {{end}} + +
ExtensionTypeBaseNumberDescription
{{.Name}}{{.LongType}}{{.ContainingLongType}}{{.Number}}

{{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}}

+ {{end}} + + {{range .Services}} +

{{.Name}}

+

{{.Description}}

+ + + + + + {{range .Methods}} + + + + + + + {{end}} + +
Method NameRequest TypeResponse TypeDescription
{{.Name}}{{.RequestLongType}}{{.ResponseLongType}}

{{.Description}}

+ {{end}} + {{end}} + +

Scalar Value Types

+ + + + + + {{range .Scalars}} + + + + + + + + {{end}} + +
.proto TypeNotesC++ TypeJava TypePython Type
{{.ProtoType}}{{.Notes}}{{.CppType}}{{.JavaType}}{{.PythonType}}
+ + + diff --git a/templates/markdown.mustache b/templates/markdown.mustache deleted file mode 100644 index 743a1342..00000000 --- a/templates/markdown.mustache +++ /dev/null @@ -1,98 +0,0 @@ -# Protocol Documentation - - -## Table of Contents -{{#files}} -* [{{file_name}}](#{{file_name}}) - {{#file_messages}} - * [{{message_long_name}}](#{{message_full_name}}) - {{/file_messages}} - {{#file_enums}} - * [{{enum_long_name}}](#{{enum_full_name}}) - {{/file_enums}} - {{#file_has_extensions}} - * [File-level Extensions](#{{file_name}}-extensions) - {{/file_has_extensions}} - {{#file_services}} - * [{{service_name}}](#{{service_full_name}}) - {{/file_services}} -{{/files}} -* [Scalar Value Types](#scalar-value-types) - -{{#files}} - -

Top

- -## {{file_name}} - -{{#file_description}}{{& file_description}}{{/file_description}} - -{{#file_messages}} - -### {{message_long_name}} -{{& message_description}} - -{{#message_has_fields}} -| Field | Type | Label | Description | -| ----- | ---- | ----- | ----------- | -{{#message_fields}} -| {{field_name}} | [{{field_long_type}}](#{{field_full_type}}) | {{field_label}} | {{#nobr}}{{& field_description}}{{#field_default_value}} Default: {{field_default_value}}{{/field_default_value}}{{/nobr}} | -{{/message_fields}} -{{/message_has_fields}} - -{{#message_has_extensions}} -| Extension | Type | Base | Number | Description | -| --------- | ---- | ---- | ------ | ----------- | -{{#message_extensions}} -| {{extension_name}} | {{extension_long_type}} | {{extension_containing_long_type}} | {{extension_number}} | {{#nobr}}{{& extension_description}}{{#extension_default_value}} Default: {{extension_default_value}}{{/extension_default_value}}{{/nobr}} | -{{/message_extensions}} -{{/message_has_extensions}} - -{{/file_messages}} - -{{#file_enums}} - -### {{enum_long_name}} -{{& enum_description}} - -| Name | Number | Description | -| ---- | ------ | ----------- | -{{#enum_values}} -| {{value_name}} | {{value_number}} | {{#nobr}}{{& value_description}}{{/nobr}} | -{{/enum_values}} - -{{/file_enums}} - -{{#file_has_extensions}} - -### File-level Extensions -| Extension | Type | Base | Number | Description | -| --------- | ---- | ---- | ------ | ----------- | -{{#file_extensions}} -| {{extension_name}} | {{extension_long_type}} | {{extension_containing_long_type}} | {{extension_number}} | {{#nobr}}{{extension_description}}{{#extension_default_value}} Default: {{extension_default_value}}{{/extension_default_value}}{{/nobr}} | -{{/file_extensions}} -{{/file_has_extensions}} - -{{#file_services}} - -### {{service_name}} -{{& service_description}} - -| Method Name | Request Type | Response Type | Description | -| ----------- | ------------ | ------------- | ------------| -{{#service_methods}} -| {{method_name}} | [{{method_request_long_type}}](#{{method_request_full_type}}) | [{{method_response_long_type}}](#{{method_response_full_type}}) | {{#nobr}}{{& method_description}}{{/nobr}} | -{{/service_methods}} - -{{/file_services}} - -{{/files}} - - -## Scalar Value Types - -| .proto Type | Notes | C++ Type | Java Type | Python Type | -| ----------- | ----- | -------- | --------- | ----------- | -{{#scalar_value_types}} -| {{scalar_value_proto_type}} | {{scalar_value_notes}} | {{scalar_value_cpp_type}} | {{scalar_value_java_type}} | {{scalar_value_python_type}} | -{{/scalar_value_types}} diff --git a/templates/markdown.tmpl b/templates/markdown.tmpl new file mode 100644 index 00000000..14495e23 --- /dev/null +++ b/templates/markdown.tmpl @@ -0,0 +1,102 @@ +# Protocol Documentation + + +## Table of Contents +{{range .Files}} +{{$file_name := .Name}} +* [{{.Name}}](#{{.Name}}) + {{range .Messages}} + * [{{.LongName}}](#{{.FullName}}) + {{end}} + {{range .Enums}} + * [{{.LongName}}](#{{.FullName}}) + {{end}} + {{range .Extensions}} + * [File-level Extensions](#{{$file_name}}-extensions) + {{end}} + {{range .Services}} + * [{{.Name}}](#{{.FullName}}) + {{end}} +{{end}} +* [Scalar Value Types](#scalar-value-types) + +{{range .Files}} +{{$file_name := .Name}} + +

Top

+## {{.Name}} + +{{.Description}} + +{{range .Messages}} + +### {{.LongName}} + +{{.Description}} + +{{if .HasFields}} +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +{{range .Fields -}} + | {{.Name}} | [{{.LongType}}](#{{.FullType}}) | {{.Label}} | {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} | +{{end}} +{{end}} + +{{if .HasExtensions}} +| Extension | Type | Base | Number | Description | +| --------- | ---- | ---- | ------ | ----------- | +{{range .Extensions -}} + | {{.Name}} | {{.LongType}} | {{.ContainingLongType}} | {{.Number}} | {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} | +{{end}} +{{end}} + +{{end}} + +{{range .Enums}} + +### {{.LongName}} + +{{.Description}} + +| Name | Number | Description | +| ---- | ------ | ----------- | +{{range .Values -}} + | {{.Name}} | {{.Number}} | {{.Description}} | +{{end}} + +{{end}} + +{{if .HasExtensions}} + +### File-level Extensions + +| Extension | Type | Base | Number | Description | +| --------- | ---- | ---- | ------ | ----------- | +{{range .Extensions -}} + | {{.Name}} | {{.LongType}} | {{.ContainingLongType}} | {{.Number}} | {{.Description}}{{if .DefaultValue}} Default: `{{.DefaultValue}}`{{end}} | +{{end}} +{{end}} + +{{range .Services}} + +### {{.Name}} + +{{.Description}} + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +{{range .Methods -}} + | {{.Name}} | [{{.RequestLongType}}](#{{.RequestFullType}}) | [{{.ResponseLongType}}](#{{.RequestFullType}}) | {{.Description}} | +{{end}} +{{end}} + +{{end}} + + +## Scalar Value Types + +| .proto Type | Notes | C++ Type | Java Type | Python Type | +| ----------- | ----- | -------- | --------- | ----------- | +{{range .Scalars -}} + | {{.ProtoType}} | {{.Notes}} | {{.CppType}} | {{.JavaType}} | {{.PythonType}} | +{{end}} diff --git a/templates/scalar_value_types.json b/templates/scalar_value_types.json deleted file mode 100644 index f6a04c52..00000000 --- a/templates/scalar_value_types.json +++ /dev/null @@ -1,167 +0,0 @@ -[ - { - "scalar_value_proto_type": "double", - "scalar_value_notes": "", - "scalar_value_cpp_type": "double", - "scalar_value_cs_type": "double", - "scalar_value_go_type": "float64", - "scalar_value_java_type": "double", - "scalar_value_php_type": "float", - "scalar_value_python_type": "float", - "scalar_value_ruby_type": "Float" - }, - { - "scalar_value_proto_type": "float", - "scalar_value_notes": "", - "scalar_value_cpp_type": "float", - "scalar_value_cs_type": "float", - "scalar_value_go_type": "float32", - "scalar_value_java_type": "float", - "scalar_value_php_type": "float", - "scalar_value_python_type": "float", - "scalar_value_ruby_type": "Float" - }, - { - "scalar_value_proto_type": "int32", - "scalar_value_notes": "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.", - "scalar_value_cpp_type": "int32", - "scalar_value_cs_type": "int", - "scalar_value_go_type": "int32", - "scalar_value_java_type": "int", - "scalar_value_php_type": "integer", - "scalar_value_python_type": "int", - "scalar_value_ruby_type": "Bignum or Fixnum (as required)" - }, - { - "scalar_value_proto_type": "int64", - "scalar_value_notes": "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.", - "scalar_value_cpp_type": "int64", - "scalar_value_cs_type": "long", - "scalar_value_go_type": "int64", - "scalar_value_java_type": "long", - "scalar_value_php_type": "integer/string", - "scalar_value_python_type": "int/long", - "scalar_value_ruby_type": "Bignum" - }, - { - "scalar_value_proto_type": "uint32", - "scalar_value_notes": "Uses variable-length encoding.", - "scalar_value_cpp_type": "uint32", - "scalar_value_cs_type": "uint", - "scalar_value_go_type": "uint32", - "scalar_value_java_type": "int", - "scalar_value_php_type": "integer", - "scalar_value_python_type": "int/long", - "scalar_value_ruby_type": "Bignum or Fixnum (as required)" - }, - { - "scalar_value_proto_type": "uint64", - "scalar_value_notes": "Uses variable-length encoding.", - "scalar_value_cpp_type": "uint64", - "scalar_value_cs_type": "ulong", - "scalar_value_go_type": "uint64", - "scalar_value_java_type": "long", - "scalar_value_php_type": "integer/string", - "scalar_value_python_type": "int/long", - "scalar_value_ruby_type": "Bignum or Fixnum (as required)" - }, - { - "scalar_value_proto_type": "sint32", - "scalar_value_notes": "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.", - "scalar_value_cpp_type": "int32", - "scalar_value_cs_type": "int", - "scalar_value_go_type": "int32", - "scalar_value_java_type": "int", - "scalar_value_php_type": "integer", - "scalar_value_python_type": "int", - "scalar_value_ruby_type": "Bignum or Fixnum (as required)" - }, - { - "scalar_value_proto_type": "sint64", - "scalar_value_notes": "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.", - "scalar_value_cpp_type": "int64", - "scalar_value_cs_type": "long", - "scalar_value_go_type": "int64", - "scalar_value_java_type": "long", - "scalar_value_php_type": "integer/string", - "scalar_value_python_type": "int/long", - "scalar_value_ruby_type": "Bignum" - }, - { - "scalar_value_proto_type": "fixed32", - "scalar_value_notes": "Always four bytes. More efficient than uint32 if values are often greater than 2^28.", - "scalar_value_cpp_type": "uint32", - "scalar_value_cs_type": "uint", - "scalar_value_go_type": "uint32", - "scalar_value_java_type": "int", - "scalar_value_php_type": "integer", - "scalar_value_python_type": "int", - "scalar_value_ruby_type": "Bignum or Fixnum (as required)" - }, - { - "scalar_value_proto_type": "fixed64", - "scalar_value_notes": "Always eight bytes. More efficient than uint64 if values are often greater than 2^56.", - "scalar_value_cpp_type": "uint64", - "scalar_value_cs_type": "ulong", - "scalar_value_go_type": "uint64", - "scalar_value_java_type": "long", - "scalar_value_php_type": "integer/string", - "scalar_value_python_type": "int/long", - "scalar_value_ruby_type": "Bignum" - }, - { - "scalar_value_proto_type": "sfixed32", - "scalar_value_notes": "Always four bytes.", - "scalar_value_cpp_type": "int32", - "scalar_value_cs_type": "int", - "scalar_value_go_type": "int32", - "scalar_value_java_type": "int", - "scalar_value_php_type": "integer", - "scalar_value_python_type": "int", - "scalar_value_ruby_type": "Bignum or Fixnum (as required)" - }, - { - "scalar_value_proto_type": "sfixed64", - "scalar_value_notes": "Always eight bytes.", - "scalar_value_cpp_type": "int64", - "scalar_value_cs_type": "long", - "scalar_value_go_type": "int64", - "scalar_value_java_type": "long", - "scalar_value_php_type": "integer/string", - "scalar_value_python_type": "int/long", - "scalar_value_ruby_type": "Bignum" - }, - { - "scalar_value_proto_type": "bool", - "scalar_value_notes": "", - "scalar_value_cpp_type": "bool", - "scalar_value_cs_type": "bool", - "scalar_value_go_type": "bool", - "scalar_value_java_type": "boolean", - "scalar_value_php_type": "boolean", - "scalar_value_python_type": "boolean", - "scalar_value_ruby_type": "TrueClass/FalseClass" - }, - { - "scalar_value_proto_type": "string", - "scalar_value_notes": "A string must always contain UTF-8 encoded or 7-bit ASCII text.", - "scalar_value_cpp_type": "string", - "scalar_value_cs_type": "string", - "scalar_value_go_type": "string", - "scalar_value_java_type": "String", - "scalar_value_php_type": "string", - "scalar_value_python_type": "str/unicode", - "scalar_value_ruby_type": "String (UTF-8)" - }, - { - "scalar_value_proto_type": "bytes", - "scalar_value_notes": "May contain any arbitrary sequence of bytes.", - "scalar_value_cpp_type": "string", - "scalar_value_cs_type": "ByteString", - "scalar_value_go_type": "[]byte", - "scalar_value_java_type": "ByteString", - "scalar_value_php_type": "string", - "scalar_value_python_type": "str", - "scalar_value_ruby_type": "String (ASCII-8BIT)" - } -] From dbd2d50ae8e20da870e472353f49cd4aad5f7ad6 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Mon, 3 Jul 2017 20:10:41 -0400 Subject: [PATCH 15/50] Move test/cmd to build/cmd --- {test => build}/cmd/gen_fixtures/main.go | 0 generator.go | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {test => build}/cmd/gen_fixtures/main.go (100%) diff --git a/test/cmd/gen_fixtures/main.go b/build/cmd/gen_fixtures/main.go similarity index 100% rename from test/cmd/gen_fixtures/main.go rename to build/cmd/gen_fixtures/main.go diff --git a/generator.go b/generator.go index c1fea3b1..d8ccbd0a 100644 --- a/generator.go +++ b/generator.go @@ -1,6 +1,6 @@ package protoc_gen_doc -//go:generate go build ./test/cmd/gen_fixtures +//go:generate go build ./build/cmd/gen_fixtures //go:generate protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=. fixtures/Booking.proto fixtures/Vehicle.proto //go:generate rm gen_fixtures From da880548dbb13da6222adc06c269c21e30579eb7 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Mon, 3 Jul 2017 21:10:56 -0400 Subject: [PATCH 16/50] Add p, para, and nobr functions for templates --- filters.go | 27 +++++ filters_test.go | 56 +++++++++ fixtures/Booking.proto | 2 +- fixtures/generator_request.dat | Bin 7517 -> 7518 bytes renderer.go | 10 +- resources.go | 6 +- templates/docbook.mustache | 210 --------------------------------- templates/docbook.tmpl | 18 +-- templates/html.tmpl | 10 +- templates/markdown.tmpl | 10 +- 10 files changed, 114 insertions(+), 235 deletions(-) create mode 100644 filters.go create mode 100644 filters_test.go delete mode 100644 templates/docbook.mustache diff --git a/filters.go b/filters.go new file mode 100644 index 00000000..8ea370c8 --- /dev/null +++ b/filters.go @@ -0,0 +1,27 @@ +package protoc_gen_doc + +import ( + "fmt" + "html/template" + "regexp" + "strings" +) + +var paraPattern = regexp.MustCompile("(\\n|\\r|\\r\\n)\\s*") + +func PFilter(content string) template.HTML { + paragraphs := paraPattern.Split(content, -1) + return template.HTML(fmt.Sprintf("

%s

", strings.Join(paragraphs, "

"))) +} + +func ParaFilter(content string) string { + paragraphs := paraPattern.Split(content, -1) + return fmt.Sprintf("%s", strings.Join(paragraphs, "")) +} + +func NoBrFilter(content string) string { + withoutCR := strings.Replace(content, "\r", "", -1) + withoutLF := strings.Replace(withoutCR, "\n", "", -1) + + return strings.Replace(withoutLF, "\r\n", "", -1) +} diff --git a/filters_test.go b/filters_test.go new file mode 100644 index 00000000..7ff61ad6 --- /dev/null +++ b/filters_test.go @@ -0,0 +1,56 @@ +package protoc_gen_doc_test + +import ( + "github.com/pseudomuto/protoc-gen-doc" + "github.com/stretchr/testify/suite" + html "html/template" + "testing" +) + +type FilterTest struct { + suite.Suite +} + +func TestFilter(t *testing.T) { + suite.Run(t, new(FilterTest)) +} + +func (assert *FilterTest) TestPFilter() { + tests := map[string]string{ + "Some content.": "

Some content.

", + "Some content.\nRight here.": "

Some content.

Right here.

", + "Some content.\r\nRight here.": "

Some content.

Right here.

", + "Some content.\n\tRight here.": "

Some content.

Right here.

", + "Some content.\r\n\n \r\n Right here.": "

Some content.

Right here.

", + } + + for input, output := range tests { + assert.Equal(html.HTML(output), protoc_gen_doc.PFilter(input)) + } +} + +func (assert *FilterTest) TestParaFilter() { + tests := map[string]string{ + "Some content.": "Some content.", + "Some content.\nRight here.": "Some content.Right here.", + "Some content.\r\nRight here.": "Some content.Right here.", + "Some content.\n\tRight here.": "Some content.Right here.", + "Some content.\r\n\n \r\n Right here.": "Some content.Right here.", + } + + for input, output := range tests { + assert.Equal(output, protoc_gen_doc.ParaFilter(input)) + } +} + +func (assert *FilterTest) TestNoBrFilter() { + tests := map[string]string{ + "My content": "My content", + "My content \r\nHere.": "My content Here.", + "My\n content\r right\r\n here.": "My content right here.", + } + + for input, output := range tests { + assert.Equal(output, protoc_gen_doc.NoBrFilter(input)) + } +} diff --git a/fixtures/Booking.proto b/fixtures/Booking.proto index 5f774117..09b85b83 100644 --- a/fixtures/Booking.proto +++ b/fixtures/Booking.proto @@ -1,7 +1,7 @@ /** * Booking related messages. * - * This file is really just an example. The data model is completely + * This file is really just an example. The data model is completely * fictional. */ syntax = "proto2"; diff --git a/fixtures/generator_request.dat b/fixtures/generator_request.dat index 895c20b8a1b214b69b4271172825a27f3fa52742..33ecb2f2840e95f5691d758f71927cbf2e3120e3 100644 GIT binary patch delta 49 zcmca>b -
- Protocol Documentation - {{#files}} -
- {{file_name}} - {{#file_description}}{{#para}}{{file_description}}{{/para}}{{/file_description}} - {{#file_messages}} -
- {{message_long_name}} - {{#para}}{{message_description}}{{/para}} - {{#message_has_fields}} - - <classname>{{message_long_name}}</classname> Fields - - - - - - - - Field - Type - Label - Description - - - - {{#message_fields}} - - {{field_name}} - {{field_long_type}} - {{field_label}} - {{#para}}{{field_description}}{{#field_default_value}} Default: {{field_default_value}}{{/field_default_value}}{{/para}} - - {{/message_fields}} - - -
- {{/message_has_fields}} - {{#message_has_extensions}} - - <classname>{{message_long_name}}</classname> Nested Extensions - - - - - - - - - Extension - Type - Base - Number - Description - - - - {{#message_extensions}} - - {{extension_name}} - {{extension_long_type}} - {{extension_containing_long_type}} - {{extension_number}} - {{#para}}{{extension_description}}{{#extension_default_value}} Default: {{extension_default_value}}{{/extension_default_value}}{{/para}} - - {{/message_extensions}} - - -
- {{/message_has_extensions}} -
- {{/file_messages}} - {{#file_enums}} -
- {{enum_long_name}} - {{#para}}{{enum_description}}{{/para}} - - <classname>{{enum_long_name}}</classname> Values - - - - - - - Name - Number - Description - - - - {{#enum_values}} - - {{value_name}} - {{value_number}} - {{#para}}{{value_description}}{{/para}} - - {{/enum_values}} - - -
-
- {{/file_enums}} - - {{#file_has_extensions}} -
- File-level Extensions - - - - - - - - - - Extension - Type - Base - Number - Description - - - - {{#file_extensions}} - - {{extension_name}} - {{extension_long_type}} - {{extension_containing_long_type}} - {{extension_number}} - {{#para}}{{extension_description}}{{#extension_default_value}} Default: {{extension_default_value}}{{/extension_default_value}}{{/para}} - - {{/file_extensions}} - - - -
- {{/file_has_extensions}} - - {{#file_services}} -
- {{service_name}} - {{#para}}{{service_description}}{{/para}} - - <classname>{{service_name}}</classname> Methods - - - - - - - - Method Name - Request Type - Response Type - Description - - - - {{#service_methods}} - - {{method_name}} - {{method_request_long_type}} - {{method_response_long_type}} - {{#para}}{{method_description}}{{/para}} - - {{/service_methods}} - - -
-
- {{/file_services}} -
- {{/files}} - -
- Scalar Value Types - - - - - - - - - - .proto Type - Notes - C++ Type - Java Type - Python Type - - - - {{#scalar_value_types}} - - {{scalar_value_proto_type}} - {{scalar_value_notes}} - {{scalar_value_cpp_type}} - {{scalar_value_java_type}} - {{scalar_value_python_type}} - - {{/scalar_value_types}} - - - -
- -
diff --git a/templates/docbook.tmpl b/templates/docbook.tmpl index 5425fe86..24396c5e 100644 --- a/templates/docbook.tmpl +++ b/templates/docbook.tmpl @@ -4,11 +4,11 @@ {{range .Files}}
{{.Name}} - {{.Description}} + {{para .Description}} {{range .Messages}}
{{.LongName}} - {{.Description}} + {{para .Description}} {{if .HasFields}} <classname>{{.LongName}}</classname> Fields @@ -31,7 +31,7 @@ {{.Name}}{{.LongType}}{{.Label}} - {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} + {{para .Description}}{{if .DefaultValue}}Default: {{.DefaultValue}}{{end}} {{end}} @@ -63,7 +63,7 @@ {{.LongType}}{{.ContainingLongType}}{{.Number}} - {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} + {{para .Description}}{{if .DefaultValue}}Default: {{.DefaultValue}}{{end}} {{end}} @@ -75,7 +75,7 @@ {{range .Enums}}
{{.LongName}} - {{.Description}} + {{para .Description}}
<classname>{{.LongName}}</classname> Values @@ -94,7 +94,7 @@ {{.Name}} {{.Number}} - {{.Description}} + {{para .Description}} {{end}} @@ -129,7 +129,7 @@ {{.LongType}} {{.ContainingLongType}} {{.Number}} - {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} + {{para .Description}}{{if .DefaultValue}}Default: {{.DefaultValue}}{{end}} {{end}} @@ -141,7 +141,7 @@ {{range .Services}}
{{.Name}} - {{.Description}} + {{para .Description}}
<classname>{{.Name}}</classname> Methods @@ -163,7 +163,7 @@ {{.Name}} {{.RequestLongType}} {{.ResponseLongType}} - {{.Description}} + {{para .Description}} {{end}} diff --git a/templates/html.tmpl b/templates/html.tmpl index f64149b2..c7b85652 100644 --- a/templates/html.tmpl +++ b/templates/html.tmpl @@ -209,11 +209,11 @@

{{.Name}}

Top
-

{{.Description}}

+ {{p .Description}} {{range .Messages}}

{{.LongName}}

-

{{.Description}}

+ {{p .Description}} {{if .HasFields}}
@@ -226,7 +226,7 @@ - {{end}} @@ -256,7 +256,7 @@ {{range .Enums}}

{{.LongName}}

-

{{.Description}}

+ {{p .Description}}
{{.Name}} {{.LongType}} {{.Label}}

{{.Description}} {{if .DefaultValue}}Default: {{.DefaultValue}}{{end}}

+

{{.Description}} {{if .DefaultValue}}Default: {{.DefaultValue}}{{end}}

@@ -295,7 +295,7 @@ {{range .Services}}

{{.Name}}

-

{{.Description}}

+ {{p .Description}}
NameNumberDescription
diff --git a/templates/markdown.tmpl b/templates/markdown.tmpl index 14495e23..187ed373 100644 --- a/templates/markdown.tmpl +++ b/templates/markdown.tmpl @@ -38,7 +38,7 @@ | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | {{range .Fields -}} - | {{.Name}} | [{{.LongType}}](#{{.FullType}}) | {{.Label}} | {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} | + | {{.Name}} | [{{.LongType}}](#{{.FullType}}) | {{.Label}} | {{nobr .Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} | {{end}} {{end}} @@ -46,7 +46,7 @@ | Extension | Type | Base | Number | Description | | --------- | ---- | ---- | ------ | ----------- | {{range .Extensions -}} - | {{.Name}} | {{.LongType}} | {{.ContainingLongType}} | {{.Number}} | {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} | + | {{.Name}} | {{.LongType}} | {{.ContainingLongType}} | {{.Number}} | {{nobr .Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} | {{end}} {{end}} @@ -61,7 +61,7 @@ | Name | Number | Description | | ---- | ------ | ----------- | {{range .Values -}} - | {{.Name}} | {{.Number}} | {{.Description}} | + | {{.Name}} | {{.Number}} | {{nobr .Description}} | {{end}} {{end}} @@ -73,7 +73,7 @@ | Extension | Type | Base | Number | Description | | --------- | ---- | ---- | ------ | ----------- | {{range .Extensions -}} - | {{.Name}} | {{.LongType}} | {{.ContainingLongType}} | {{.Number}} | {{.Description}}{{if .DefaultValue}} Default: `{{.DefaultValue}}`{{end}} | + | {{.Name}} | {{.LongType}} | {{.ContainingLongType}} | {{.Number}} | {{nobr .Description}}{{if .DefaultValue}} Default: `{{.DefaultValue}}`{{end}} | {{end}} {{end}} @@ -86,7 +86,7 @@ | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| {{range .Methods -}} - | {{.Name}} | [{{.RequestLongType}}](#{{.RequestFullType}}) | [{{.ResponseLongType}}](#{{.RequestFullType}}) | {{.Description}} | + | {{.Name}} | [{{.RequestLongType}}](#{{.RequestFullType}}) | [{{.ResponseLongType}}](#{{.RequestFullType}}) | {{nobr .Description}} | {{end}} {{end}} From c35f13ffdd5dfe697e826fab3b2bdf661f9ee9c8 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Mon, 3 Jul 2017 21:11:35 -0400 Subject: [PATCH 17/50] Rename generator.go -> generate.go --- generator.go => generate.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename generator.go => generate.go (100%) diff --git a/generator.go b/generate.go similarity index 100% rename from generator.go rename to generate.go From e93655384c7ddbd3c567a8f291710f20915c5b26 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Mon, 3 Jul 2017 21:30:14 -0400 Subject: [PATCH 18/50] Enforce sort order for file enums, extensions, messages, and services --- template.go | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/template.go b/template.go index 5c946309..88db649c 100644 --- a/template.go +++ b/template.go @@ -3,6 +3,7 @@ package protoc_gen_doc import ( "fmt" "github.com/pseudomuto/protoc-gen-doc/parser" + "sort" "strings" ) @@ -41,6 +42,11 @@ func NewTemplate(pr *parser.ParseResult) *Template { file.Services = append(file.Services, parseService(s)) } + sort.Sort(file.Enums) + sort.Sort(file.Extensions) + sort.Sort(file.Messages) + sort.Sort(file.Services) + files = append(files, file) } @@ -57,10 +63,10 @@ type File struct { HasMessages bool `json:"file_has_messages"` HasServices bool `json:"file_has_services"` - Enums []*Enum `json:"file_enums"` - Extensions []*FileExtension `json:"file_extensions"` - Messages []*Message `json:"file_messages"` - Services []*Service `json:"file_services"` + Enums orderedEnums `json:"file_enums"` + Extensions orderedExtensions `json:"file_extensions"` + Messages orderedMessages `json:"file_messages"` + Services orderedServices `json:"file_services"` } type FileExtension struct { @@ -436,3 +442,27 @@ func makeScalars() []*ScalarValue { }, } } + +type orderedEnums []*Enum + +func (oe orderedEnums) Len() int { return len(oe) } +func (oe orderedEnums) Swap(i, j int) { oe[i], oe[j] = oe[j], oe[i] } +func (oe orderedEnums) Less(i, j int) bool { return oe[i].LongName < oe[j].LongName } + +type orderedExtensions []*FileExtension + +func (oe orderedExtensions) Len() int { return len(oe) } +func (oe orderedExtensions) Swap(i, j int) { oe[i], oe[j] = oe[j], oe[i] } +func (oe orderedExtensions) Less(i, j int) bool { return oe[i].LongName < oe[j].LongName } + +type orderedMessages []*Message + +func (om orderedMessages) Len() int { return len(om) } +func (om orderedMessages) Swap(i, j int) { om[i], om[j] = om[j], om[i] } +func (om orderedMessages) Less(i, j int) bool { return om[i].LongName < om[j].LongName } + +type orderedServices []*Service + +func (os orderedServices) Len() int { return len(os) } +func (os orderedServices) Swap(i, j int) { os[i], os[j] = os[j], os[i] } +func (os orderedServices) Less(i, j int) bool { return os[i].LongName < os[j].LongName } From a8e7214859f9a0b1eb4fce1ceec3e6ba76c9cd04 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Mon, 3 Jul 2017 21:32:37 -0400 Subject: [PATCH 19/50] Removing unused dep --- glide.lock | 6 ++---- glide.yaml | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/glide.lock b/glide.lock index 6244155f..52c7337a 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,6 @@ -hash: be2dc3c8df5a9b9438a9d286680627d2cf561e7cb544bf1815ccc720d4785da6 -updated: 2017-06-26T10:13:22.128548248-04:00 +hash: c932df53d468fd90b3d4bbd3e1a2787f1133945e8bad773e5e42502b7a5775d5 +updated: 2017-07-03T21:32:05.577813669-04:00 imports: -- name: github.com/cbroglie/mustache - version: 6857e4b493bdb8d4b1931446eb41704aeb4c28cb - name: github.com/golang/protobuf version: c9c7427a2a70d2eb3bafa0ab2dc163e45f143317 subpackages: diff --git a/glide.yaml b/glide.yaml index 4b8344c5..36dfb9e1 100644 --- a/glide.yaml +++ b/glide.yaml @@ -3,4 +3,3 @@ import: - package: github.com/golang/protobuf subpackages: - protoc-gen-go -- package: github.com/cbroglie/mustache From 96800733b4d22fd153cc5cca186e3b8eb55415a0 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 21 Jul 2017 15:44:48 -0400 Subject: [PATCH 20/50] Create the protoc-gen-doc binary --- Makefile | 7 ++- cmd/protoc-gen-doc/main.go | 36 +++++++++++++ plugin.go | 73 ++++++++++++++++++++++++++ plugin_test.go | 102 +++++++++++++++++++++++++++++++++++++ renderer.go | 86 +++++++++++++++++++------------ renderer_test.go | 49 +++++++++--------- 6 files changed, 296 insertions(+), 57 deletions(-) create mode 100644 cmd/protoc-gen-doc/main.go create mode 100644 plugin.go create mode 100644 plugin_test.go diff --git a/Makefile b/Makefile index 8448c6b0..597946cb 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ -.PHONY: bench test +.PHONY: bench test build generate: @go generate test: generate - @go test -cover $(shell go list ./... | grep -v -E 'build|test|tools|vendor') + @go test -cover $(shell go list ./... | grep -v -E 'build|cmd|test|tools|vendor') bench: @go test -bench=. + +build: + @go build ./cmd/... diff --git a/cmd/protoc-gen-doc/main.go b/cmd/protoc-gen-doc/main.go new file mode 100644 index 00000000..20eecea9 --- /dev/null +++ b/cmd/protoc-gen-doc/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/plugin" + "github.com/pseudomuto/protoc-gen-doc" + "io/ioutil" + "log" + "os" +) + +func main() { + input, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalf("Could not read contents from stdin") + } + + req := new(plugin_go.CodeGeneratorRequest) + if err = proto.Unmarshal(input, req); err != nil { + log.Fatal(err) + } + + resp, err := protoc_gen_doc.RunPlugin(req) + if err != nil { + log.Fatal(err) + } + + data, err := proto.Marshal(resp) + if err != nil { + log.Fatal(err) + } + + if _, err := os.Stdout.Write(data); err != nil { + log.Fatal(err) + } +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 00000000..42dc48d4 --- /dev/null +++ b/plugin.go @@ -0,0 +1,73 @@ +package protoc_gen_doc + +import ( + "fmt" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/plugin" + "github.com/pseudomuto/protoc-gen-doc/parser" + "path" + "strings" +) + +// , +type PluginOptions struct { + Type RenderType + TemplateFile string + OutputFile string +} + +func ParseOptions(req *plugin_go.CodeGeneratorRequest) (*PluginOptions, error) { + options := &PluginOptions{ + Type: RenderTypeHtml, + TemplateFile: "", + OutputFile: "index.html", + } + + params := req.GetParameter() + if params == "" { + return options, nil + } + + if !strings.Contains(params, ",") { + return nil, fmt.Errorf("Invalid parameter: %s", params) + } + + parts := strings.Split(params, ",") + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid parameter: %s", params) + } + + options.TemplateFile = parts[0] + options.OutputFile = path.Base(parts[1]) + + renderType, err := NewRenderType(options.TemplateFile) + if err == nil { + options.Type = renderType + options.TemplateFile = "" + } + + return options, nil +} + +func RunPlugin(request *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGeneratorResponse, error) { + result := parser.ParseCodeRequest(request) + template := NewTemplate(result) + + options, err := ParseOptions(request) + if err != nil { + return nil, err + } + + output, err := RenderTemplate(options.Type, template) + if err != nil { + return nil, err + } + + resp := new(plugin_go.CodeGeneratorResponse) + resp.File = append(resp.File, &plugin_go.CodeGeneratorResponse_File{ + Name: proto.String(options.OutputFile), + Content: proto.String(string(output)), + }) + + return resp, nil +} diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 00000000..a9f367ea --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,102 @@ +package protoc_gen_doc_test + +import ( + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/plugin" + "github.com/pseudomuto/protoc-gen-doc" + "github.com/stretchr/testify/suite" + "testing" +) + +type PluginTest struct { + suite.Suite +} + +func TestPlugin(t *testing.T) { + suite.Run(t, new(PluginTest)) +} + +func (assert *PluginTest) TestParseOptionsForBuiltinTemplates() { + results := map[string]string{ + "docbook": "output.xml", + "html": "output.html", + "json": "output.json", + "markdown": "output.md", + } + + for kind, file := range results { + req := new(plugin_go.CodeGeneratorRequest) + req.Parameter = proto.String(kind + "," + file) + + options, err := protoc_gen_doc.ParseOptions(req) + assert.Nil(err) + + renderType, err := protoc_gen_doc.NewRenderType(kind) + assert.Nil(err) + + assert.Equal(renderType, options.Type) + assert.Equal("", options.TemplateFile) + assert.Equal(file, options.OutputFile) + } +} + +func (assert *PluginTest) TestParseOptionsForCustomTemplate() { + req := new(plugin_go.CodeGeneratorRequest) + req.Parameter = proto.String("/path/to/template.tmpl,/base/name/only/output.md") + + options, err := protoc_gen_doc.ParseOptions(req) + assert.Nil(err) + + assert.Equal(protoc_gen_doc.RenderTypeHtml, options.Type) + assert.Equal("/path/to/template.tmpl", options.TemplateFile) + assert.Equal("output.md", options.OutputFile) +} + +func (assert *PluginTest) TestParseOptionsWithInvalidValues() { + badValues := []string{ + "markdown", + "html", + "/some/path.tmpl", + "more,than,1,comma", + } + + for _, value := range badValues { + req := new(plugin_go.CodeGeneratorRequest) + req.Parameter = proto.String(value) + + _, err := protoc_gen_doc.ParseOptions(req) + assert.NotNil(err) + } +} + +func (assert *PluginTest) TestRunPluginForBuiltinTemplate() { + req := new(plugin_go.CodeGeneratorRequest) + req.Parameter = proto.String("markdown,/base/name/only/output.md") + + resp, err := protoc_gen_doc.RunPlugin(req) + assert.Nil(err) + + assert.Equal(1, len(resp.File)) + assert.Equal("output.md", resp.File[0].GetName()) + assert.NotEmpty(resp.File[0].GetContent()) +} + +func (assert *PluginTest) TestRunPluginForCustomTemplate() { + req := new(plugin_go.CodeGeneratorRequest) + req.Parameter = proto.String("templates/html.tmpl,/base/name/only/output.html") + + resp, err := protoc_gen_doc.RunPlugin(req) + assert.Nil(err) + + assert.Equal(1, len(resp.File)) + assert.Equal("output.html", resp.File[0].GetName()) + assert.NotEmpty(resp.File[0].GetContent()) +} + +func (assert *PluginTest) TestRunPluginWithInvalidOptions() { + req := new(plugin_go.CodeGeneratorRequest) + req.Parameter = proto.String("html") + + _, err := protoc_gen_doc.RunPlugin(req) + assert.NotNil(err) +} diff --git a/renderer.go b/renderer.go index 431c7089..ecea515f 100644 --- a/renderer.go +++ b/renderer.go @@ -3,8 +3,8 @@ package protoc_gen_doc import ( "bytes" "encoding/json" + "errors" html_template "html/template" - "io/ioutil" text_template "text/template" ) @@ -18,51 +18,73 @@ const ( RenderTypeMarkdown ) -var funcMap = map[string]interface{}{ - "p": PFilter, - "para": ParaFilter, - "nobr": NoBrFilter, -} +func NewRenderType(renderType string) (RenderType, error) { + switch renderType { + case "docbook": + return RenderTypeDocBook, nil + case "html": + return RenderTypeHtml, nil + case "json": + return RenderTypeJson, nil + case "markdown": + return RenderTypeMarkdown, nil + } -type Processor interface { - Apply(template *Template) ([]byte, error) + return 0, errors.New("Invalid render type") } -func RenderTemplate(kind RenderType, template *Template, outputPath string) error { - var processor Processor +func (rt RenderType) renderer() (Processor, error) { + tmpl, err := rt.template() + if err != nil { + return nil, err + } - switch kind { + switch rt { case RenderTypeDocBook: - res, err := fetchResource("docbook.tmpl") - if err != nil { - return err - } - - processor = &textRenderer{string(res)} + return &textRenderer{string(tmpl)}, nil case RenderTypeHtml: - res, err := fetchResource("html.tmpl") - if err != nil { - return err - } - - processor = &htmlRenderer{string(res)} + return &htmlRenderer{string(tmpl)}, nil case RenderTypeJson: - processor = &jsonRenderer{} + return new(jsonRenderer), nil case RenderTypeMarkdown: - res, err := fetchResource("markdown.tmpl") - if err != nil { - return err - } + return &htmlRenderer{string(tmpl)}, nil + } + + return nil, errors.New("Unable to create a processor") +} - processor = &htmlRenderer{string(res)} +func (rt RenderType) template() ([]byte, error) { + switch rt { + case RenderTypeDocBook: + return fetchResource("docbook.tmpl") + case RenderTypeHtml: + return fetchResource("html.tmpl") + case RenderTypeJson: + return nil, nil + case RenderTypeMarkdown: + return fetchResource("markdown.tmpl") } - result, err := processor.Apply(template) + return nil, errors.New("Couldn't find template for render type") +} + +var funcMap = map[string]interface{}{ + "p": PFilter, + "para": ParaFilter, + "nobr": NoBrFilter, +} + +type Processor interface { + Apply(template *Template) ([]byte, error) +} + +func RenderTemplate(kind RenderType, template *Template) ([]byte, error) { + processor, err := kind.renderer() if err != nil { - return err + return nil, err } - return ioutil.WriteFile(outputPath, result, 0644) + return processor.Apply(template) } type textRenderer struct { diff --git a/renderer_test.go b/renderer_test.go index 17b6f3eb..6c530558 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -32,41 +32,44 @@ func (assert *RendererTest) SetupSuite() { } func (assert *RendererTest) TestDocBookRenderer() { - err := protoc_gen_doc.RenderTemplate( - protoc_gen_doc.RenderTypeDocBook, - renderTemplate, - tempTestDir+"/output.docbook", - ) - + _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeDocBook, renderTemplate) assert.Nil(err) } func (assert *RendererTest) TestHtmlRenderer() { - err := protoc_gen_doc.RenderTemplate( - protoc_gen_doc.RenderTypeHtml, - renderTemplate, - tempTestDir+"/output.html", - ) - + _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeHtml, renderTemplate) assert.Nil(err) } func (assert *RendererTest) TestJsonRenderer() { - err := protoc_gen_doc.RenderTemplate( - protoc_gen_doc.RenderTypeJson, - renderTemplate, - tempTestDir+"/output.json", - ) - + _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeJson, renderTemplate) assert.Nil(err) } func (assert *RendererTest) TestMarkdownRenderer() { - err := protoc_gen_doc.RenderTemplate( + _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeMarkdown, renderTemplate) + assert.Nil(err) +} + +func (assert *RendererTest) TestNewRenderType() { + expected := []protoc_gen_doc.RenderType{ + protoc_gen_doc.RenderTypeDocBook, + protoc_gen_doc.RenderTypeHtml, + protoc_gen_doc.RenderTypeJson, protoc_gen_doc.RenderTypeMarkdown, - renderTemplate, - tempTestDir+"/output.md", - ) + } - assert.Nil(err) + supplied := []string{"docbook", "html", "json", "markdown"} + + for idx, input := range supplied { + rt, err := protoc_gen_doc.NewRenderType(input) + assert.Nil(err) + assert.Equal(expected[idx], rt) + } +} + +func (assert *RendererTest) TestNewRenderTypeUnknown() { + rt, err := protoc_gen_doc.NewRenderType("/some/template.tmpl") + assert.Zero(rt) + assert.NotNil(err) } From 5ce44e51df616af5485628d3ef58c5e23ab64586 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 21 Jul 2017 16:31:20 -0400 Subject: [PATCH 21/50] Generate examples with `make examples` --- Makefile | 10 +- examples/Makefile | 32 - examples/NMakefile | 20 - examples/doc/example.docbook | 181 ++- examples/doc/example.html | 1348 ++++++++++++--------- examples/doc/example.json | 1074 ++++++++-------- examples/doc/example.md | 222 +++- examples/doc/example.pdf | Bin 47266 -> 0 bytes examples/doc/example.txt | 248 ++++ examples/doc/example_types.md | 26 - examples/proto/Booking.proto | 30 +- examples/proto/Customer.proto | 31 +- examples/proto/SpecialCases.proto | 11 - examples/proto/Vehicle.proto | 119 +- examples/templates/asciidoc.mustache | 45 - examples/templates/asciidoc.tmpl | 45 + examples/templates/example_types.mustache | 14 - plugin.go | 14 +- renderer.go | 7 +- renderer_test.go | 8 +- 20 files changed, 2004 insertions(+), 1481 deletions(-) delete mode 100644 examples/Makefile delete mode 100644 examples/NMakefile delete mode 100644 examples/doc/example.pdf create mode 100644 examples/doc/example.txt delete mode 100644 examples/doc/example_types.md delete mode 100644 examples/proto/SpecialCases.proto delete mode 100644 examples/templates/asciidoc.mustache create mode 100644 examples/templates/asciidoc.tmpl delete mode 100644 examples/templates/example_types.mustache diff --git a/Makefile b/Makefile index 597946cb..c94cbaa3 100644 --- a/Makefile +++ b/Makefile @@ -9,5 +9,13 @@ test: generate bench: @go test -bench=. -build: +build: generate @go build ./cmd/... + +examples: build + @rm -f examples/doc/* + @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=docbook,example.docbook examples/proto/*.proto + @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=html,example.html examples/proto/*.proto + @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=json,example.json examples/proto/*.proto + @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=markdown,example.md examples/proto/*.proto + @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=examples/templates/asciidoc.tmpl,example.txt examples/proto/*.proto diff --git a/examples/Makefile b/examples/Makefile deleted file mode 100644 index c8d77076..00000000 --- a/examples/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -DOCBOOK_XSL?=/usr/share/xml/docbook/xsl-stylesheets-1.79.1/fo/docbook.xsl - -export PATH := ..:$(PATH) - -all: doc/example.md doc/example.html doc/example.docbook doc/example.pdf doc/example.json doc/example_types.md - -doc/example.md: proto/*.proto ../protoc-gen-doc - protoc --doc_out=markdown,example.md:doc proto/*.proto - -doc/example.html: proto/*.proto ../protoc-gen-doc - protoc --doc_out=html,example.html:doc proto/*.proto - -doc/example.docbook: proto/*.proto ../protoc-gen-doc - protoc --doc_out=docbook,example.docbook:doc proto/*.proto - -doc/example.pdf: doc/example.docbook - fop -xml doc/example.docbook \ - -xsl $(DOCBOOK_XSL) \ - -param use.extensions 0 \ - -param fop1.extensions 1 \ - -param paper.type A4 \ - -param page.orientation landscape \ - -pdf doc/example.pdf - -doc/example.json: proto/*.proto ../protoc-gen-doc - protoc --doc_out=json,example.json:doc proto/*.proto - -doc/example_types.md: proto/*.proto ../protoc-gen-doc - protoc --doc_out=./templates/example_types.mustache,example_types.md:doc proto/*.proto - -clean: - -rm doc/* diff --git a/examples/NMakefile b/examples/NMakefile deleted file mode 100644 index e0f5a299..00000000 --- a/examples/NMakefile +++ /dev/null @@ -1,20 +0,0 @@ -PATH=..\release;$(PATH) - -INPUT=proto\Booking.proto proto\Customer.proto proto\Vehicle.proto - -all: doc\example.md doc\example.html doc\example.docbook doc\example.json - -doc\example.md: $(INPUT) ..\release\protoc-gen-doc.exe - protoc --doc_out=markdown,example.md:doc $(INPUT) - -doc\example.html: $(INPUT) ..\release\protoc-gen-doc.exe - protoc --doc_out=html,example.html:doc $(INPUT) - -doc\example.docbook: $(INPUT) ..\release\protoc-gen-doc.exe - protoc --doc_out=docbook,example.docbook:doc $(INPUT) - -doc\example.json: $(INPUT) ..\release\protoc-gen-doc.exe - protoc --doc_out=json,example.json:doc $(INPUT) - -clean: - del /Q doc\* diff --git a/examples/doc/example.docbook b/examples/doc/example.docbook index d8c47bb8..866223cc 100644 --- a/examples/doc/example.docbook +++ b/examples/doc/example.docbook @@ -1,15 +1,15 @@ -
Protocol Documentation +
Booking.proto - Booking related messages.This file is really just an example. The data model is completely -fictional.Author: Elvis Stansvik + Booking related messages.This file is really just an example. The data model is completelyfictional. +
Booking Represents the booking of a vehicle.Vehicles are some cool shit. But drive carefully! +
Method NameRequest TypeResponse TypeDescription
<classname>Booking</classname> Fields @@ -26,43 +26,53 @@ fictional.Author: Elvis Stansvik + vehicle_id int32 - required + ID of booked vehicle. + customer_id int32 - required + Customer that booked the vehicle. + status BookingStatus - required + Status of the booking. + confirmation_sent bool - required + Has booking confirmation been sent? + payment_received bool - required + Has payment been received? +
+ +
+
BookingStatus Represents the status of a vehicle booking. + <classname>BookingStatus</classname> Fields @@ -79,24 +89,40 @@ fictional.Author: Elvis Stansvik + id int32 - required + Unique booking status ID. + description string - required - Booking status description. E.g. "Active". + + Booking status description. E.g. "Active". +
+ +
+ +
+ EmptyBookingMessage + An empty message for testing + + +
+ + + +
BookingService Service for handling vehicle bookings. @@ -116,23 +142,29 @@ fictional.Author: Elvis Stansvik + BookVehicle Booking BookingStatus Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned. +
+
+
Customer.proto - This file has messages for describing a customer.Author: Elvis Stansvik + This file has messages for describing a customer. +
Address Represents a mail address. + <classname>Address</classname> Fields @@ -149,49 +181,60 @@ fictional.Author: Elvis Stansvik + address_line_1 string required First address line. + address_line_2 string optional Second address line. + address_line_3 string optional Second address line. + town string required Address town. + county string optional Address county, if applicable. + country string required Address country. +
+ +
+
Customer Represents a customer. + <classname>Customer</classname> Fields @@ -208,69 +251,78 @@ fictional.Author: Elvis Stansvik + id int32 required Unique customer ID. + first_name string required Customer first name. + last_name string required Customer last name. + details string optional Customer details. + email_address string optional Customer e-mail address. + phone_number string repeated Customer phone numbers, primary first. + mail_addresses Address repeated Customer mail addresses, primary first. +
+ +
-
+ + -
- SpecialCases.proto - Special cases for testingThis is of course entirely fictional/useless model. -
- Empty - Represents a message with no fields -
-
+ + + +
Vehicle.proto Messages describing manufacturers / vehicles. +
Manufacturer Represents a manufacturer of cars. + <classname>Manufacturer</classname> Fields @@ -287,37 +339,46 @@ fictional.Author: Elvis Stansvik + id int32 required The unique manufacturer ID. + code string required - A manufacturer code, e.g. "DKL4P". + A manufacturer code, e.g. "DKL4P". + details string optional Manufacturer details (minimum orders et.c.). + category Manufacturer.Category optional - Manufacturer category. Default: CATEGORY_EXTERNAL + Manufacturer category. +
+ +
+
Model Represents a vehicle model. + <classname>Model</classname> Fields @@ -334,43 +395,53 @@ fictional.Author: Elvis Stansvik + id string required The unique model ID. + model_code string required - The car model code, e.g. "PZ003". + The car model code, e.g. "PZ003". + model_name string required - The car model name, e.g. "Z3". + The car model name, e.g. "Z3". + daily_hire_rate_dollars sint32 required Dollars per day. + daily_hire_rate_cents sint32 required Cents per day. +
+ +
+
Vehicle Represents a vehicle that can be hired. + <classname>Vehicle</classname> Fields @@ -387,51 +458,61 @@ fictional.Author: Elvis Stansvik + id int32 required Unique vehicle ID. + model Model required Vehicle model. + reg_number string required Vehicle registration number. + mileage sint32 optional Current vehicle mileage, if known. + category Vehicle.Category optional Vehicle category. + daily_hire_rate_dollars sint32 optional - Dollars per day. Default: 50 + Dollars per day. + daily_hire_rate_cents sint32 optional Cents per day. +
+ + <classname>Vehicle</classname> Nested Extensions @@ -450,6 +531,7 @@ fictional.Author: Elvis Stansvik + series string @@ -457,13 +539,17 @@ fictional.Author: Elvis Stansvik 100 Vehicle model series. +
+
+
Vehicle.Category - Represents a vehicle category. E.g. "Sedan" or "Truck". + Represents a vehicle category. E.g. "Sedan" or "Truck". + <classname>Vehicle.Category</classname> Fields @@ -480,22 +566,29 @@ fictional.Author: Elvis Stansvik + code string required - Category code. E.g. "S". + Category code. E.g. "S". + description string required - Category name. E.g. "Sedan". + Category name. E.g. "Sedan". +
+ +
+ +
Manufacturer.Category Manufacturer category. A manufacturer may be either inhouse or external. @@ -513,21 +606,26 @@ fictional.Author: Elvis Stansvik + CATEGORY_INHOUSE 0 The manufacturer is inhouse. + CATEGORY_EXTERNAL 1 The manufacturer is external. +
+ +
File-level Extensions @@ -547,19 +645,24 @@ fictional.Author: Elvis Stansvik + country string Manufacturer 100 - Manufacturer country. Default: "China" + Manufacturer country.Default: China +
+ +
+
Scalar Value Types @@ -580,6 +683,7 @@ fictional.Author: Elvis Stansvik + double @@ -587,6 +691,7 @@ fictional.Author: Elvis Stansvik double float + float @@ -594,6 +699,7 @@ fictional.Author: Elvis Stansvik float float + int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. @@ -601,6 +707,7 @@ fictional.Author: Elvis Stansvik int int + int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. @@ -608,6 +715,7 @@ fictional.Author: Elvis Stansvik long int/long + uint32 Uses variable-length encoding. @@ -615,6 +723,7 @@ fictional.Author: Elvis Stansvik int int/long + uint64 Uses variable-length encoding. @@ -622,6 +731,7 @@ fictional.Author: Elvis Stansvik long int/long + sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. @@ -629,6 +739,7 @@ fictional.Author: Elvis Stansvik int int + sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. @@ -636,6 +747,7 @@ fictional.Author: Elvis Stansvik long int/long + fixed32 Always four bytes. More efficient than uint32 if values are often greater than 2^28. @@ -643,6 +755,7 @@ fictional.Author: Elvis Stansvik int int + fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 2^56. @@ -650,6 +763,7 @@ fictional.Author: Elvis Stansvik long int/long + sfixed32 Always four bytes. @@ -657,6 +771,7 @@ fictional.Author: Elvis Stansvik int int + sfixed64 Always eight bytes. @@ -664,6 +779,7 @@ fictional.Author: Elvis Stansvik long int/long + bool @@ -671,6 +787,7 @@ fictional.Author: Elvis Stansvik boolean boolean + string A string must always contain UTF-8 encoded or 7-bit ASCII text. @@ -678,6 +795,7 @@ fictional.Author: Elvis Stansvik String str/unicode + bytes May contain any arbitrary sequence of bytes. @@ -685,6 +803,7 @@ fictional.Author: Elvis Stansvik ByteString str + diff --git a/examples/doc/example.html b/examples/doc/example.html index 3018f667..d153eb20 100644 --- a/examples/doc/example.html +++ b/examples/doc/example.html @@ -63,57 +63,57 @@ } td p:nth-child(1) { - text-indent: 0; /* No indent on first p in td */ + text-indent: 0; } - /* Table of fields */ - .field-table td:nth-child(1) { /* Field */ + + .field-table td:nth-child(1) { width: 10em; } - .field-table td:nth-child(2) { /* Type */ + .field-table td:nth-child(2) { width: 10em; } - .field-table td:nth-child(3) { /* Label */ + .field-table td:nth-child(3) { width: 6em; } - .field-table td:nth-child(4) { /* Description */ + .field-table td:nth-child(4) { width: auto; } - /* Table of extensions */ - .extension-table td:nth-child(1) { /* Extension */ + + .extension-table td:nth-child(1) { width: 10em; } - .extension-table td:nth-child(2) { /* Type */ + .extension-table td:nth-child(2) { width: 10em; } - .extension-table td:nth-child(3) { /* Base */ + .extension-table td:nth-child(3) { width: 10em; } - .extension-table td:nth-child(4) { /* Number */ + .extension-table td:nth-child(4) { width: 5em; } - .extension-table td:nth-child(5) { /* Description */ + .extension-table td:nth-child(5) { width: auto; } - /* Table of enum values. */ - .enum-table td:nth-child(1) { /* Name */ + + .enum-table td:nth-child(1) { width: 10em; } - .enum-table td:nth-child(2) { /* Number */ + .enum-table td:nth-child(2) { width: 10em; } - .enum-table td:nth-child(3) { /* Description */ + .enum-table td:nth-child(3) { width: auto; } - /* Table of scalar value types. */ + .scalar-value-types-table tr { height: 3em; } - /* Table of contents. */ + #toc-container ul { list-style-type: none; padding-left: 1em; @@ -124,7 +124,7 @@ font-weight: bold; } - /* File heading div */ + .file-heading { width: 100%; display: table; @@ -140,7 +140,7 @@ display: table-cell; } - /* The 'M', 'E' and 'X' badges in the ToC */ + .badge { width: 1.6em; height: 1.6em; @@ -160,7 +160,7 @@ } - + @@ -172,485 +172,613 @@

Table of Contents

-
-

Booking.proto

Top -
-

Booking related messages.

This file is really just an example. The data model is completely -fictional.

Author: Elvis Stansvik

-

Booking

-

Represents the booking of a vehicle.

Vehicles are some cool shit. But drive carefully!

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldTypeLabelDescription
vehicle_idint32required

ID of booked vehicle.

customer_idint32required

Customer that booked the vehicle.

statusBookingStatusrequired

Status of the booking.

confirmation_sentboolrequired

Has booking confirmation been sent?

payment_receivedboolrequired

Has payment been received?

-

BookingStatus

-

Represents the status of a vehicle booking.

- - - - - - - - - - - - - - - - - - -
FieldTypeLabelDescription
idint32required

Unique booking status ID.

descriptionstringrequired

Booking status description. E.g. "Active".

-

BookingService

-

Service for handling vehicle bookings.

- - - - - - - - - - - - -
Method NameRequest TypeResponse TypeDescription
BookVehicleBookingBookingStatus

Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.

-
-

Customer.proto

Top -
-

This file has messages for describing a customer.

Author: Elvis Stansvik

-

Address

-

Represents a mail address.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldTypeLabelDescription
address_line_1stringrequired

First address line.

address_line_2stringoptional

Second address line.

address_line_3stringoptional

Second address line.

townstringrequired

Address town.

countystringoptional

Address county, if applicable.

countrystringrequired

Address country.

-

Customer

-

Represents a customer.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldTypeLabelDescription
idint32required

Unique customer ID.

first_namestringrequired

Customer first name.

last_namestringrequired

Customer last name.

detailsstringoptional

Customer details.

email_addressstringoptional

Customer e-mail address.

phone_numberstringrepeated

Customer phone numbers, primary first.

mail_addressesAddressrepeated

Customer mail addresses, primary first.

-
-

SpecialCases.proto

Top -
-

Special cases for testing

This is of course entirely fictional/useless model.

-

Empty

-

Represents a message with no fields

-
-

Vehicle.proto

Top -
-

Messages describing manufacturers / vehicles.

-

Manufacturer

-

Represents a manufacturer of cars.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldTypeLabelDescription
idint32required

The unique manufacturer ID.

codestringrequired

A manufacturer code, e.g. "DKL4P".

detailsstringoptional

Manufacturer details (minimum orders et.c.).

categoryManufacturer.Categoryoptional

Manufacturer category. Default: CATEGORY_EXTERNAL

-

Model

-

Represents a vehicle model.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldTypeLabelDescription
idstringrequired

The unique model ID.

model_codestringrequired

The car model code, e.g. "PZ003".

model_namestringrequired

The car model name, e.g. "Z3".

daily_hire_rate_dollarssint32required

Dollars per day.

daily_hire_rate_centssint32required

Cents per day.

-

Vehicle

-

Represents a vehicle that can be hired.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldTypeLabelDescription
idint32required

Unique vehicle ID.

modelModelrequired

Vehicle model.

reg_numberstringrequired

Vehicle registration number.

mileagesint32optional

Current vehicle mileage, if known.

categoryVehicle.Categoryoptional

Vehicle category.

daily_hire_rate_dollarssint32optional

Dollars per day. Default: 50

daily_hire_rate_centssint32optional

Cents per day.

-
- - - - - - - - - - - - - -
ExtensionTypeBaseNumberDescription
seriesstringModel100

Vehicle model series.

-

Vehicle.Category

-

Represents a vehicle category. E.g. "Sedan" or "Truck".

- - - - - - - - - - - - - - - - - - -
FieldTypeLabelDescription
codestringrequired

Category code. E.g. "S".

descriptionstringrequired

Category name. E.g. "Sedan".

-

Manufacturer.Category

-

Manufacturer category. A manufacturer may be either inhouse or external.

- - - - - - - - - - - - - - - - -
NameNumberDescription
CATEGORY_INHOUSE0

The manufacturer is inhouse.

CATEGORY_EXTERNAL1

The manufacturer is external.

-

File-level Extensions

- - - - - - - - - - - - - -
ExtensionTypeBaseNumberDescription
countrystringManufacturer100

Manufacturer country. Default: "China"

+ + +
+

Booking.proto

Top +
+

Booking related messages.

This file is really just an example. The data model is completely

fictional.

+ + +

Booking

+

Represents the booking of a vehicle.

Vehicles are some cool shit. But drive carefully!

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
vehicle_idint32

ID of booked vehicle.

customer_idint32

Customer that booked the vehicle.

statusBookingStatus

Status of the booking.

confirmation_sentbool

Has booking confirmation been sent?

payment_receivedbool

Has payment been received?

+ + + + +

BookingStatus

+

Represents the status of a vehicle booking.

+ + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idint32

Unique booking status ID.

descriptionstring

Booking status description. E.g. "Active".

+ + + + +

EmptyBookingMessage

+

An empty message for testing

+ + + + + + + + + + + +

BookingService

+

Service for handling vehicle bookings.

+ + + + + + + + + + + + + + +
Method NameRequest TypeResponse TypeDescription
BookVehicleBookingBookingStatus

Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.

+ + + +
+

Customer.proto

Top +
+

This file has messages for describing a customer.

+ + +

Address

+

Represents a mail address.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
address_line_1stringrequired

First address line.

address_line_2stringoptional

Second address line.

address_line_3stringoptional

Second address line.

townstringrequired

Address town.

countystringoptional

Address county, if applicable.

countrystringrequired

Address country.

+ + + + +

Customer

+

Represents a customer.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idint32required

Unique customer ID.

first_namestringrequired

Customer first name.

last_namestringrequired

Customer last name.

detailsstringoptional

Customer details.

email_addressstringoptional

Customer e-mail address.

phone_numberstringrepeated

Customer phone numbers, primary first.

mail_addressesAddressrepeated

Customer mail addresses, primary first.

+ + + + + + + + + + + + +
+

Vehicle.proto

Top +
+

Messages describing manufacturers / vehicles.

+ + +

Manufacturer

+

Represents a manufacturer of cars.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idint32required

The unique manufacturer ID.

codestringrequired

A manufacturer code, e.g. "DKL4P".

detailsstringoptional

Manufacturer details (minimum orders et.c.).

categoryManufacturer.Categoryoptional

Manufacturer category.

+ + + + +

Model

+

Represents a vehicle model.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idstringrequired

The unique model ID.

model_codestringrequired

The car model code, e.g. "PZ003".

model_namestringrequired

The car model name, e.g. "Z3".

daily_hire_rate_dollarssint32required

Dollars per day.

daily_hire_rate_centssint32required

Cents per day.

+ + + + +

Vehicle

+

Represents a vehicle that can be hired.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idint32required

Unique vehicle ID.

modelModelrequired

Vehicle model.

reg_numberstringrequired

Vehicle registration number.

mileagesint32optional

Current vehicle mileage, if known.

categoryVehicle.Categoryoptional

Vehicle category.

daily_hire_rate_dollarssint32optional

Dollars per day.

daily_hire_rate_centssint32optional

Cents per day.

+ + + +
+ + + + + + + + + + + + + + + +
ExtensionTypeBaseNumberDescription
seriesstringModel100

Vehicle model series.

+ + +

Vehicle.Category

+

Represents a vehicle category. E.g. "Sedan" or "Truck".

+ + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
codestringrequired

Category code. E.g. "S".

descriptionstringrequired

Category name. E.g. "Sedan".

+ + + + + + +

Manufacturer.Category

+

Manufacturer category. A manufacturer may be either inhouse or external.

+ + + + + + + + + + + + + + + + + + + +
NameNumberDescription
CATEGORY_INHOUSE0

The manufacturer is inhouse.

CATEGORY_EXTERNAL1

The manufacturer is external.

+ + + +

File-level Extensions

+ + + + + + + + + + + + + + + +
ExtensionTypeBaseNumberDescription
countrystringManufacturer100

Manufacturer country. Default: China

+ + + +

Scalar Value Types

@@ -658,113 +786,129 @@

Scalar Value Types

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
.proto TypeNotesC++ TypeJava TypePython Type
doubledoubledoublefloat
floatfloatfloatfloat
int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.int32intint
int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.int64longint/long
uint32Uses variable-length encoding.uint32intint/long
uint64Uses variable-length encoding.uint64longint/long
sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.int32intint
sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.int64longint/long
fixed32Always four bytes. More efficient than uint32 if values are often greater than 2^28.uint32intint
fixed64Always eight bytes. More efficient than uint64 if values are often greater than 2^56.uint64longint/long
sfixed32Always four bytes.int32intint
sfixed64Always eight bytes.int64longint/long
boolboolbooleanboolean
stringA string must always contain UTF-8 encoded or 7-bit ASCII text.stringStringstr/unicode
bytesMay contain any arbitrary sequence of bytes.stringByteStringstr
doubledoubledoublefloat
floatfloatfloatfloat
int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.int32intint
int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.int64longint/long
uint32Uses variable-length encoding.uint32intint/long
uint64Uses variable-length encoding.uint64longint/long
sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.int32intint
sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.int64longint/long
fixed32Always four bytes. More efficient than uint32 if values are often greater than 2^28.uint32intint
fixed64Always eight bytes. More efficient than uint64 if values are often greater than 2^56.uint64longint/long
sfixed32Always four bytes.int32intint
sfixed64Always eight bytes.int64longint/long
boolboolbooleanboolean
stringA string must always contain UTF-8 encoded or 7-bit ASCII text.stringStringstr/unicode
bytesMay contain any arbitrary sequence of bytes.stringByteStringstr
- + diff --git a/examples/doc/example.json b/examples/doc/example.json index 45d41721..6c3a0dd4 100644 --- a/examples/doc/example.json +++ b/examples/doc/example.json @@ -1,545 +1,539 @@ [ - { - "file_description": "Booking related messages.\n\nThis file is really just an example. The data model is completely\nfictional.\n\nAuthor: Elvis Stansvik", - "file_enums": [ - ], - "file_extensions": [ - ], - "file_has_extensions": false, - "file_has_services": true, - "file_messages": [ - { - "message_description": "Represents the booking of a vehicle.\n\nVehicles are some cool shit. But drive carefully!", - "message_extensions": [ - ], - "message_fields": [ - { - "field_default_value": "", - "field_description": "ID of booked vehicle.", - "field_full_type": "int32", - "field_label": "required", - "field_long_type": "int32", - "field_name": "vehicle_id", - "field_type": "int32" - }, - { - "field_default_value": "", - "field_description": "Customer that booked the vehicle.", - "field_full_type": "int32", - "field_label": "required", - "field_long_type": "int32", - "field_name": "customer_id", - "field_type": "int32" - }, - { - "field_default_value": "", - "field_description": "Status of the booking.", - "field_full_type": "com.example.BookingStatus", - "field_label": "required", - "field_long_type": "BookingStatus", - "field_name": "status", - "field_type": "BookingStatus" - }, - { - "field_default_value": "", - "field_description": "Has booking confirmation been sent?", - "field_full_type": "bool", - "field_label": "required", - "field_long_type": "bool", - "field_name": "confirmation_sent", - "field_type": "bool" - }, - { - "field_default_value": "", - "field_description": "Has payment been received?", - "field_full_type": "bool", - "field_label": "required", - "field_long_type": "bool", - "field_name": "payment_received", - "field_type": "bool" - } - ], - "message_full_name": "com.example.Booking", - "message_has_extensions": false, - "message_long_name": "Booking", - "message_name": "Booking" - }, - { - "message_description": "Represents the status of a vehicle booking.", - "message_extensions": [ - ], - "message_fields": [ - { - "field_default_value": "", - "field_description": "Unique booking status ID.", - "field_full_type": "int32", - "field_label": "required", - "field_long_type": "int32", - "field_name": "id", - "field_type": "int32" - }, - { - "field_default_value": "", - "field_description": "Booking status description. E.g. \"Active\".", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "description", - "field_type": "string" - } - ], - "message_full_name": "com.example.BookingStatus", - "message_has_extensions": false, - "message_long_name": "BookingStatus", - "message_name": "BookingStatus" - } - ], - "file_name": "Booking.proto", - "file_package": "com.example", - "file_services": [ - { - "service_description": "Service for handling vehicle bookings.", - "service_full_name": "com.example.BookingService", - "service_methods": [ - { - "method_description": "Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.", - "method_name": "BookVehicle", - "method_request_full_type": "com.example.Booking", - "method_request_long_type": "Booking", - "method_request_type": "Booking", - "method_response_full_type": "com.example.BookingStatus", - "method_response_long_type": "BookingStatus", - "method_response_type": "BookingStatus" - } - ], - "service_name": "BookingService" - } + { + "file_name": "Booking.proto", + "file_description": "Booking related messages.\n\nThis file is really just an example. The data model is completely\nfictional.", + "file_package": "com.example", + "file_has_enums": false, + "file_has_extensions": false, + "file_has_messages": true, + "file_has_services": true, + "file_enums": null, + "file_extensions": null, + "file_messages": [ + { + "message_name": "Booking", + "message_long_name": "Booking", + "message_full_name": "com.example.Booking", + "message_description": "Represents the booking of a vehicle.\n\nVehicles are some cool shit. But drive carefully!", + "message_extensions": null, + "message_fields": [ + { + "field_name": "vehicle_id", + "field_description": "ID of booked vehicle.", + "field_label": "", + "field_type": "int32", + "field_long_type": "int32", + "field_full_type": "int32", + "field_default_value": "" + }, + { + "field_name": "customer_id", + "field_description": "Customer that booked the vehicle.", + "field_label": "", + "field_type": "int32", + "field_long_type": "int32", + "field_full_type": "int32", + "field_default_value": "" + }, + { + "field_name": "status", + "field_description": "Status of the booking.", + "field_label": "", + "field_type": "BookingStatus", + "field_long_type": "BookingStatus", + "field_full_type": "com.example.BookingStatus", + "field_default_value": "" + }, + { + "field_name": "confirmation_sent", + "field_description": "Has booking confirmation been sent?", + "field_label": "", + "field_type": "bool", + "field_long_type": "bool", + "field_full_type": "bool", + "field_default_value": "" + }, + { + "field_name": "payment_received", + "field_description": "Has payment been received?", + "field_label": "", + "field_type": "bool", + "field_long_type": "bool", + "field_full_type": "bool", + "field_default_value": "" + } ] - }, - { - "file_description": "This file has messages for describing a customer.\n\nAuthor: Elvis Stansvik", - "file_enums": [ - ], - "file_extensions": [ - ], - "file_has_extensions": false, - "file_has_services": false, - "file_messages": [ - { - "message_description": "Represents a mail address.", - "message_extensions": [ - ], - "message_fields": [ - { - "field_default_value": "", - "field_description": "First address line.", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "address_line_1", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Second address line.", - "field_full_type": "string", - "field_label": "optional", - "field_long_type": "string", - "field_name": "address_line_2", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Second address line.", - "field_full_type": "string", - "field_label": "optional", - "field_long_type": "string", - "field_name": "address_line_3", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Address town.", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "town", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Address county, if applicable.", - "field_full_type": "string", - "field_label": "optional", - "field_long_type": "string", - "field_name": "county", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Address country.", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "country", - "field_type": "string" - } - ], - "message_full_name": "com.example.Address", - "message_has_extensions": false, - "message_long_name": "Address", - "message_name": "Address" - }, - { - "message_description": "Represents a customer.", - "message_extensions": [ - ], - "message_fields": [ - { - "field_default_value": "", - "field_description": "Unique customer ID.", - "field_full_type": "int32", - "field_label": "required", - "field_long_type": "int32", - "field_name": "id", - "field_type": "int32" - }, - { - "field_default_value": "", - "field_description": "Customer first name.", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "first_name", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Customer last name.", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "last_name", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Customer details.", - "field_full_type": "string", - "field_label": "optional", - "field_long_type": "string", - "field_name": "details", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Customer e-mail address.", - "field_full_type": "string", - "field_label": "optional", - "field_long_type": "string", - "field_name": "email_address", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Customer phone numbers, primary first.", - "field_full_type": "string", - "field_label": "repeated", - "field_long_type": "string", - "field_name": "phone_number", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Customer mail addresses, primary first.", - "field_full_type": "com.example.Address", - "field_label": "repeated", - "field_long_type": "Address", - "field_name": "mail_addresses", - "field_type": "Address" - } - ], - "message_full_name": "com.example.Customer", - "message_has_extensions": false, - "message_long_name": "Customer", - "message_name": "Customer" - } - ], - "file_name": "Customer.proto", - "file_package": "com.example", - "file_services": [ + }, + { + "message_name": "BookingStatus", + "message_long_name": "BookingStatus", + "message_full_name": "com.example.BookingStatus", + "message_description": "Represents the status of a vehicle booking.", + "message_extensions": null, + "message_fields": [ + { + "field_name": "id", + "field_description": "Unique booking status ID.", + "field_label": "", + "field_type": "int32", + "field_long_type": "int32", + "field_full_type": "int32", + "field_default_value": "" + }, + { + "field_name": "description", + "field_description": "Booking status description. E.g. \"Active\".", + "field_label": "", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + } ] - }, - { - "file_description": "Messages describing manufacturers / vehicles.", - "file_enums": [ - { - "enum_description": "Manufacturer category. A manufacturer may be either inhouse or external.", - "enum_full_name": "com.example.Manufacturer.Category", - "enum_long_name": "Manufacturer.Category", - "enum_name": "Category", - "enum_values": [ - { - "value_description": "The manufacturer is inhouse.", - "value_name": "CATEGORY_INHOUSE", - "value_number": 0 - }, - { - "value_description": "The manufacturer is external.", - "value_name": "CATEGORY_EXTERNAL", - "value_number": 1 - } - ] - } - ], - "file_extensions": [ - { - "extension_containing_full_type": "com.example.Manufacturer", - "extension_containing_long_type": "Manufacturer", - "extension_containing_type": "Manufacturer", - "extension_default_value": "\"China\"", - "extension_description": "Manufacturer country.", - "extension_full_name": "com.example.country", - "extension_full_type": "string", - "extension_label": "optional", - "extension_long_name": ".country", - "extension_long_type": "string", - "extension_name": "country", - "extension_number": "100", - "extension_type": "string" - } - ], - "file_has_extensions": true, - "file_has_services": false, - "file_messages": [ - { - "message_description": "Represents a manufacturer of cars.", - "message_extensions": [ - ], - "message_fields": [ - { - "field_default_value": "", - "field_description": "The unique manufacturer ID.", - "field_full_type": "int32", - "field_label": "required", - "field_long_type": "int32", - "field_name": "id", - "field_type": "int32" - }, - { - "field_default_value": "", - "field_description": "A manufacturer code, e.g. \"DKL4P\".", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "code", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Manufacturer details (minimum orders et.c.).", - "field_full_type": "string", - "field_label": "optional", - "field_long_type": "string", - "field_name": "details", - "field_type": "string" - }, - { - "field_default_value": "CATEGORY_EXTERNAL", - "field_description": "Manufacturer category.", - "field_full_type": "com.example.Manufacturer.Category", - "field_label": "optional", - "field_long_type": "Manufacturer.Category", - "field_name": "category", - "field_type": "Category" - } - ], - "message_full_name": "com.example.Manufacturer", - "message_has_extensions": false, - "message_long_name": "Manufacturer", - "message_name": "Manufacturer" - }, - { - "message_description": "Represents a vehicle model.", - "message_extensions": [ - ], - "message_fields": [ - { - "field_default_value": "", - "field_description": "The unique model ID.", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "id", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "The car model code, e.g. \"PZ003\".", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "model_code", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "The car model name, e.g. \"Z3\".", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "model_name", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Dollars per day.", - "field_full_type": "sint32", - "field_label": "required", - "field_long_type": "sint32", - "field_name": "daily_hire_rate_dollars", - "field_type": "sint32" - }, - { - "field_default_value": "", - "field_description": "Cents per day.", - "field_full_type": "sint32", - "field_label": "required", - "field_long_type": "sint32", - "field_name": "daily_hire_rate_cents", - "field_type": "sint32" - } - ], - "message_full_name": "com.example.Model", - "message_has_extensions": false, - "message_long_name": "Model", - "message_name": "Model" - }, - { - "message_description": "Represents a vehicle that can be hired.", - "message_extensions": [ - { - "extension_containing_full_type": "com.example.Model", - "extension_containing_long_type": "Model", - "extension_containing_type": "Model", - "extension_default_value": "", - "extension_description": "Vehicle model series.", - "extension_full_name": "com.example.Vehicle.series", - "extension_full_type": "string", - "extension_label": "optional", - "extension_long_name": "Vehicle.series", - "extension_long_type": "string", - "extension_name": "series", - "extension_number": "100", - "extension_scope_full_type": "com.example.Vehicle", - "extension_scope_long_type": "Vehicle", - "extension_scope_type": "Vehicle", - "extension_type": "string" - } - ], - "message_fields": [ - { - "field_default_value": "", - "field_description": "Unique vehicle ID.", - "field_full_type": "int32", - "field_label": "required", - "field_long_type": "int32", - "field_name": "id", - "field_type": "int32" - }, - { - "field_default_value": "", - "field_description": "Vehicle model.", - "field_full_type": "com.example.Model", - "field_label": "required", - "field_long_type": "Model", - "field_name": "model", - "field_type": "Model" - }, - { - "field_default_value": "", - "field_description": "Vehicle registration number.", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "reg_number", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Current vehicle mileage, if known.", - "field_full_type": "sint32", - "field_label": "optional", - "field_long_type": "sint32", - "field_name": "mileage", - "field_type": "sint32" - }, - { - "field_default_value": "", - "field_description": "Vehicle category.", - "field_full_type": "com.example.Vehicle.Category", - "field_label": "optional", - "field_long_type": "Vehicle.Category", - "field_name": "category", - "field_type": "Category" - }, - { - "field_default_value": "50", - "field_description": "Dollars per day.", - "field_full_type": "sint32", - "field_label": "optional", - "field_long_type": "sint32", - "field_name": "daily_hire_rate_dollars", - "field_type": "sint32" - }, - { - "field_default_value": "", - "field_description": "Cents per day.", - "field_full_type": "sint32", - "field_label": "optional", - "field_long_type": "sint32", - "field_name": "daily_hire_rate_cents", - "field_type": "sint32" - } - ], - "message_full_name": "com.example.Vehicle", - "message_has_extensions": true, - "message_long_name": "Vehicle", - "message_name": "Vehicle" - }, - { - "message_description": "Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", - "message_extensions": [ - ], - "message_fields": [ - { - "field_default_value": "", - "field_description": "Category code. E.g. \"S\".", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "code", - "field_type": "string" - }, - { - "field_default_value": "", - "field_description": "Category name. E.g. \"Sedan\".", - "field_full_type": "string", - "field_label": "required", - "field_long_type": "string", - "field_name": "description", - "field_type": "string" - } - ], - "message_full_name": "com.example.Vehicle.Category", - "message_has_extensions": false, - "message_long_name": "Vehicle.Category", - "message_name": "Category" - } + }, + { + "message_name": "EmptyBookingMessage", + "message_long_name": "EmptyBookingMessage", + "message_full_name": "com.example.EmptyBookingMessage", + "message_description": "An empty message for testing", + "message_extensions": null, + "message_fields": null + } + ], + "file_services": [ + { + "service_name": "BookingService", + "service_long_name": "BookingService", + "service_full_name": "com.example.BookingService", + "service_description": "Service for handling vehicle bookings.", + "service_methods": [ + { + "method_name": "BookVehicle", + "method_description": "Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.", + "method_request_type": "Booking", + "method_request_long_type": "Booking", + "method_request_full_type": "com.example.Booking", + "method_response_type": "BookingStatus", + "method_response_long_type": "BookingStatus", + "method_response_full_type": "com.example.BookingStatus" + } + ] + } + ] + }, + { + "file_name": "Customer.proto", + "file_description": "This file has messages for describing a customer.", + "file_package": "com.example", + "file_has_enums": false, + "file_has_extensions": false, + "file_has_messages": true, + "file_has_services": false, + "file_enums": null, + "file_extensions": null, + "file_messages": [ + { + "message_name": "Address", + "message_long_name": "Address", + "message_full_name": "com.example.Address", + "message_description": "Represents a mail address.", + "message_extensions": null, + "message_fields": [ + { + "field_name": "address_line_1", + "field_description": "First address line.", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "address_line_2", + "field_description": "Second address line.", + "field_label": "optional", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "address_line_3", + "field_description": "Second address line.", + "field_label": "optional", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "town", + "field_description": "Address town.", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "county", + "field_description": "Address county, if applicable.", + "field_label": "optional", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "country", + "field_description": "Address country.", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + } + ] + }, + { + "message_name": "Customer", + "message_long_name": "Customer", + "message_full_name": "com.example.Customer", + "message_description": "Represents a customer.", + "message_extensions": null, + "message_fields": [ + { + "field_name": "id", + "field_description": "Unique customer ID.", + "field_label": "required", + "field_type": "int32", + "field_long_type": "int32", + "field_full_type": "int32", + "field_default_value": "" + }, + { + "field_name": "first_name", + "field_description": "Customer first name.", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "last_name", + "field_description": "Customer last name.", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "details", + "field_description": "Customer details.", + "field_label": "optional", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "email_address", + "field_description": "Customer e-mail address.", + "field_label": "optional", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "phone_number", + "field_description": "Customer phone numbers, primary first.", + "field_label": "repeated", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "mail_addresses", + "field_description": "Customer mail addresses, primary first.", + "field_label": "repeated", + "field_type": "Address", + "field_long_type": "Address", + "field_full_type": "com.example.Address", + "field_default_value": "" + } + ] + } + ], + "file_services": null + }, + { + "file_name": "Vehicle.proto", + "file_description": "Messages describing manufacturers / vehicles.", + "file_package": "com.example", + "file_has_enums": true, + "file_has_extensions": true, + "file_has_messages": true, + "file_has_services": false, + "file_enums": [ + { + "enum_name": "Category", + "enum_long_name": "Manufacturer.Category", + "enum_full_name": "com.example.Manufacturer.Category", + "enum_description": "Manufacturer category. A manufacturer may be either inhouse or external.", + "enum_values": [ + { + "value_name": "CATEGORY_INHOUSE", + "value_number": "0", + "value_description": "The manufacturer is inhouse." + }, + { + "value_name": "CATEGORY_EXTERNAL", + "value_number": "1", + "value_description": "The manufacturer is external." + } + ] + } + ], + "file_extensions": [ + { + "extension_name": "country", + "extension_long_name": "Manufacturer.country", + "extension_full_name": "com.example.Manufacturer.country", + "extension_description": "Manufacturer country.", + "extension_label": "optional", + "extension_type": "string", + "extension_long_type": "string", + "extension_full_type": "string", + "extension_number": 100, + "extension_default_value": "China", + "extension_containing_type": "Manufacturer", + "extension_containing_long_type": "Manufacturer", + "extension_containing_full_type": "com.example.Manufacturer" + } + ], + "file_messages": [ + { + "message_name": "Manufacturer", + "message_long_name": "Manufacturer", + "message_full_name": "com.example.Manufacturer", + "message_description": "Represents a manufacturer of cars.", + "message_extensions": null, + "message_fields": [ + { + "field_name": "id", + "field_description": "The unique manufacturer ID.", + "field_label": "required", + "field_type": "int32", + "field_long_type": "int32", + "field_full_type": "int32", + "field_default_value": "" + }, + { + "field_name": "code", + "field_description": "A manufacturer code, e.g. \"DKL4P\".", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "details", + "field_description": "Manufacturer details (minimum orders et.c.).", + "field_label": "optional", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "category", + "field_description": "Manufacturer category.", + "field_label": "optional", + "field_type": "Category", + "field_long_type": "Manufacturer.Category", + "field_full_type": "com.example.Manufacturer.Category", + "field_default_value": "" + } + ] + }, + { + "message_name": "Model", + "message_long_name": "Model", + "message_full_name": "com.example.Model", + "message_description": "Represents a vehicle model.", + "message_extensions": null, + "message_fields": [ + { + "field_name": "id", + "field_description": "The unique model ID.", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "model_code", + "field_description": "The car model code, e.g. \"PZ003\".", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "model_name", + "field_description": "The car model name, e.g. \"Z3\".", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "daily_hire_rate_dollars", + "field_description": "Dollars per day.", + "field_label": "required", + "field_type": "sint32", + "field_long_type": "sint32", + "field_full_type": "sint32", + "field_default_value": "" + }, + { + "field_name": "daily_hire_rate_cents", + "field_description": "Cents per day.", + "field_label": "required", + "field_type": "sint32", + "field_long_type": "sint32", + "field_full_type": "sint32", + "field_default_value": "" + } + ] + }, + { + "message_name": "Vehicle", + "message_long_name": "Vehicle", + "message_full_name": "com.example.Vehicle", + "message_description": "Represents a vehicle that can be hired.", + "message_extensions": [ + { + "extension_name": "series", + "extension_long_name": "Model.series", + "extension_full_name": "com.example.Model.series", + "extension_description": "Vehicle model series.", + "extension_label": "optional", + "extension_type": "string", + "extension_long_type": "string", + "extension_full_type": "string", + "extension_number": 100, + "extension_default_value": "", + "extension_containing_type": "Model", + "extension_containing_long_type": "Model", + "extension_containing_full_type": "com.example.Model", + "extension_scope_type": "Vehicle", + "extension_scope_long_type": "Vehicle", + "extension_scope_full_type": "com.example.Vehicle" + } ], - "file_name": "Vehicle.proto", - "file_package": "com.example", - "file_services": [ + "message_fields": [ + { + "field_name": "id", + "field_description": "Unique vehicle ID.", + "field_label": "required", + "field_type": "int32", + "field_long_type": "int32", + "field_full_type": "int32", + "field_default_value": "" + }, + { + "field_name": "model", + "field_description": "Vehicle model.", + "field_label": "required", + "field_type": "Model", + "field_long_type": "Model", + "field_full_type": "com.example.Model", + "field_default_value": "" + }, + { + "field_name": "reg_number", + "field_description": "Vehicle registration number.", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "mileage", + "field_description": "Current vehicle mileage, if known.", + "field_label": "optional", + "field_type": "sint32", + "field_long_type": "sint32", + "field_full_type": "sint32", + "field_default_value": "" + }, + { + "field_name": "category", + "field_description": "Vehicle category.", + "field_label": "optional", + "field_type": "Category", + "field_long_type": "Vehicle.Category", + "field_full_type": "com.example.Vehicle.Category", + "field_default_value": "" + }, + { + "field_name": "daily_hire_rate_dollars", + "field_description": "Dollars per day.", + "field_label": "optional", + "field_type": "sint32", + "field_long_type": "sint32", + "field_full_type": "sint32", + "field_default_value": "" + }, + { + "field_name": "daily_hire_rate_cents", + "field_description": "Cents per day.", + "field_label": "optional", + "field_type": "sint32", + "field_long_type": "sint32", + "field_full_type": "sint32", + "field_default_value": "" + } + ] + }, + { + "message_name": "Category", + "message_long_name": "Vehicle.Category", + "message_full_name": "com.example.Vehicle.Category", + "message_description": "Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", + "message_extensions": null, + "message_fields": [ + { + "field_name": "code", + "field_description": "Category code. E.g. \"S\".", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + }, + { + "field_name": "description", + "field_description": "Category name. E.g. \"Sedan\".", + "field_label": "required", + "field_type": "string", + "field_long_type": "string", + "field_full_type": "string", + "field_default_value": "" + } ] - } -] + } + ], + "file_services": null + } +] \ No newline at end of file diff --git a/examples/doc/example.md b/examples/doc/example.md index 73710fd3..4ceaeb01 100644 --- a/examples/doc/example.md +++ b/examples/doc/example.md @@ -2,27 +2,58 @@ ## Table of Contents + + * [Booking.proto](#Booking.proto) - * [Booking](#com.example.Booking) - * [BookingStatus](#com.example.BookingStatus) - * [BookingService](#com.example.BookingService) + + * [Booking](#com.example.Booking) + + * [BookingStatus](#com.example.BookingStatus) + + * [EmptyBookingMessage](#com.example.EmptyBookingMessage) + + + + + * [BookingService](#com.example.BookingService) + + + * [Customer.proto](#Customer.proto) - * [Address](#com.example.Address) - * [Customer](#com.example.Customer) -* [SpecialCases.proto](#SpecialCases.proto) - * [Empty](#com.example.Empty) + + * [Address](#com.example.Address) + + * [Customer](#com.example.Customer) + + + + + + * [Vehicle.proto](#Vehicle.proto) - * [Manufacturer](#com.example.Manufacturer) - * [Model](#com.example.Model) - * [Vehicle](#com.example.Vehicle) - * [Vehicle.Category](#com.example.Vehicle.Category) - * [Manufacturer.Category](#com.example.Manufacturer.Category) - * [File-level Extensions](#Vehicle.proto-extensions) + + * [Manufacturer](#com.example.Manufacturer) + + * [Model](#com.example.Model) + + * [Vehicle](#com.example.Vehicle) + + * [Vehicle.Category](#com.example.Vehicle.Category) + + + * [Manufacturer.Category](#com.example.Manufacturer.Category) + + + * [File-level Extensions](#Vehicle.proto-extensions) + + + * [Scalar Value Types](#scalar-value-types) + +

Top

- ## Booking.proto Booking related messages. @@ -30,58 +61,86 @@ Booking related messages. This file is really just an example. The data model is completely fictional. -Author: Elvis Stansvik ### Booking + Represents the booking of a vehicle. Vehicles are some cool shit. But drive carefully! + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| vehicle_id | [int32](#int32) | required | ID of booked vehicle. | -| customer_id | [int32](#int32) | required | Customer that booked the vehicle. | -| status | [BookingStatus](#com.example.BookingStatus) | required | Status of the booking. | -| confirmation_sent | [bool](#bool) | required | Has booking confirmation been sent? | -| payment_received | [bool](#bool) | required | Has payment been received? | +| vehicle_id | [int32](#int32) | | ID of booked vehicle. | +| customer_id | [int32](#int32) | | Customer that booked the vehicle. | +| status | [BookingStatus](#com.example.BookingStatus) | | Status of the booking. | +| confirmation_sent | [bool](#bool) | | Has booking confirmation been sent? | +| payment_received | [bool](#bool) | | Has payment been received? | + + + + ### BookingStatus + Represents the status of a vehicle booking. + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| id | [int32](#int32) | required | Unique booking status ID. | -| description | [string](#string) | required | Booking status description. E.g. "Active". | +| id | [int32](#int32) | | Unique booking status ID. | +| description | [string](#string) | | Booking status description. E.g. "Active". | + + + + +### EmptyBookingMessage + +An empty message for testing + + + + + + + + + + ### BookingService + Service for handling vehicle bookings. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| -| BookVehicle | [Booking](#com.example.Booking) | [BookingStatus](#com.example.BookingStatus) | Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned. | +| BookVehicle | [Booking](#com.example.Booking) | [BookingStatus](#com.example.Booking) | Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned. | + + +

Top

- ## Customer.proto This file has messages for describing a customer. -Author: Elvis Stansvik ### Address + Represents a mail address. + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | address_line_1 | [string](#string) | required | First address line. | @@ -92,10 +151,16 @@ Represents a mail address. | country | [string](#string) | required | Address country. | + + + + ### Customer + Represents a customer. + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [int32](#int32) | required | Unique customer ID. | @@ -110,62 +175,66 @@ Represents a customer. + - -

Top

- -## SpecialCases.proto - -Special cases for testing - -This is of course entirely fictional/useless model. - - -### Empty -Represents a message with no fields - - + + +

Top

- ## Vehicle.proto Messages describing manufacturers / vehicles. + ### Manufacturer + Represents a manufacturer of cars. + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [int32](#int32) | required | The unique manufacturer ID. | -| code | [string](#string) | required | A manufacturer code, e.g. "DKL4P". | +| code | [string](#string) | required | A manufacturer code, e.g. "DKL4P". | | details | [string](#string) | optional | Manufacturer details (minimum orders et.c.). | -| category | [Manufacturer.Category](#com.example.Manufacturer.Category) | optional | Manufacturer category. Default: CATEGORY_EXTERNAL | +| category | [Manufacturer.Category](#com.example.Manufacturer.Category) | optional | Manufacturer category. | + + + + ### Model + Represents a vehicle model. + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [string](#string) | required | The unique model ID. | -| model_code | [string](#string) | required | The car model code, e.g. "PZ003". | -| model_name | [string](#string) | required | The car model name, e.g. "Z3". | +| model_code | [string](#string) | required | The car model code, e.g. "PZ003". | +| model_name | [string](#string) | required | The car model name, e.g. "Z3". | | daily_hire_rate_dollars | [sint32](#sint32) | required | Dollars per day. | | daily_hire_rate_cents | [sint32](#sint32) | required | Cents per day. | + + + + ### Vehicle + Represents a vehicle that can be hired. + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [int32](#int32) | required | Unique vehicle ID. | @@ -173,26 +242,40 @@ Represents a vehicle that can be hired. | reg_number | [string](#string) | required | Vehicle registration number. | | mileage | [sint32](#sint32) | optional | Current vehicle mileage, if known. | | category | [Vehicle.Category](#com.example.Vehicle.Category) | optional | Vehicle category. | -| daily_hire_rate_dollars | [sint32](#sint32) | optional | Dollars per day. Default: 50 | +| daily_hire_rate_dollars | [sint32](#sint32) | optional | Dollars per day. | | daily_hire_rate_cents | [sint32](#sint32) | optional | Cents per day. | + + + | Extension | Type | Base | Number | Description | | --------- | ---- | ---- | ------ | ----------- | | series | string | Model | 100 | Vehicle model series. | + + + ### Vehicle.Category -Represents a vehicle category. E.g. "Sedan" or "Truck". + +Represents a vehicle category. E.g. "Sedan" or "Truck". + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| code | [string](#string) | required | Category code. E.g. "S". | -| description | [string](#string) | required | Category name. E.g. "Sedan". | +| code | [string](#string) | required | Category code. E.g. "S". | +| description | [string](#string) | required | Category name. E.g. "Sedan". | + + + + + ### Manufacturer.Category + Manufacturer category. A manufacturer may be either inhouse or external. | Name | Number | Description | @@ -201,31 +284,40 @@ Manufacturer category. A manufacturer may be either inhouse or external. | CATEGORY_EXTERNAL | 1 | The manufacturer is external. | + + + ### File-level Extensions + | Extension | Type | Base | Number | Description | | --------- | ---- | ---- | ------ | ----------- | -| country | string | Manufacturer | 100 | Manufacturer country. Default: "China" | +| country | string | Manufacturer | 100 | Manufacturer country. Default: `China` | + + + + + - ## Scalar Value Types | .proto Type | Notes | C++ Type | Java Type | Python Type | | ----------- | ----- | -------- | --------- | ----------- | -| double | | double | double | float | -| float | | float | float | float | -| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | -| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | -| uint32 | Uses variable-length encoding. | uint32 | int | int/long | -| uint64 | Uses variable-length encoding. | uint64 | long | int/long | -| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | -| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | -| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | -| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | -| sfixed32 | Always four bytes. | int32 | int | int | -| sfixed64 | Always eight bytes. | int64 | long | int/long | -| bool | | bool | boolean | boolean | -| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | -| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | +| double | | double | double | float | +| float | | float | float | float | +| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | +| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | +| uint32 | Uses variable-length encoding. | uint32 | int | int/long | +| uint64 | Uses variable-length encoding. | uint64 | long | int/long | +| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | +| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | +| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | +| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | +| sfixed32 | Always four bytes. | int32 | int | int | +| sfixed64 | Always eight bytes. | int64 | long | int/long | +| bool | | bool | boolean | boolean | +| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | +| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | + diff --git a/examples/doc/example.pdf b/examples/doc/example.pdf deleted file mode 100644 index 69bc3f79dfbd64d69da11ec63327f7db7ce58e05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47266 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 qglR(tQh%LVX`?mZyVH*(dgmp8#*k^> | |ID of booked vehicle. + +|customer_id | <> | |Customer that booked the vehicle. + +|status | <> | |Status of the booking. + +|confirmation_sent | <> | |Has booking confirmation been sent? + +|payment_received | <> | |Has payment been received? + +|=========================================== + + + +=== BookingStatus +Represents the status of a vehicle booking. + + +|=========================================== +|*Field* |*Type* |*Label* |*Description* + +|id | <> | |Unique booking status ID. + +|description | <> | |Booking status description. E.g. "Active". + +|=========================================== + + + +=== EmptyBookingMessage +An empty message for testing + + + + + + + +== Customer.proto + + + +=== Address +Represents a mail address. + + +|=========================================== +|*Field* |*Type* |*Label* |*Description* + +|address_line_1 | <> |required |First address line. + +|address_line_2 | <> |optional |Second address line. + +|address_line_3 | <> |optional |Second address line. + +|town | <> |required |Address town. + +|county | <> |optional |Address county, if applicable. + +|country | <> |required |Address country. + +|=========================================== + + + +=== Customer +Represents a customer. + + +|=========================================== +|*Field* |*Type* |*Label* |*Description* + +|id | <> |required |Unique customer ID. + +|first_name | <> |required |Customer first name. + +|last_name | <> |required |Customer last name. + +|details | <> |optional |Customer details. + +|email_address | <> |optional |Customer e-mail address. + +|phone_number | <> |repeated |Customer phone numbers, primary first. + +|mail_addresses | <> |repeated |Customer mail addresses, primary first. + +|=========================================== + + + + + + +== Vehicle.proto + + + +=== Manufacturer +Represents a manufacturer of cars. + + +|=========================================== +|*Field* |*Type* |*Label* |*Description* + +|id | <> |required |The unique manufacturer ID. + +|code | <> |required |A manufacturer code, e.g. "DKL4P". + +|details | <> |optional |Manufacturer details (minimum orders et.c.). + +|category | <> |optional |Manufacturer category. + +|=========================================== + + + +=== Model +Represents a vehicle model. + + +|=========================================== +|*Field* |*Type* |*Label* |*Description* + +|id | <> |required |The unique model ID. + +|model_code | <> |required |The car model code, e.g. "PZ003". + +|model_name | <> |required |The car model name, e.g. "Z3". + +|daily_hire_rate_dollars | <> |required |Dollars per day. + +|daily_hire_rate_cents | <> |required |Cents per day. + +|=========================================== + + + +=== Vehicle +Represents a vehicle that can be hired. + + +|=========================================== +|*Field* |*Type* |*Label* |*Description* + +|id | <> |required |Unique vehicle ID. + +|model | <> |required |Vehicle model. + +|reg_number | <> |required |Vehicle registration number. + +|mileage | <> |optional |Current vehicle mileage, if known. + +|category | <> |optional |Vehicle category. + +|daily_hire_rate_dollars | <> |optional |Dollars per day. + +|daily_hire_rate_cents | <> |optional |Cents per day. + +|=========================================== + + + +=== Vehicle.Category +Represents a vehicle category. E.g. "Sedan" or "Truck". + + +|=========================================== +|*Field* |*Type* |*Label* |*Description* + +|code | <> |required |Category code. E.g. "S". + +|description | <> |required |Category name. E.g. "Sedan". + +|=========================================== + + + + + +[[Manufacturer.Category]] +=== Manufacturer.Category +Manufacturer category. A manufacturer may be either inhouse or external. + +|===================================== +|*Name* |*Number* |*Description* + +|CATEGORY_INHOUSE |0 |The manufacturer is inhouse. + +|CATEGORY_EXTERNAL |1 |The manufacturer is external. + +|===================================== + + + +== Scalar Value Types + +|============================================================== +|*.proto Type* |*Notes* |*C++ Type* |*Java Type* |*Python Type* + +|[[double]] (((double))) double | |double |double |float + +|[[float]] (((float))) float | |float |float |float + +|[[int32]] (((int32))) int32 |Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. |int32 |int |int + +|[[int64]] (((int64))) int64 |Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. |int64 |long |int/long + +|[[uint32]] (((uint32))) uint32 |Uses variable-length encoding. |uint32 |int |int/long + +|[[uint64]] (((uint64))) uint64 |Uses variable-length encoding. |uint64 |long |int/long + +|[[sint32]] (((sint32))) sint32 |Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. |int32 |int |int + +|[[sint64]] (((sint64))) sint64 |Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. |int64 |long |int/long + +|[[fixed32]] (((fixed32))) fixed32 |Always four bytes. More efficient than uint32 if values are often greater than 2^28. |uint32 |int |int + +|[[fixed64]] (((fixed64))) fixed64 |Always eight bytes. More efficient than uint64 if values are often greater than 2^56. |uint64 |long |int/long + +|[[sfixed32]] (((sfixed32))) sfixed32 |Always four bytes. |int32 |int |int + +|[[sfixed64]] (((sfixed64))) sfixed64 |Always eight bytes. |int64 |long |int/long + +|[[bool]] (((bool))) bool | |bool |boolean |boolean + +|[[string]] (((string))) string |A string must always contain UTF-8 encoded or 7-bit ASCII text. |string |String |str/unicode + +|[[bytes]] (((bytes))) bytes |May contain any arbitrary sequence of bytes. |string |ByteString |str + +|============================================================== diff --git a/examples/doc/example_types.md b/examples/doc/example_types.md deleted file mode 100644 index fd44c718..00000000 --- a/examples/doc/example_types.md +++ /dev/null @@ -1,26 +0,0 @@ -# All the Types - - -## Table of Contents -* [Scalar Value Types](#scalar-value-types) - - -## Scalar Value Types - -| .proto Type | Notes | C++ Type | C# Type | Go Type | Java Type | PHP Type | Python Type | Ruby Type | -| ----------- | ----- | -------- | ------- | --------| --------- | -------- | ----------- | --------- | -| double | | double | double | float64 | double | float | float | Float | -| float | | float | float | float32 | float | float | float | Float | -| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int32 | int | integer | int | Bignum or Fixnum (as required) | -| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int64 | long | integer/string | int/long | Bignum | -| uint32 | Uses variable-length encoding. | uint32 | uint | uint32 | int | integer | int/long | Bignum or Fixnum (as required) | -| uint64 | Uses variable-length encoding. | uint64 | ulong | uint64 | long | integer/string | int/long | Bignum or Fixnum (as required) | -| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int32 | int | integer | int | Bignum or Fixnum (as required) | -| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int64 | long | integer/string | int/long | Bignum | -| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | uint | uint32 | int | integer | int | Bignum or Fixnum (as required) | -| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | ulong | uint64 | long | integer/string | int/long | Bignum | -| sfixed32 | Always four bytes. | int32 | int | int32 | int | integer | int | Bignum or Fixnum (as required) | -| sfixed64 | Always eight bytes. | int64 | long | int64 | long | integer/string | int/long | Bignum | -| bool | | bool | bool | bool | boolean | boolean | boolean | TrueClass/FalseClass | -| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | string | string | String | string | str/unicode | String (UTF-8) | -| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | []byte | ByteString | string | str | String (ASCII-8BIT) | diff --git a/examples/proto/Booking.proto b/examples/proto/Booking.proto index e8c0d867..66a1950d 100644 --- a/examples/proto/Booking.proto +++ b/examples/proto/Booking.proto @@ -3,17 +3,17 @@ * * This file is really just an example. The data model is completely * fictional. - * - * Author: Elvis Stansvik */ +syntax = "proto3"; + package com.example; /** * Represents the status of a vehicle booking. */ message BookingStatus { - required int32 id = 1; /// Unique booking status ID. - required string description = 2; /// Booking status description. E.g. "Active". + int32 id = 1; /// Unique booking status ID. + string description = 2; /// Booking status description. E.g. "Active". } /** @@ -22,21 +22,25 @@ message BookingStatus { * Vehicles are some cool shit. But drive carefully! */ message Booking { - required int32 vehicle_id = 1; /// ID of booked vehicle. - required int32 customer_id = 2; /// Customer that booked the vehicle. - required BookingStatus status = 3; /// Status of the booking. + int32 vehicle_id = 1; /// ID of booked vehicle. + int32 customer_id = 2; /// Customer that booked the vehicle. + BookingStatus status = 3; /// Status of the booking. - /** Has booking confirmation been sent? */ - required bool confirmation_sent = 4; + /** Has booking confirmation been sent? */ + bool confirmation_sent = 4; + + /** Has payment been received? */ + bool payment_received = 5; +} - /** Has payment been received? */ - required bool payment_received = 5; +// An empty message for testing +message EmptyBookingMessage { } /** * Service for handling vehicle bookings. */ service BookingService { - /// Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned. - rpc BookVehicle (Booking) returns (BookingStatus); + /// Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned. + rpc BookVehicle (Booking) returns (BookingStatus); } diff --git a/examples/proto/Customer.proto b/examples/proto/Customer.proto index 36ab3e79..2be3043c 100644 --- a/examples/proto/Customer.proto +++ b/examples/proto/Customer.proto @@ -1,34 +1,33 @@ /// This file has messages for describing a customer. -/// -/// Author: Elvis Stansvik +syntax = "proto2"; package com.example; -// Use /// or /** */ to document messages, fields and enums. +// Use // or /** */ to document messages, fields and enums. /** * Represents a mail address. */ message Address { - required string address_line_1 = 1; /** First address line. */ - optional string address_line_2 = 2; /** Second address line. */ - optional string address_line_3 = 3; /** Second address line. */ + required string address_line_1 = 1; /** First address line. */ + optional string address_line_2 = 2; /** Second address line. */ + optional string address_line_3 = 3; /** Second address line. */ - required string town = 4; /// Address town. - optional string county = 5; /// Address county, if applicable. - required string country = 6; /// Address country. + required string town = 4; /// Address town. + optional string county = 5; /// Address county, if applicable. + required string country = 6; /// Address country. } /** * Represents a customer. */ message Customer { - required int32 id = 1; /// Unique customer ID. - required string first_name = 2; /// Customer first name. - required string last_name = 3; /// Customer last name. - optional string details = 4; /// Customer details. + required int32 id = 1; /// Unique customer ID. + required string first_name = 2; /// Customer first name. + required string last_name = 3; /// Customer last name. + optional string details = 4; /// Customer details. - optional string email_address = 5; /// Customer e-mail address. - repeated string phone_number = 6; /// Customer phone numbers, primary first. - repeated Address mail_addresses = 7; /// Customer mail addresses, primary first. + optional string email_address = 5; /// Customer e-mail address. + repeated string phone_number = 6; /// Customer phone numbers, primary first. + repeated Address mail_addresses = 7; /// Customer mail addresses, primary first. } diff --git a/examples/proto/SpecialCases.proto b/examples/proto/SpecialCases.proto deleted file mode 100644 index 7ed5cf94..00000000 --- a/examples/proto/SpecialCases.proto +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Special cases for testing - * - * This is of course entirely fictional/useless model. - */ -package com.example; - -/** - * Represents a message with no fields - */ -message Empty {} diff --git a/examples/proto/Vehicle.proto b/examples/proto/Vehicle.proto index e67bd19d..db6daac0 100644 --- a/examples/proto/Vehicle.proto +++ b/examples/proto/Vehicle.proto @@ -1,4 +1,5 @@ /** Messages describing manufacturers / vehicles. */ +syntax = "proto2"; package com.example; @@ -6,82 +7,82 @@ package com.example; * Represents a manufacturer of cars. */ message Manufacturer { - /** - * Manufacturer category. A manufacturer may be either inhouse or external. - */ - enum Category { - CATEGORY_INHOUSE = 0; /** The manufacturer is inhouse. */ - CATEGORY_EXTERNAL = 1; /** The manufacturer is external. */ - } - - required int32 id = 1; /** The unique manufacturer ID. */ - required string code = 2; /** A manufacturer code, e.g. "DKL4P". */ - optional string details = 3; /** Manufacturer details (minimum orders et.c.). */ - - /** Manufacturer category. */ - optional Category category = 4 [default = CATEGORY_EXTERNAL]; - - extensions 100 to max; + /** + * Manufacturer category. A manufacturer may be either inhouse or external. + */ + enum Category { + CATEGORY_INHOUSE = 0; /** The manufacturer is inhouse. */ + CATEGORY_EXTERNAL = 1; /** The manufacturer is external. */ + } + + required int32 id = 1; /** The unique manufacturer ID. */ + required string code = 2; /** A manufacturer code, e.g. "DKL4P". */ + optional string details = 3; /** Manufacturer details (minimum orders et.c.). */ + + /** Manufacturer category. */ + optional Category category = 4 [default = CATEGORY_EXTERNAL]; + + extensions 100 to max; } // File-level extensions can also be documented: extend Manufacturer { - /** Manufacturer country. */ - optional string country = 100 [default = "China"]; + /** Manufacturer country. */ + optional string country = 100 [default = "China"]; } /** * Represents a vehicle model. */ message Model { - required string id = 1; /** The unique model ID. */ - required string model_code = 2; /** The car model code, e.g. "PZ003". */ - required string model_name = 3; /** The car model name, e.g. "Z3". */ + required string id = 1; /** The unique model ID. */ + required string model_code = 2; /** The car model code, e.g. "PZ003". */ + required string model_name = 3; /** The car model name, e.g. "Z3". */ - required sint32 daily_hire_rate_dollars = 4; /// Dollars per day. - required sint32 daily_hire_rate_cents = 5; /// Cents per day. + required sint32 daily_hire_rate_dollars = 4; /// Dollars per day. + required sint32 daily_hire_rate_cents = 5; /// Cents per day. - extensions 100 to max; + extensions 100 to max; } /** * Represents a vehicle that can be hired. */ message Vehicle { - /** - * Represents a vehicle category. E.g. "Sedan" or "Truck". - */ - message Category { - required string code = 1; /// Category code. E.g. "S". - required string description = 2; /// Category name. E.g. "Sedan". - } - - required int32 id = 1; /** Unique vehicle ID. */ - required Model model = 2; /** Vehicle model. */ - required string reg_number = 3; /** Vehicle registration number. */ - optional sint32 mileage = 4; /** Current vehicle mileage, if known. */ - optional Category category = 5; /** Vehicle category. */ - - // Doc comments for fields can come before or - // after the field definition. And just like - // comments for messages / enums, they can be - // multi-paragraph: - - /** - * Dollars per day. - */ - optional sint32 daily_hire_rate_dollars = 6 [default = 50]; - - /** - * Cents per day. - */ - optional sint32 daily_hire_rate_cents = 7; - - // Nested extensions can also be documented: - - extend Model { - /** Vehicle model series. */ - optional string series = 100; - } + /** + * Represents a vehicle category. E.g. "Sedan" or "Truck". + */ + message Category { + required string code = 1; /// Category code. E.g. "S". + required string description = 2; /// Category name. E.g. "Sedan". + } + + required int32 id = 1; /** Unique vehicle ID. */ + required Model model = 2; /** Vehicle model. */ + required string reg_number = 3; /** Vehicle registration number. */ + optional sint32 mileage = 4; /** Current vehicle mileage, if known. */ + optional Category category = 5; /** Vehicle category. */ + + // Doc comments for fields can come before or + // after the field definition. And just like + // comments for messages / enums, they can be + // multi-paragraph: + + /** + * Dollars per day. + */ + optional sint32 daily_hire_rate_dollars = 6 [default = 50]; + + /** + * Cents per day. + */ + optional sint32 daily_hire_rate_cents = 7; + + // Nested extensions can also be documented: + + extend Model { + /** Vehicle model series. */ + optional string series = 100; + } } diff --git a/examples/templates/asciidoc.mustache b/examples/templates/asciidoc.mustache deleted file mode 100644 index aec16805..00000000 --- a/examples/templates/asciidoc.mustache +++ /dev/null @@ -1,45 +0,0 @@ -= __Application_Name__ Protobuf Documentation -:toc: - -{{#files}} - -== {{file_name}} - -{{#file_messages}} - -=== {{message_long_name}} -{{message_description}} - -{{#message_has_fields}} -|=========================================== -|*Field* |*Type* |*Label* |*Description* -{{#message_fields}} -|{{field_name}} | <<{{field_long_type}},{{field_long_type}}>> |{{field_label}} |{{#nobr}}{{field_description}}{{/nobr}} -{{/message_fields}} -|=========================================== -{{/message_has_fields}} -{{/file_messages}} - -{{#file_enums}} - -[[{{enum_long_name}}]] -=== {{enum_long_name}} -{{enum_description}} - -|===================================== -|*Name* |*Number* |*Description* -{{#enum_values}} -|{{value_name}} |{{value_number}} |{{#nobr}}{{value_description}}{{/nobr}} -{{/enum_values}} -|===================================== -{{/file_enums}} -{{/files}} - -== Scalar Value Types - -|============================================================== -|*.proto Type* |*Notes* |*C++ Type* |*Java Type* |*Python Type* -{{#scalar_value_types}} -|[[{{scalar_value_proto_type}}]] ((({{scalar_value_proto_type}}))) {{scalar_value_proto_type}} |{{scalar_value_notes}} |{{scalar_value_cpp_type}} |{{scalar_value_java_type}} |{{scalar_value_python_type}} -{{/scalar_value_types}} -|============================================================== diff --git a/examples/templates/asciidoc.tmpl b/examples/templates/asciidoc.tmpl new file mode 100644 index 00000000..d05a27e2 --- /dev/null +++ b/examples/templates/asciidoc.tmpl @@ -0,0 +1,45 @@ += __Application_Name__ Protobuf Documentation +:toc: + +{{range .Files}} + +== {{.Name}} + +{{range .Messages}} + +=== {{.LongName}} +{{.Description}} + +{{if .HasFields}} +|=========================================== +|*Field* |*Type* |*Label* |*Description* +{{range .Fields}} +|{{.Name}} | <<{{.FullType}},{{.LongType}}>> |{{.Label}} |{{.Description}} +{{end}} +|=========================================== +{{end}} +{{end}} + +{{range .Enums}} + +[[{{.LongName}}]] +=== {{.LongName}} +{{.Description}} + +|===================================== +|*Name* |*Number* |*Description* +{{range .Values}} +|{{.Name}} |{{.Number}} |{{.Description}} +{{end}} +|===================================== +{{end}} +{{end}} + +== Scalar Value Types + +|============================================================== +|*.proto Type* |*Notes* |*C++ Type* |*Java Type* |*Python Type* +{{range .Scalars}} +|[[{{.ProtoType}}]] ((({{.ProtoType}}))) {{.ProtoType}} |{{.Notes}} |{{.CppType}} |{{.JavaType}} |{{.PythonType}} +{{end}} +|============================================================== diff --git a/examples/templates/example_types.mustache b/examples/templates/example_types.mustache deleted file mode 100644 index a1f3cc2e..00000000 --- a/examples/templates/example_types.mustache +++ /dev/null @@ -1,14 +0,0 @@ -# All the Types - - -## Table of Contents -* [Scalar Value Types](#scalar-value-types) - - -## Scalar Value Types - -| .proto Type | Notes | C++ Type | C# Type | Go Type | Java Type | PHP Type | Python Type | Ruby Type | -| ----------- | ----- | -------- | ------- | --------| --------- | -------- | ----------- | --------- | -{{#scalar_value_types}} -| {{scalar_value_proto_type}} | {{scalar_value_notes}} | {{scalar_value_cpp_type}} | {{scalar_value_cs_type}} | {{scalar_value_go_type}} | {{scalar_value_java_type}} | {{scalar_value_php_type}} | {{scalar_value_python_type}} | {{scalar_value_ruby_type}} | -{{/scalar_value_types}} diff --git a/plugin.go b/plugin.go index 42dc48d4..01b5ec38 100644 --- a/plugin.go +++ b/plugin.go @@ -5,6 +5,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/protoc-gen-go/plugin" "github.com/pseudomuto/protoc-gen-doc/parser" + "io/ioutil" "path" "strings" ) @@ -58,7 +59,18 @@ func RunPlugin(request *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGenerato return nil, err } - output, err := RenderTemplate(options.Type, template) + customTemplate := "" + + if options.TemplateFile != "" { + data, err := ioutil.ReadFile(options.TemplateFile) + if err != nil { + return nil, err + } + + customTemplate = string(data) + } + + output, err := RenderTemplate(options.Type, template, customTemplate) if err != nil { return nil, err } diff --git a/renderer.go b/renderer.go index ecea515f..ae40ec0a 100644 --- a/renderer.go +++ b/renderer.go @@ -78,7 +78,12 @@ type Processor interface { Apply(template *Template) ([]byte, error) } -func RenderTemplate(kind RenderType, template *Template) ([]byte, error) { +func RenderTemplate(kind RenderType, template *Template, inputTemplate string) ([]byte, error) { + if inputTemplate != "" { + processor := &textRenderer{inputTemplate} + return processor.Apply(template) + } + processor, err := kind.renderer() if err != nil { return nil, err diff --git a/renderer_test.go b/renderer_test.go index 6c530558..25f7e873 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -32,22 +32,22 @@ func (assert *RendererTest) SetupSuite() { } func (assert *RendererTest) TestDocBookRenderer() { - _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeDocBook, renderTemplate) + _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeDocBook, renderTemplate, "") assert.Nil(err) } func (assert *RendererTest) TestHtmlRenderer() { - _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeHtml, renderTemplate) + _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeHtml, renderTemplate, "") assert.Nil(err) } func (assert *RendererTest) TestJsonRenderer() { - _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeJson, renderTemplate) + _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeJson, renderTemplate, "") assert.Nil(err) } func (assert *RendererTest) TestMarkdownRenderer() { - _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeMarkdown, renderTemplate) + _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeMarkdown, renderTemplate, "") assert.Nil(err) } From 8313467f985ee1d9729922fe235be6d4463a4b7c Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 21 Jul 2017 19:35:32 -0400 Subject: [PATCH 22/50] Move fixtures into ./test --- generate.go | 2 +- {fixtures => test/fixtures}/Booking.proto | 0 {fixtures => test/fixtures}/Vehicle.proto | 0 {fixtures => test/fixtures}/generator_request.dat | Bin test/protoc_stubs.go | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) rename {fixtures => test/fixtures}/Booking.proto (100%) rename {fixtures => test/fixtures}/Vehicle.proto (100%) rename {fixtures => test/fixtures}/generator_request.dat (100%) diff --git a/generate.go b/generate.go index d8ccbd0a..5d8c7056 100644 --- a/generate.go +++ b/generate.go @@ -1,7 +1,7 @@ package protoc_gen_doc //go:generate go build ./build/cmd/gen_fixtures -//go:generate protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=. fixtures/Booking.proto fixtures/Vehicle.proto +//go:generate protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=./test test/fixtures/Booking.proto test/fixtures/Vehicle.proto //go:generate rm gen_fixtures //go:generate go run build/cmd/resources/main.go -in templates -out resources.go -pkg protoc_gen_doc diff --git a/fixtures/Booking.proto b/test/fixtures/Booking.proto similarity index 100% rename from fixtures/Booking.proto rename to test/fixtures/Booking.proto diff --git a/fixtures/Vehicle.proto b/test/fixtures/Vehicle.proto similarity index 100% rename from fixtures/Vehicle.proto rename to test/fixtures/Vehicle.proto diff --git a/fixtures/generator_request.dat b/test/fixtures/generator_request.dat similarity index 100% rename from fixtures/generator_request.dat rename to test/fixtures/generator_request.dat diff --git a/test/protoc_stubs.go b/test/protoc_stubs.go index 6ad8f277..25db8196 100644 --- a/test/protoc_stubs.go +++ b/test/protoc_stubs.go @@ -10,7 +10,7 @@ import ( func MakeCodeGeneratorRequest() (*plugin_go.CodeGeneratorRequest, error) { _, filename, _, _ := runtime.Caller(0) - filepath := path.Join(path.Dir(filename), "../fixtures/generator_request.dat") + filepath := path.Join(path.Dir(filename), "../test/fixtures/generator_request.dat") data, err := ioutil.ReadFile(filepath) if err != nil { From 93027119a7aaa97ea4cbf0777c16672ffa978240 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 21 Jul 2017 19:36:44 -0400 Subject: [PATCH 23/50] mv ./templates ./resources --- generate.go | 2 +- {templates => resources}/docbook.tmpl | 0 {templates => resources}/html.tmpl | 0 {templates => resources}/markdown.tmpl | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename {templates => resources}/docbook.tmpl (100%) rename {templates => resources}/html.tmpl (100%) rename {templates => resources}/markdown.tmpl (100%) diff --git a/generate.go b/generate.go index 5d8c7056..bdade6e4 100644 --- a/generate.go +++ b/generate.go @@ -4,4 +4,4 @@ package protoc_gen_doc //go:generate protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=./test test/fixtures/Booking.proto test/fixtures/Vehicle.proto //go:generate rm gen_fixtures -//go:generate go run build/cmd/resources/main.go -in templates -out resources.go -pkg protoc_gen_doc +//go:generate go run build/cmd/resources/main.go -in resources -out resources.go -pkg protoc_gen_doc diff --git a/templates/docbook.tmpl b/resources/docbook.tmpl similarity index 100% rename from templates/docbook.tmpl rename to resources/docbook.tmpl diff --git a/templates/html.tmpl b/resources/html.tmpl similarity index 100% rename from templates/html.tmpl rename to resources/html.tmpl diff --git a/templates/markdown.tmpl b/resources/markdown.tmpl similarity index 100% rename from templates/markdown.tmpl rename to resources/markdown.tmpl From 3b9b9e89de91cc7a9f350600ce1774d7dccf19f7 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 21 Jul 2017 20:11:37 -0400 Subject: [PATCH 24/50] Update README --- README.md | 108 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 5cfe5305..eb4fdd29 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ [![Travis Build Status][travis-svg]][travis-ci] -This is a documentation generator plugin for the Google Protocol Buffers compiler -(`protoc`). The plugin can generate HTML, DocBook and Markdown documentation from -comments in your `.proto` files, as well as a raw JSON representation. +This is a documentation generator plugin for the Google Protocol Buffers compiler (`protoc`). The plugin can generate +HTML, JSON, DocBook and Markdown documentation from comments in your `.proto` files. + +It supports proto2 and proto3, and can handle having both in the same context (see [examples](examples/) for proof). ## Installation @@ -12,33 +13,72 @@ comments in your `.proto` files, as well as a raw JSON representation. ## Writing Documentation -Use `/** */` or `///` comments to document your files. Comments for files go at the -very top of the the file. Comments for enumerations, messages and services go before -the message, enumeration or service definition. Comments for fields, enum values, -extensions and service methods can go either before or after the definition. If a -documentation comment begins with `@exclude`, the corresponding item will be excluded -from the generated documentation. +Messages, Fields, Services (and their methods), Enums (and their values), Extensions, and Files can be documented. +Generally speaking, comments come in 2 forms: leading and trailing. + +**Leading comments** + +Leading comments can be used everywhere. + +```protobuf +/** + * This is a leading comment for a message +*/ +message SomeMessage { + // this is another leading comment + string value = 1; +} +``` + +**Trailing comments** + +Fields, Service Methods, Enum Values and Extensions support trailing comments. + +```protobuf +enum MyEnum { + DEFAULT = 0; // the default value + OTHER = 1; // the other value +} +``` + +Check out the [example protos](examples/proto) to see all the options. ## Invoking the Plugin -The plugin is invoked by passing the `--doc_out` option to the `protoc` compiler. The -option has the following format: +The plugin is invoked by passing the `--doc_out`, and `--doc_opt` options to the `protoc` compiler. The option has the +following format: - --doc_out=docbook|html|markdown|json|,[,no-exclude]: + --doc_opt=|, The format may be one of the built-in ones ( `docbook`, `html`, `markdown` or `json`) -or the name of a file containing a custom [Mustache][mustache] template. For example, -to generate HTML documentation for all `.proto` files in the `proto` directory into -`doc/index.html`, type: +or the name of a file containing a custom [Go template][gotemplate]. + +### Simple Usage + +For example, to generate HTML documentation for all `.proto` files in the `proto` directory into `doc/index.html`, type: + + protoc --doc_out=./doc --doc_opt=html,index.html proto/*.proto + +The plugin executable must be in `PATH` for this to work. + +### With a Custom Build + +Alternatively, you can specify a pre-built/not in `PATH` binary using the `--plugin` option. + + protoc --plugin=protoc-gen-doc=./protoc-gen-doc \ + --doc_out=./doc \ + --doc_opt=html,index.html \ + proto/*.proto + +### With a Custom Template + +If you'd like to use your own template, simply use the path to the template file rather than the type. - protoc --doc_out=html,index.html:doc proto/*.proto + protoc --doc_out=./doc --doc_opt=/path/to/template.tmpl,index.txt proto/*.proto -The plugin executable must be in `PATH` or specified explicitly using the `--plugin` -option in order for `protoc` to find it. If you need support for a custom output -format, see [Custom Templates][custom]. If you just want to customize the look of the -HTML output, put your CSS in `stylesheet.css` next to the output file and it will be -picked up. If the optional `no-exclude` flag is given, all `@exclude` directives are -ignored. +For information about the available template arguments and functions, see [Custom Templates][custom]. If you just want +to customize the look of the HTML output, put your CSS in `stylesheet.css` next to the output file and it will be picked +up. ## Output Example @@ -53,35 +93,19 @@ the plugin gives the output * [Markdown](examples/doc/example.md) * [HTML][html_preview] * [DocBook](examples/doc/example.docbook) -* [PDF](examples/doc/example.pdf?raw=true) (Using [Apache FOP][fop]) * [JSON](examples/doc/example.json) -Look in [examples/Makefile](examples/Makefile) to see how these outputs were built. +Check out the `examples` task in the [Makefile](Makefile) to see how these were generated. -[epel]: - https://fedoraproject.org/wiki/EPEL - "EPEL repository" -[mustache]: - http://mustache.github.io/ - "Mustache - Logic-less templates" +[gotemplate]: + https//golang.org/pkg/text/template/ + "Template - The Go Programming Language" [custom]: https://github.com/pseudomuto/protoc-gen-doc/wiki/Custom-Templates "Custom templates instructions" -[fop]: - http://xmlgraphics.apache.org/fop/ - "Apache™ FOP (Formatting Objects Processor)" [html_preview]: https://rawgit.com/pseudomuto/protoc-gen-doc/master/examples/doc/example.html "HTML Example Output" -[obs]: - http://tinyurl.com/protoc-gen-doc-packages - "Packages at Open Build Service" -[releases]: - https://github.com/pseudomuto/protoc-gen-doc/releases - "Releases for download" -[centos]: - http://estan.github.io/protoc-gen-doc/ - "CentOS 7 repository" [travis-svg]: https://travis-ci.org/pseudomuto/protoc-gen-doc.svg?branch=master "Travis CI build status SVG" From 88a49b489fc0648b1ca6f887ba25b2ec1706cc64 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Fri, 21 Jul 2017 20:16:39 -0400 Subject: [PATCH 25/50] Fix headings for md output --- examples/doc/example.md | 31 +++++++++++++++---------------- resources.go | 2 +- resources/markdown.tmpl | 11 +++++------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/examples/doc/example.md b/examples/doc/example.md index 4ceaeb01..5f561f1c 100644 --- a/examples/doc/example.md +++ b/examples/doc/example.md @@ -54,8 +54,8 @@

Top

-## Booking.proto +## Booking.proto Booking related messages. This file is really just an example. The data model is completely @@ -63,8 +63,8 @@ fictional. -### Booking +### Booking Represents the booking of a vehicle. Vehicles are some cool shit. But drive carefully! @@ -84,8 +84,8 @@ Vehicles are some cool shit. But drive carefully! -### BookingStatus +### BookingStatus Represents the status of a vehicle booking. @@ -100,8 +100,8 @@ Represents the status of a vehicle booking. -### EmptyBookingMessage +### EmptyBookingMessage An empty message for testing @@ -116,8 +116,8 @@ An empty message for testing -### BookingService +### BookingService Service for handling vehicle bookings. | Method Name | Request Type | Response Type | Description | @@ -130,14 +130,14 @@ Service for handling vehicle bookings.

Top

-## Customer.proto +## Customer.proto This file has messages for describing a customer. -### Address +### Address Represents a mail address. @@ -156,8 +156,8 @@ Represents a mail address. -### Customer +### Customer Represents a customer. @@ -187,14 +187,14 @@ Represents a customer.

Top

-## Vehicle.proto +## Vehicle.proto Messages describing manufacturers / vehicles. -### Manufacturer +### Manufacturer Represents a manufacturer of cars. @@ -211,8 +211,8 @@ Represents a manufacturer of cars. -### Model +### Model Represents a vehicle model. @@ -230,8 +230,8 @@ Represents a vehicle model. -### Vehicle +### Vehicle Represents a vehicle that can be hired. @@ -256,8 +256,8 @@ Represents a vehicle that can be hired. -### Vehicle.Category +### Vehicle.Category Represents a vehicle category. E.g. "Sedan" or "Truck". @@ -274,8 +274,8 @@ Represents a vehicle category. E.g. "Sedan" or "Truck". -### Manufacturer.Category +### Manufacturer.Category Manufacturer category. A manufacturer may be either inhouse or external. | Name | Number | Description | @@ -288,8 +288,8 @@ Manufacturer category. A manufacturer may be either inhouse or external. -### File-level Extensions +### File-level Extensions | Extension | Type | Base | Number | Description | | --------- | ---- | ---- | ------ | ----------- | | country | string | Manufacturer | 100 | Manufacturer country. Default: `China` | @@ -300,7 +300,6 @@ Manufacturer category. A manufacturer may be either inhouse or external. - ## Scalar Value Types | .proto Type | Notes | C++ Type | Java Type | Python Type | diff --git a/resources.go b/resources.go index 2bc6625d..95a327e2 100644 --- a/resources.go +++ b/resources.go @@ -12,7 +12,7 @@ import ( var embeddedResources = map[string]string{ "docbook.tmpl": "H4sIAAAAAAAA/+xZ30/bOhR+719h5fEiEq64SFeTW6TBqmkChIDt3U1OW2uOndlOAUX93ycnIb8Tl7UUtvGCZJ/P58TH5/uOa/DpQ8jQCqSigo+df90jBwH3RUD5Yux8vZse/u+cTkaYSE19BpMRQlhTzWByLYUWvmDoXPhxCFwTTQXHXmYdIZQkkvAFIHdKGaj12ixV4BuUMReOksS9IiGs15W1ZnVEJEHuOShf0sisSl1U/F6CUmSRuy6dIxqMnSRxpzFjmWMnc1mNeCH4oiPqUFxjo3PkfiZqSoEFqpjHmswYoLkkIYwdwlgRsAiJfUaU4iRsRS8NKHPb+CDjYiFFHCFfMDV2/qs4RwibyQh8Y7yngV6OnX8cb2vEkXtiBx03IXoJJKjOIISluK/PIISBa/k4SXeLvWzQDbl7jGAYcUFmwIYhlZPsBGKv8Y3Ya20E65kIGusq9V2rBuvGKwU/9N2YUf4dmT/Ay4o2KTEVnVdRNsSegU2G/ZkVJlu2uJ0MyEr/HOYkZvobYbGJanCTfO4DSpKm3UsBSQI86Anayr1Jawqvn0c9+9jLGFHQ2ksJWFK46qEg7acHDdzI3O6JewVKQ4DKCBYOn+yDw2+D5UVOtmX6R6IsiKs4nIF8ZTHoqDJrjl5JENr+zgTXhHLKFw3PpeH5opMdy1+lOtirXXSqprJQeBzu7+qyK6VLk2yTt+N9yNuWumT29hvISZbvnUvJdrTcF6l6aJQP+tp6/UdGUd7mV8ghgxWw/j6NKZ8LGRI2yJb3Tv7eyd87+R/VyWu830R88hq5Bbmi/q89Qbx8D+/o35egl+JtvDG8uGBle0X2Tn8DP2JQGtml6wZUJLiCDaAvrk/5Ue5BnPL8NJQkn91aqp5y2nKfTT9bpt7ehaVhqEhJ54PorU8YkdlVOy20Ol3tl5T+K4qNhh32Extgt/Y2B1qnlJ+0G0mhRT8Tn+4SQpsE9gPODg6sTr6QFbGCrh/1UvA+WKPcWmxvc73sMmlB1Jnel5an1pM+z1foVBkP7cFohEmYFXUWRRt5M5nbCJhlrxfaYmuTqw2m1nna0eErlBxhr/j/xs8AAAD//2FyhLQRGQAA", "html.tmpl": "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", - "markdown.tmpl": "H4sIAAAAAAAA/+RWTW/bMAy9+1dwyQ5rCyf3IvFh7YphaIuiLXYphlVJmMSAInmWHKyw/N8HSv6Q7XgJUGCX+WKTkijyvSfKY3hIpZZLyeFaLrMdCs10LEUwYyDYDucjLZPRNAqC8Rie2YIjyDVcSaFRaBXkecrEBmFyE3NURRHk+cd1zPEnrYXLOUzu2Q6LIjiHlzwvjR+fxvX3WQBQB7lDpdjGxgEAcGtupdj4624yzv21KFZ2fh3li8h27w3xW6NQsRReHKow5LhHDs2wjddUXBQh1mMDsZ8w3cfLTpHHs6ve5/DytGScpfCd8Qzh+S1BSkNZZ7gnZ6jJeRacTk/Ndp0LcT5LgPF4I+ajNN5s9SiaMdimuJ6PxqSK6FkmsymLZtMkInnUa2njyTWqZRonJCbn6ZPs79pUTTuPXbiGuMMh4zVMvjJ1EyNfUUAD9hOMhQUM3LIFcjDgrQQTGAjpAfeGtlk+YHz0KD6EljLT1Ammlhft5xPo7DM322Zhp+e5kIsU2pW4Oq5xzTKuLatFAaV5CbZuf6hUgk2wrY0GkZaATSPYBpnPTNHrPtstMB1CqI9SDdIwWs3eBxFrAeZsaicsFrHYdEdcev8GOjc0+xCGgGIFu1KnEIaRJ9+qu7xTuwZo7BgBJ6BtKxtE+hh+HhJ9BJBKrco/JCwPg4EeWOFxsHkG/68yX3vSfB3UZkNIC/+OLr1r5ag0/ybLO9RbuarU+Yi/MlS6ouYRVSKFwsoepKbLQtfs2sa/HyiB4W5bptRtuqW73XvdfJfzSQuOHZIeJaqEvWKjPEt0HfavacJ3ktD/VgXgvdSowMDVxUXl+sb2rPp+eNNbKUrrML4ekNA/GwcOgUurwddXi/0XdGiMYBpB21WSQSnX5yNJ/DHK3bdd/pWnhudPAAAA//8m3DykdQoAAA==", + "markdown.tmpl": "H4sIAAAAAAAA/+RWTW+bQBC98yumpocmEfY9sn1o0qiqkihKol6iqlnbYxtpvUvZxWrE8t+r/WBZMNSWIvVSLjD7Mbx57+1ADA85l3zJKVzzZbFDJolMOYumBBjZ4WwkeTaazKMojuGZLCgCX8MVZxKZFFFZ5oRtEMY3KUVRVVFZflynFH/qvXA5g/E92WFVRefwUpYu+PEp9s9nEYBPcodCkI3JAwBg99xytgn33RSUhnuRrcx6n+ULK3bvTfFbIhMpZ0EeXWFCcY8UmmmTr6m4qhL0cwO5nzDfp8tOkcfR1fdzeHlaEkpy+E5ogfD8lqGGIcxgsteDidSDZ9Hp8ni1PRat+TQDQtMNm43ydLOVo/mUwDbH9WwUa1fMn3k2nZD5dJJZf/jNUVmOr1Es8zTTZqqqAEogcvjWpmrnNpOuUa43ZbqG8VciblKkK51QgXkEZWgBBbdkgRQUBDtBRQoSfYG9Qzt0F6iQPZ0fEiOZasoE5e2l3xcKaOMzu9qgMMvLkvFFDu1KbB3XuCYFlUbVqgIXXoKpO5xyTjAA295oGGkZWDWGbZj5TIS+3Re7BeZDDB2y5EkaZqt5dy9jLcJsrNsJSVnKNt0ZC+/fUGenph+SBJCtYOd8CkkyD+xbd5d3eleBnjomwAlsm8oGmT7GX8DEIQOoS63L7zNWwMFAD/R89HbP/9eZrwfWfB30ZiNIi/+OL4PPynFr/sWWdyi3fFW78xF/FShkLc0jiowzgXU8KE1XhW7YjVX4fdAAhrutg9Rtum643Xvteov5pA3HDsmBJMLRXqvhzlIcw+FXWtM7zvTvVs3fPZcoQMHVxUU99I3sSf388Ca3nLmon96ARzg8Gj1nwMJq6A3NYn4FLRkjmMyhPeS00JD98ciycE5jD2OLvx7x7PwJAAD//3dr9Ll0CgAA", } func fetchResource(name string) ([]byte, error) { diff --git a/resources/markdown.tmpl b/resources/markdown.tmpl index 187ed373..48d3b7db 100644 --- a/resources/markdown.tmpl +++ b/resources/markdown.tmpl @@ -24,14 +24,14 @@ {{$file_name := .Name}}

Top

-## {{.Name}} +## {{.Name}} {{.Description}} {{range .Messages}} -### {{.LongName}} +### {{.LongName}} {{.Description}} {{if .HasFields}} @@ -54,8 +54,8 @@ {{range .Enums}} -### {{.LongName}} +### {{.LongName}} {{.Description}} | Name | Number | Description | @@ -68,8 +68,8 @@ {{if .HasExtensions}} -### File-level Extensions +### File-level Extensions | Extension | Type | Base | Number | Description | | --------- | ---- | ---- | ------ | ----------- | {{range .Extensions -}} @@ -79,8 +79,8 @@ {{range .Services}} -### {{.Name}} +### {{.Name}} {{.Description}} | Method Name | Request Type | Response Type | Description | @@ -92,7 +92,6 @@ {{end}} - ## Scalar Value Types | .proto Type | Notes | C++ Type | Java Type | Python Type | From 726922de303ce8026929a1423da4beaf46531eda Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Sat, 22 Jul 2017 14:35:19 -0400 Subject: [PATCH 26/50] Fix JSON template to be more idiomatic --- examples/doc/example.json | 1265 +++++++++++++++++------------- examples/doc/example.txt | 4 +- examples/templates/asciidoc.tmpl | 2 +- plugin_test.go | 2 +- renderer.go | 2 +- template.go | 154 ++-- 6 files changed, 810 insertions(+), 619 deletions(-) diff --git a/examples/doc/example.json b/examples/doc/example.json index 6c3a0dd4..50ea8067 100644 --- a/examples/doc/example.json +++ b/examples/doc/example.json @@ -1,539 +1,726 @@ -[ - { - "file_name": "Booking.proto", - "file_description": "Booking related messages.\n\nThis file is really just an example. The data model is completely\nfictional.", - "file_package": "com.example", - "file_has_enums": false, - "file_has_extensions": false, - "file_has_messages": true, - "file_has_services": true, - "file_enums": null, - "file_extensions": null, - "file_messages": [ - { - "message_name": "Booking", - "message_long_name": "Booking", - "message_full_name": "com.example.Booking", - "message_description": "Represents the booking of a vehicle.\n\nVehicles are some cool shit. But drive carefully!", - "message_extensions": null, - "message_fields": [ - { - "field_name": "vehicle_id", - "field_description": "ID of booked vehicle.", - "field_label": "", - "field_type": "int32", - "field_long_type": "int32", - "field_full_type": "int32", - "field_default_value": "" - }, - { - "field_name": "customer_id", - "field_description": "Customer that booked the vehicle.", - "field_label": "", - "field_type": "int32", - "field_long_type": "int32", - "field_full_type": "int32", - "field_default_value": "" - }, - { - "field_name": "status", - "field_description": "Status of the booking.", - "field_label": "", - "field_type": "BookingStatus", - "field_long_type": "BookingStatus", - "field_full_type": "com.example.BookingStatus", - "field_default_value": "" - }, - { - "field_name": "confirmation_sent", - "field_description": "Has booking confirmation been sent?", - "field_label": "", - "field_type": "bool", - "field_long_type": "bool", - "field_full_type": "bool", - "field_default_value": "" - }, - { - "field_name": "payment_received", - "field_description": "Has payment been received?", - "field_label": "", - "field_type": "bool", - "field_long_type": "bool", - "field_full_type": "bool", - "field_default_value": "" - } - ] - }, - { - "message_name": "BookingStatus", - "message_long_name": "BookingStatus", - "message_full_name": "com.example.BookingStatus", - "message_description": "Represents the status of a vehicle booking.", - "message_extensions": null, - "message_fields": [ - { - "field_name": "id", - "field_description": "Unique booking status ID.", - "field_label": "", - "field_type": "int32", - "field_long_type": "int32", - "field_full_type": "int32", - "field_default_value": "" - }, - { - "field_name": "description", - "field_description": "Booking status description. E.g. \"Active\".", - "field_label": "", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - } - ] - }, - { - "message_name": "EmptyBookingMessage", - "message_long_name": "EmptyBookingMessage", - "message_full_name": "com.example.EmptyBookingMessage", - "message_description": "An empty message for testing", - "message_extensions": null, - "message_fields": null - } - ], - "file_services": [ - { - "service_name": "BookingService", - "service_long_name": "BookingService", - "service_full_name": "com.example.BookingService", - "service_description": "Service for handling vehicle bookings.", - "service_methods": [ - { - "method_name": "BookVehicle", - "method_description": "Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.", - "method_request_type": "Booking", - "method_request_long_type": "Booking", - "method_request_full_type": "com.example.Booking", - "method_response_type": "BookingStatus", - "method_response_long_type": "BookingStatus", - "method_response_full_type": "com.example.BookingStatus" - } - ] - } - ] - }, - { - "file_name": "Customer.proto", - "file_description": "This file has messages for describing a customer.", - "file_package": "com.example", - "file_has_enums": false, - "file_has_extensions": false, - "file_has_messages": true, - "file_has_services": false, - "file_enums": null, - "file_extensions": null, - "file_messages": [ - { - "message_name": "Address", - "message_long_name": "Address", - "message_full_name": "com.example.Address", - "message_description": "Represents a mail address.", - "message_extensions": null, - "message_fields": [ - { - "field_name": "address_line_1", - "field_description": "First address line.", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "address_line_2", - "field_description": "Second address line.", - "field_label": "optional", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "address_line_3", - "field_description": "Second address line.", - "field_label": "optional", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "town", - "field_description": "Address town.", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "county", - "field_description": "Address county, if applicable.", - "field_label": "optional", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "country", - "field_description": "Address country.", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - } - ] - }, - { - "message_name": "Customer", - "message_long_name": "Customer", - "message_full_name": "com.example.Customer", - "message_description": "Represents a customer.", - "message_extensions": null, - "message_fields": [ - { - "field_name": "id", - "field_description": "Unique customer ID.", - "field_label": "required", - "field_type": "int32", - "field_long_type": "int32", - "field_full_type": "int32", - "field_default_value": "" - }, - { - "field_name": "first_name", - "field_description": "Customer first name.", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "last_name", - "field_description": "Customer last name.", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "details", - "field_description": "Customer details.", - "field_label": "optional", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "email_address", - "field_description": "Customer e-mail address.", - "field_label": "optional", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "phone_number", - "field_description": "Customer phone numbers, primary first.", - "field_label": "repeated", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "mail_addresses", - "field_description": "Customer mail addresses, primary first.", - "field_label": "repeated", - "field_type": "Address", - "field_long_type": "Address", - "field_full_type": "com.example.Address", - "field_default_value": "" - } - ] - } - ], - "file_services": null - }, - { - "file_name": "Vehicle.proto", - "file_description": "Messages describing manufacturers / vehicles.", - "file_package": "com.example", - "file_has_enums": true, - "file_has_extensions": true, - "file_has_messages": true, - "file_has_services": false, - "file_enums": [ - { - "enum_name": "Category", - "enum_long_name": "Manufacturer.Category", - "enum_full_name": "com.example.Manufacturer.Category", - "enum_description": "Manufacturer category. A manufacturer may be either inhouse or external.", - "enum_values": [ - { - "value_name": "CATEGORY_INHOUSE", - "value_number": "0", - "value_description": "The manufacturer is inhouse." - }, - { - "value_name": "CATEGORY_EXTERNAL", - "value_number": "1", - "value_description": "The manufacturer is external." - } - ] - } - ], - "file_extensions": [ - { - "extension_name": "country", - "extension_long_name": "Manufacturer.country", - "extension_full_name": "com.example.Manufacturer.country", - "extension_description": "Manufacturer country.", - "extension_label": "optional", - "extension_type": "string", - "extension_long_type": "string", - "extension_full_type": "string", - "extension_number": 100, - "extension_default_value": "China", - "extension_containing_type": "Manufacturer", - "extension_containing_long_type": "Manufacturer", - "extension_containing_full_type": "com.example.Manufacturer" - } - ], - "file_messages": [ - { - "message_name": "Manufacturer", - "message_long_name": "Manufacturer", - "message_full_name": "com.example.Manufacturer", - "message_description": "Represents a manufacturer of cars.", - "message_extensions": null, - "message_fields": [ - { - "field_name": "id", - "field_description": "The unique manufacturer ID.", - "field_label": "required", - "field_type": "int32", - "field_long_type": "int32", - "field_full_type": "int32", - "field_default_value": "" - }, - { - "field_name": "code", - "field_description": "A manufacturer code, e.g. \"DKL4P\".", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "details", - "field_description": "Manufacturer details (minimum orders et.c.).", - "field_label": "optional", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "category", - "field_description": "Manufacturer category.", - "field_label": "optional", - "field_type": "Category", - "field_long_type": "Manufacturer.Category", - "field_full_type": "com.example.Manufacturer.Category", - "field_default_value": "" - } - ] - }, - { - "message_name": "Model", - "message_long_name": "Model", - "message_full_name": "com.example.Model", - "message_description": "Represents a vehicle model.", - "message_extensions": null, - "message_fields": [ - { - "field_name": "id", - "field_description": "The unique model ID.", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "model_code", - "field_description": "The car model code, e.g. \"PZ003\".", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "model_name", - "field_description": "The car model name, e.g. \"Z3\".", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "daily_hire_rate_dollars", - "field_description": "Dollars per day.", - "field_label": "required", - "field_type": "sint32", - "field_long_type": "sint32", - "field_full_type": "sint32", - "field_default_value": "" - }, - { - "field_name": "daily_hire_rate_cents", - "field_description": "Cents per day.", - "field_label": "required", - "field_type": "sint32", - "field_long_type": "sint32", - "field_full_type": "sint32", - "field_default_value": "" - } - ] - }, - { - "message_name": "Vehicle", - "message_long_name": "Vehicle", - "message_full_name": "com.example.Vehicle", - "message_description": "Represents a vehicle that can be hired.", - "message_extensions": [ - { - "extension_name": "series", - "extension_long_name": "Model.series", - "extension_full_name": "com.example.Model.series", - "extension_description": "Vehicle model series.", - "extension_label": "optional", - "extension_type": "string", - "extension_long_type": "string", - "extension_full_type": "string", - "extension_number": 100, - "extension_default_value": "", - "extension_containing_type": "Model", - "extension_containing_long_type": "Model", - "extension_containing_full_type": "com.example.Model", - "extension_scope_type": "Vehicle", - "extension_scope_long_type": "Vehicle", - "extension_scope_full_type": "com.example.Vehicle" - } - ], - "message_fields": [ - { - "field_name": "id", - "field_description": "Unique vehicle ID.", - "field_label": "required", - "field_type": "int32", - "field_long_type": "int32", - "field_full_type": "int32", - "field_default_value": "" - }, - { - "field_name": "model", - "field_description": "Vehicle model.", - "field_label": "required", - "field_type": "Model", - "field_long_type": "Model", - "field_full_type": "com.example.Model", - "field_default_value": "" - }, - { - "field_name": "reg_number", - "field_description": "Vehicle registration number.", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "mileage", - "field_description": "Current vehicle mileage, if known.", - "field_label": "optional", - "field_type": "sint32", - "field_long_type": "sint32", - "field_full_type": "sint32", - "field_default_value": "" - }, - { - "field_name": "category", - "field_description": "Vehicle category.", - "field_label": "optional", - "field_type": "Category", - "field_long_type": "Vehicle.Category", - "field_full_type": "com.example.Vehicle.Category", - "field_default_value": "" - }, - { - "field_name": "daily_hire_rate_dollars", - "field_description": "Dollars per day.", - "field_label": "optional", - "field_type": "sint32", - "field_long_type": "sint32", - "field_full_type": "sint32", - "field_default_value": "" - }, - { - "field_name": "daily_hire_rate_cents", - "field_description": "Cents per day.", - "field_label": "optional", - "field_type": "sint32", - "field_long_type": "sint32", - "field_full_type": "sint32", - "field_default_value": "" - } - ] - }, - { - "message_name": "Category", - "message_long_name": "Vehicle.Category", - "message_full_name": "com.example.Vehicle.Category", - "message_description": "Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", - "message_extensions": null, - "message_fields": [ - { - "field_name": "code", - "field_description": "Category code. E.g. \"S\".", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - }, - { - "field_name": "description", - "field_description": "Category name. E.g. \"Sedan\".", - "field_label": "required", - "field_type": "string", - "field_long_type": "string", - "field_full_type": "string", - "field_default_value": "" - } - ] - } - ], - "file_services": null - } -] \ No newline at end of file +{ + "files": [ + { + "name": "Booking.proto", + "description": "Booking related messages.\n\nThis file is really just an example. The data model is completely\nfictional.", + "package": "com.example", + "hasEnums": false, + "hasExtensions": false, + "hasMessages": true, + "hasServices": true, + "enums": [], + "extensions": [], + "messages": [ + { + "name": "Booking", + "longName": "Booking", + "fullName": "com.example.Booking", + "description": "Represents the booking of a vehicle.\n\nVehicles are some cool shit. But drive carefully!", + "hasExtensions": false, + "hasFields": true, + "extensions": [], + "fields": [ + { + "name": "vehicle_id", + "description": "ID of booked vehicle.", + "label": "", + "type": "int32", + "longType": "int32", + "fullType": "int32", + "defaultValue": "" + }, + { + "name": "customer_id", + "description": "Customer that booked the vehicle.", + "label": "", + "type": "int32", + "longType": "int32", + "fullType": "int32", + "defaultValue": "" + }, + { + "name": "status", + "description": "Status of the booking.", + "label": "", + "type": "BookingStatus", + "longType": "BookingStatus", + "fullType": "com.example.BookingStatus", + "defaultValue": "" + }, + { + "name": "confirmation_sent", + "description": "Has booking confirmation been sent?", + "label": "", + "type": "bool", + "longType": "bool", + "fullType": "bool", + "defaultValue": "" + }, + { + "name": "payment_received", + "description": "Has payment been received?", + "label": "", + "type": "bool", + "longType": "bool", + "fullType": "bool", + "defaultValue": "" + } + ] + }, + { + "name": "BookingStatus", + "longName": "BookingStatus", + "fullName": "com.example.BookingStatus", + "description": "Represents the status of a vehicle booking.", + "hasExtensions": false, + "hasFields": true, + "extensions": [], + "fields": [ + { + "name": "id", + "description": "Unique booking status ID.", + "label": "", + "type": "int32", + "longType": "int32", + "fullType": "int32", + "defaultValue": "" + }, + { + "name": "description", + "description": "Booking status description. E.g. \"Active\".", + "label": "", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + } + ] + }, + { + "name": "EmptyBookingMessage", + "longName": "EmptyBookingMessage", + "fullName": "com.example.EmptyBookingMessage", + "description": "An empty message for testing", + "hasExtensions": false, + "hasFields": false, + "extensions": [], + "fields": [] + } + ], + "services": [ + { + "name": "BookingService", + "longName": "BookingService", + "fullName": "com.example.BookingService", + "description": "Service for handling vehicle bookings.", + "methods": [ + { + "name": "BookVehicle", + "description": "Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.", + "requestType": "Booking", + "requestLongType": "Booking", + "requestFullType": "com.example.Booking", + "responseType": "BookingStatus", + "responseLongType": "BookingStatus", + "responseFullType": "com.example.BookingStatus" + } + ] + } + ] + }, + { + "name": "Customer.proto", + "description": "This file has messages for describing a customer.", + "package": "com.example", + "hasEnums": false, + "hasExtensions": false, + "hasMessages": true, + "hasServices": false, + "enums": [], + "extensions": [], + "messages": [ + { + "name": "Address", + "longName": "Address", + "fullName": "com.example.Address", + "description": "Represents a mail address.", + "hasExtensions": false, + "hasFields": true, + "extensions": [], + "fields": [ + { + "name": "address_line_1", + "description": "First address line.", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "address_line_2", + "description": "Second address line.", + "label": "optional", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "address_line_3", + "description": "Second address line.", + "label": "optional", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "town", + "description": "Address town.", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "county", + "description": "Address county, if applicable.", + "label": "optional", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "country", + "description": "Address country.", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + } + ] + }, + { + "name": "Customer", + "longName": "Customer", + "fullName": "com.example.Customer", + "description": "Represents a customer.", + "hasExtensions": false, + "hasFields": true, + "extensions": [], + "fields": [ + { + "name": "id", + "description": "Unique customer ID.", + "label": "required", + "type": "int32", + "longType": "int32", + "fullType": "int32", + "defaultValue": "" + }, + { + "name": "first_name", + "description": "Customer first name.", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "last_name", + "description": "Customer last name.", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "details", + "description": "Customer details.", + "label": "optional", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "email_address", + "description": "Customer e-mail address.", + "label": "optional", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "phone_number", + "description": "Customer phone numbers, primary first.", + "label": "repeated", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "mail_addresses", + "description": "Customer mail addresses, primary first.", + "label": "repeated", + "type": "Address", + "longType": "Address", + "fullType": "com.example.Address", + "defaultValue": "" + } + ] + } + ], + "services": [] + }, + { + "name": "Vehicle.proto", + "description": "Messages describing manufacturers / vehicles.", + "package": "com.example", + "hasEnums": true, + "hasExtensions": true, + "hasMessages": true, + "hasServices": false, + "enums": [ + { + "name": "Category", + "longName": "Manufacturer.Category", + "fullName": "com.example.Manufacturer.Category", + "description": "Manufacturer category. A manufacturer may be either inhouse or external.", + "values": [ + { + "name": "CATEGORY_INHOUSE", + "number": "0", + "description": "The manufacturer is inhouse." + }, + { + "name": "CATEGORY_EXTERNAL", + "number": "1", + "description": "The manufacturer is external." + } + ] + } + ], + "extensions": [ + { + "name": "country", + "longName": "Manufacturer.country", + "fullName": "com.example.Manufacturer.country", + "description": "Manufacturer country.", + "label": "optional", + "type": "string", + "longType": "string", + "fullType": "string", + "number": 100, + "defaultValue": "China", + "containingType": "Manufacturer", + "containingLongType": "Manufacturer", + "containingFullType": "com.example.Manufacturer" + } + ], + "messages": [ + { + "name": "Manufacturer", + "longName": "Manufacturer", + "fullName": "com.example.Manufacturer", + "description": "Represents a manufacturer of cars.", + "hasExtensions": false, + "hasFields": true, + "extensions": [], + "fields": [ + { + "name": "id", + "description": "The unique manufacturer ID.", + "label": "required", + "type": "int32", + "longType": "int32", + "fullType": "int32", + "defaultValue": "" + }, + { + "name": "code", + "description": "A manufacturer code, e.g. \"DKL4P\".", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "details", + "description": "Manufacturer details (minimum orders et.c.).", + "label": "optional", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "category", + "description": "Manufacturer category.", + "label": "optional", + "type": "Category", + "longType": "Manufacturer.Category", + "fullType": "com.example.Manufacturer.Category", + "defaultValue": "" + } + ] + }, + { + "name": "Model", + "longName": "Model", + "fullName": "com.example.Model", + "description": "Represents a vehicle model.", + "hasExtensions": false, + "hasFields": true, + "extensions": [], + "fields": [ + { + "name": "id", + "description": "The unique model ID.", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "model_code", + "description": "The car model code, e.g. \"PZ003\".", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "model_name", + "description": "The car model name, e.g. \"Z3\".", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "daily_hire_rate_dollars", + "description": "Dollars per day.", + "label": "required", + "type": "sint32", + "longType": "sint32", + "fullType": "sint32", + "defaultValue": "" + }, + { + "name": "daily_hire_rate_cents", + "description": "Cents per day.", + "label": "required", + "type": "sint32", + "longType": "sint32", + "fullType": "sint32", + "defaultValue": "" + } + ] + }, + { + "name": "Vehicle", + "longName": "Vehicle", + "fullName": "com.example.Vehicle", + "description": "Represents a vehicle that can be hired.", + "hasExtensions": true, + "hasFields": true, + "extensions": [ + { + "name": "series", + "longName": "Model.series", + "fullName": "com.example.Model.series", + "description": "Vehicle model series.", + "label": "optional", + "type": "string", + "longType": "string", + "fullType": "string", + "number": 100, + "defaultValue": "", + "containingType": "Model", + "containingLongType": "Model", + "containingFullType": "com.example.Model", + "scopeType": "Vehicle", + "scopeLongType": "Vehicle", + "scopeFullType": "com.example.Vehicle" + } + ], + "fields": [ + { + "name": "id", + "description": "Unique vehicle ID.", + "label": "required", + "type": "int32", + "longType": "int32", + "fullType": "int32", + "defaultValue": "" + }, + { + "name": "model", + "description": "Vehicle model.", + "label": "required", + "type": "Model", + "longType": "Model", + "fullType": "com.example.Model", + "defaultValue": "" + }, + { + "name": "reg_number", + "description": "Vehicle registration number.", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "mileage", + "description": "Current vehicle mileage, if known.", + "label": "optional", + "type": "sint32", + "longType": "sint32", + "fullType": "sint32", + "defaultValue": "" + }, + { + "name": "category", + "description": "Vehicle category.", + "label": "optional", + "type": "Category", + "longType": "Vehicle.Category", + "fullType": "com.example.Vehicle.Category", + "defaultValue": "" + }, + { + "name": "daily_hire_rate_dollars", + "description": "Dollars per day.", + "label": "optional", + "type": "sint32", + "longType": "sint32", + "fullType": "sint32", + "defaultValue": "" + }, + { + "name": "daily_hire_rate_cents", + "description": "Cents per day.", + "label": "optional", + "type": "sint32", + "longType": "sint32", + "fullType": "sint32", + "defaultValue": "" + } + ] + }, + { + "name": "Category", + "longName": "Vehicle.Category", + "fullName": "com.example.Vehicle.Category", + "description": "Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", + "hasExtensions": false, + "hasFields": true, + "extensions": [], + "fields": [ + { + "name": "code", + "description": "Category code. E.g. \"S\".", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + }, + { + "name": "description", + "description": "Category name. E.g. \"Sedan\".", + "label": "required", + "type": "string", + "longType": "string", + "fullType": "string", + "defaultValue": "" + } + ] + } + ], + "services": [] + } + ], + "scalar_value_types": [ + { + "protoType": "double", + "notes": "", + "cppType": "double", + "csType": "double", + "goType": "float64", + "javaType": "double", + "phpType": "float", + "pythonType": "float", + "rubyType": "Float" + }, + { + "protoType": "float", + "notes": "", + "cppType": "float", + "csType": "float", + "goType": "float32", + "javaType": "float", + "phpType": "float", + "pythonType": "float", + "rubyType": "Float" + }, + { + "protoType": "int32", + "notes": "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.", + "cppType": "int32", + "csType": "int", + "goType": "int32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "int64", + "notes": "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.", + "cppType": "int64", + "csType": "long", + "goType": "int64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum" + }, + { + "protoType": "uint32", + "notes": "Uses variable-length encoding.", + "cppType": "uint32", + "csType": "uint", + "goType": "uint32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int/long", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "uint64", + "notes": "Uses variable-length encoding.", + "cppType": "uint64", + "csType": "ulong", + "goType": "uint64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "sint32", + "notes": "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.", + "cppType": "int32", + "csType": "int", + "goType": "int32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "sint64", + "notes": "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.", + "cppType": "int64", + "csType": "long", + "goType": "int64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum" + }, + { + "protoType": "fixed32", + "notes": "Always four bytes. More efficient than uint32 if values are often greater than 2^28.", + "cppType": "uint32", + "csType": "uint", + "goType": "uint32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "fixed64", + "notes": "Always eight bytes. More efficient than uint64 if values are often greater than 2^56.", + "cppType": "uint64", + "csType": "ulong", + "goType": "uint64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum" + }, + { + "protoType": "sfixed32", + "notes": "Always four bytes.", + "cppType": "int32", + "csType": "int", + "goType": "int32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "sfixed64", + "notes": "Always eight bytes.", + "cppType": "int64", + "csType": "long", + "goType": "int64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum" + }, + { + "protoType": "bool", + "notes": "", + "cppType": "bool", + "csType": "bool", + "goType": "bool", + "javaType": "boolean", + "phpType": "boolean", + "pythonType": "boolean", + "rubyType": "TrueClass/FalseClass" + }, + { + "protoType": "string", + "notes": "A string must always contain UTF-8 encoded or 7-bit ASCII text.", + "cppType": "string", + "csType": "string", + "goType": "string", + "javaType": "String", + "phpType": "string", + "pythonType": "str/unicode", + "rubyType": "String (UTF-8)" + }, + { + "protoType": "bytes", + "notes": "May contain any arbitrary sequence of bytes.", + "cppType": "string", + "csType": "ByteString", + "goType": "[]byte", + "javaType": "ByteString", + "phpType": "string", + "pythonType": "str", + "rubyType": "String (ASCII-8BIT)" + } + ] +} \ No newline at end of file diff --git a/examples/doc/example.txt b/examples/doc/example.txt index 68f0893d..36388aab 100644 --- a/examples/doc/example.txt +++ b/examples/doc/example.txt @@ -8,9 +8,7 @@ === Booking -Represents the booking of a vehicle. - -Vehicles are some cool shit. But drive carefully! +Represents the booking of a vehicle.Vehicles are some cool shit. But drive carefully! |=========================================== diff --git a/examples/templates/asciidoc.tmpl b/examples/templates/asciidoc.tmpl index d05a27e2..be1424c3 100644 --- a/examples/templates/asciidoc.tmpl +++ b/examples/templates/asciidoc.tmpl @@ -8,7 +8,7 @@ {{range .Messages}} === {{.LongName}} -{{.Description}} +{{nobr .Description}} {{if .HasFields}} |=========================================== diff --git a/plugin_test.go b/plugin_test.go index a9f367ea..9ed3b9dd 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -83,7 +83,7 @@ func (assert *PluginTest) TestRunPluginForBuiltinTemplate() { func (assert *PluginTest) TestRunPluginForCustomTemplate() { req := new(plugin_go.CodeGeneratorRequest) - req.Parameter = proto.String("templates/html.tmpl,/base/name/only/output.html") + req.Parameter = proto.String("resources/html.tmpl,/base/name/only/output.html") resp, err := protoc_gen_doc.RunPlugin(req) assert.Nil(err) diff --git a/renderer.go b/renderer.go index ae40ec0a..24f65d87 100644 --- a/renderer.go +++ b/renderer.go @@ -131,5 +131,5 @@ func (mr *htmlRenderer) Apply(template *Template) ([]byte, error) { type jsonRenderer struct{} func (r *jsonRenderer) Apply(template *Template) ([]byte, error) { - return json.MarshalIndent(template.Files, "", " ") + return json.MarshalIndent(template, "", " ") } diff --git a/template.go b/template.go index 88db649c..accda58a 100644 --- a/template.go +++ b/template.go @@ -24,6 +24,10 @@ func NewTemplate(pr *parser.ParseResult) *Template { HasExtensions: len(f.Extensions) > 0, HasMessages: len(f.Messages) > 0, HasServices: len(f.Services) > 0, + Enums: make(orderedEnums, 0, len(f.Enums)), + Extensions: make(orderedExtensions, 0, len(f.Extensions)), + Messages: make(orderedMessages, 0, len(f.Messages)), + Services: make(orderedServices, 0, len(f.Services)), } for _, e := range f.Enums { @@ -54,111 +58,111 @@ func NewTemplate(pr *parser.ParseResult) *Template { } type File struct { - Name string `json:"file_name"` - Description string `json:"file_description"` - Package string `json:"file_package"` - - HasEnums bool `json:"file_has_enums"` - HasExtensions bool `json:"file_has_extensions"` - HasMessages bool `json:"file_has_messages"` - HasServices bool `json:"file_has_services"` - - Enums orderedEnums `json:"file_enums"` - Extensions orderedExtensions `json:"file_extensions"` - Messages orderedMessages `json:"file_messages"` - Services orderedServices `json:"file_services"` + Name string `json:"name"` + Description string `json:"description"` + Package string `json:"package"` + + HasEnums bool `json:"hasEnums"` + HasExtensions bool `json:"hasExtensions"` + HasMessages bool `json:"hasMessages"` + HasServices bool `json:"hasServices"` + + Enums orderedEnums `json:"enums"` + Extensions orderedExtensions `json:"extensions"` + Messages orderedMessages `json:"messages"` + Services orderedServices `json:"services"` } type FileExtension struct { - Name string `json:"extension_name"` - LongName string `json:"extension_long_name"` - FullName string `json:"extension_full_name"` - Description string `json:"extension_description"` - Label string `json:"extension_label"` - Type string `json:"extension_type"` - LongType string `json:"extension_long_type"` - FullType string `json:"extension_full_type"` - Number int `json:"extension_number"` - DefaultValue string `json:"extension_default_value"` - ContainingType string `json:"extension_containing_type"` - ContainingLongType string `json:"extension_containing_long_type"` - ContainingFullType string `json:"extension_containing_full_type"` + Name string `json:"name"` + LongName string `json:"longName"` + FullName string `json:"fullName"` + Description string `json:"description"` + Label string `json:"label"` + Type string `json:"type"` + LongType string `json:"longType"` + FullType string `json:"fullType"` + Number int `json:"number"` + DefaultValue string `json:"defaultValue"` + ContainingType string `json:"containingType"` + ContainingLongType string `json:"containingLongType"` + ContainingFullType string `json:"containingFullType"` } type Message struct { - Name string `json:"message_name"` - LongName string `json:"message_long_name"` - FullName string `json:"message_full_name"` - Description string `json:"message_description"` + Name string `json:"name"` + LongName string `json:"longName"` + FullName string `json:"fullName"` + Description string `json:"description"` - HasExtensions bool `json:"message_has_extensions"` - HasFields bool `json:"message_has_extensions"` + HasExtensions bool `json:"hasExtensions"` + HasFields bool `json:"hasFields"` - Extensions []*MessageExtension `json:"message_extensions"` - Fields []*MessageField `json:"message_fields"` + Extensions []*MessageExtension `json:"extensions"` + Fields []*MessageField `json:"fields"` } type MessageField struct { - Name string `json:"field_name"` - Description string `json:"field_description"` - Label string `json:"field_label"` - Type string `json:"field_type"` - LongType string `json:"field_long_type"` - FullType string `json:"field_full_type"` - DefaultValue string `json:"field_default_value"` + Name string `json:"name"` + Description string `json:"description"` + Label string `json:"label"` + Type string `json:"type"` + LongType string `json:"longType"` + FullType string `json:"fullType"` + DefaultValue string `json:"defaultValue"` } type MessageExtension struct { FileExtension - ScopeType string `json:"extension_scope_type"` - ScopeLongType string `json:"extension_scope_long_type"` - ScopeFullType string `json:"extension_scope_full_type"` + ScopeType string `json:"scopeType"` + ScopeLongType string `json:"scopeLongType"` + ScopeFullType string `json:"scopeFullType"` } type Enum struct { - Name string `json:"enum_name"` - LongName string `json:"enum_long_name"` - FullName string `json:"enum_full_name"` - Description string `json:"enum_description"` - Values []*EnumValue `json:"enum_values"` + Name string `json:"name"` + LongName string `json:"longName"` + FullName string `json:"fullName"` + Description string `json:"description"` + Values []*EnumValue `json:"values"` } type EnumValue struct { - Name string `json:"value_name"` - Number string `json:"value_number"` - Description string `json:"value_description"` + Name string `json:"name"` + Number string `json:"number"` + Description string `json:"description"` } type Service struct { - Name string `json:"service_name"` - LongName string `json:"service_long_name"` - FullName string `json:"service_full_name"` - Description string `json:"service_description"` - Methods []*ServiceMethod `json:"service_methods"` + Name string `json:"name"` + LongName string `json:"longName"` + FullName string `json:"fullName"` + Description string `json:"description"` + Methods []*ServiceMethod `json:"methods"` } type ServiceMethod struct { - Name string `json:"method_name"` - Description string `json:"method_description"` - RequestType string `json:"method_request_type"` - RequestLongType string `json:"method_request_long_type"` - RequestFullType string `json:"method_request_full_type"` - ResponseType string `json:"method_response_type"` - ResponseLongType string `json:"method_response_long_type"` - ResponseFullType string `json:"method_response_full_type"` + Name string `json:"name"` + Description string `json:"description"` + RequestType string `json:"requestType"` + RequestLongType string `json:"requestLongType"` + RequestFullType string `json:"requestFullType"` + ResponseType string `json:"responseType"` + ResponseLongType string `json:"responseLongType"` + ResponseFullType string `json:"responseFullType"` } type ScalarValue struct { - ProtoType string `json:"scalar_value_proto_type"` - Notes string `json:"scalar_value_notes"` - CppType string `json:"scalar_value_cpp_type"` - CSharp string `json:"scalar_value_cs_type"` - GoType string `json:"scalar_value_go_type"` - JavaType string `json:"scalar_value_java_type"` - PhpType string `json:"scalar_value_php_type"` - PythonType string `json:"scalar_value_python_type"` - RubyType string `json:"scalar_value_ruby_type"` + ProtoType string `json:"protoType"` + Notes string `json:"notes"` + CppType string `json:"cppType"` + CSharp string `json:"csType"` + GoType string `json:"goType"` + JavaType string `json:"javaType"` + PhpType string `json:"phpType"` + PythonType string `json:"pythonType"` + RubyType string `json:"rubyType"` } func parseEnum(pe *parser.Enum) *Enum { @@ -206,6 +210,8 @@ func parseMessage(pm *parser.Message) *Message { Description: pm.Comment, HasExtensions: len(pm.Extensions) > 0, HasFields: len(pm.Fields) > 0, + Extensions: make([]*MessageExtension, 0, len(pm.Extensions)), + Fields: make([]*MessageField, 0, len(pm.Fields)), } for _, ext := range pm.Extensions { From 4ffd3136b647e2f9638da1438156a35e41516a72 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Sat, 22 Jul 2017 14:51:33 -0400 Subject: [PATCH 27/50] Generate scalars from json resource --- build/cmd/resources/main.go | 2 +- examples/doc/example.json | 2 +- resources.go | 1 + resources/scalars.json | 167 +++++++++++++++++++++++++++++++++ template.go | 182 +++--------------------------------- 5 files changed, 181 insertions(+), 173 deletions(-) create mode 100644 resources/scalars.json diff --git a/build/cmd/resources/main.go b/build/cmd/resources/main.go index 2067dcdc..009e3e62 100644 --- a/build/cmd/resources/main.go +++ b/build/cmd/resources/main.go @@ -60,7 +60,7 @@ import ( w.WriteString("var embeddedResources = map[string]string{\n") for _, file := range files { - if path.Ext(file.Name()) != ".tmpl" { + if file.Name() != "scalars.json" && path.Ext(file.Name()) != ".tmpl" { continue } diff --git a/examples/doc/example.json b/examples/doc/example.json index 50ea8067..12cbdb95 100644 --- a/examples/doc/example.json +++ b/examples/doc/example.json @@ -556,7 +556,7 @@ "services": [] } ], - "scalar_value_types": [ + "scalarValueTypes": [ { "protoType": "double", "notes": "", diff --git a/resources.go b/resources.go index 95a327e2..10be0a44 100644 --- a/resources.go +++ b/resources.go @@ -13,6 +13,7 @@ var embeddedResources = map[string]string{ "docbook.tmpl": "H4sIAAAAAAAA/+xZ30/bOhR+719h5fEiEq64SFeTW6TBqmkChIDt3U1OW2uOndlOAUX93ycnIb8Tl7UUtvGCZJ/P58TH5/uOa/DpQ8jQCqSigo+df90jBwH3RUD5Yux8vZse/u+cTkaYSE19BpMRQlhTzWByLYUWvmDoXPhxCFwTTQXHXmYdIZQkkvAFIHdKGaj12ixV4BuUMReOksS9IiGs15W1ZnVEJEHuOShf0sisSl1U/F6CUmSRuy6dIxqMnSRxpzFjmWMnc1mNeCH4oiPqUFxjo3PkfiZqSoEFqpjHmswYoLkkIYwdwlgRsAiJfUaU4iRsRS8NKHPb+CDjYiFFHCFfMDV2/qs4RwibyQh8Y7yngV6OnX8cb2vEkXtiBx03IXoJJKjOIISluK/PIISBa/k4SXeLvWzQDbl7jGAYcUFmwIYhlZPsBGKv8Y3Ya20E65kIGusq9V2rBuvGKwU/9N2YUf4dmT/Ay4o2KTEVnVdRNsSegU2G/ZkVJlu2uJ0MyEr/HOYkZvobYbGJanCTfO4DSpKm3UsBSQI86Anayr1Jawqvn0c9+9jLGFHQ2ksJWFK46qEg7acHDdzI3O6JewVKQ4DKCBYOn+yDw2+D5UVOtmX6R6IsiKs4nIF8ZTHoqDJrjl5JENr+zgTXhHLKFw3PpeH5opMdy1+lOtirXXSqprJQeBzu7+qyK6VLk2yTt+N9yNuWumT29hvISZbvnUvJdrTcF6l6aJQP+tp6/UdGUd7mV8ghgxWw/j6NKZ8LGRI2yJb3Tv7eyd87+R/VyWu830R88hq5Bbmi/q89Qbx8D+/o35egl+JtvDG8uGBle0X2Tn8DP2JQGtml6wZUJLiCDaAvrk/5Ue5BnPL8NJQkn91aqp5y2nKfTT9bpt7ehaVhqEhJ54PorU8YkdlVOy20Ol3tl5T+K4qNhh32Extgt/Y2B1qnlJ+0G0mhRT8Tn+4SQpsE9gPODg6sTr6QFbGCrh/1UvA+WKPcWmxvc73sMmlB1Jnel5an1pM+z1foVBkP7cFohEmYFXUWRRt5M5nbCJhlrxfaYmuTqw2m1nna0eErlBxhr/j/xs8AAAD//2FyhLQRGQAA", "html.tmpl": "H4sIAAAAAAAA/8xabXPbuBH+7l+xx7TjXi4UJfklrkKpM3Wc6XQuaebsdK6fOhAJiZiAAEuAPrse/fcOwDeAIGXJkXoZfzCxAJ9d7D67WHAU/vD+H9d3//p8A4lM6eLkJCz/A4QJRrF6AAglkRQvPudc8ohTeM+jIsVMIkk4C4NytlyZYokgSlAusJx7X+4++FdeNUUJ+wo5pnNPyEeKRYKx9EA+ZnjuSfwgg0gID5Icr+ZeImU2C4IVZ1KM1pyvKUYZEaOIp2rZX1YoJfRx/mVZMFnMzsfjN2/H4zfn4zGRiJLICyqdWlP5DLDk8SM8VQOA30gskxlcjnH6rhGmKF8TNoMJTgEVkrczEac8n8Gr6XTaCpWBfmnMDLzSHO8NCMSEL3BOVu3SDMUxYWt/yaXk6QzOW7Wbk+ohmRj2aezfMFkncgaM5ymiLdqS5zHOG7BJ9gCCUxLDK4TQsNLx6AI/uGqnhtpDIBt+HF3gFMauyrPfZafI0Ko458c44rnmsdLMsBvvi8u3eHrhIEm0pNhl02Q8/mOHHoL8F8/gypRXe4o4pSgTeAb1k6tGZeGQq96OxwYmir6uc16w2K9NjyP152LqRJD5jMnEjxJC4z/he8x+NEnggq2W6s8Fix3uWEGKosgJUhUdmPZESMaQdYNEWIyZ1EnpMszlloIw9jb5cQhv/A6C1/CJQykAzmBFciEhA8IUzOugix28hjsdeb6CFcE0Fu2ikRb4JTNk3DFBvfpBLWhfMFhjFoPn0KYV2t1jhr8Z7KwC+xktMe1Bu9wH7LwCe49FlJNMpVUPpFlXex2LHyRmgnBmOrcRbnPwTb1oV79sRX2Jo7cC1s7+KxKHAawd/qlIlzjvgbzYF/HiQCFkRQr3iBZYjMwgsiLdFr9PKN3dMQNY0+d8shfa2WH8ISJEUV56RPc8llvKWV/P+nq2NiU3aldSlf2zns7B1BVxJrFqnFoNrySPfCVHhOEcCmrAUiKkrxslrbp7DtYHK8WrbgmmhGG/tmpinXA91bm1BBZACSys09g62Jacxn1b/EAoBnUiEraGmNxbtZcqW8qpZ47lmIiMosdZeYjv3WrUeztXnY3b4fQZ1NNhdf1sG+VHmNLtmE4vgyhZsxnkyoc74hrsSTCcfjx9A6c3p4BYDKe/nsISxWss9GGYYLjj14bD9VyPp0eXJkUadtjixijCNImWlEdf350MMMt+19xrhJnE+bvnWWT1YpeKDE6jd/XnJTq/2t5QrVbj6Mp4t6G57mfUpaF88q086WmL7G6qoV6OYlIIlWYPdvDDoLrKlKMffB++CJxDVAjJU7i+vQXff8FFq10xUlJ9bwqD8uqnHlWrWCtNJkDiuaeve97gbTCZNOuni6YmXVc1KQySaT2vElgDmrXJq29rYUHr2UYG8PSUI7bGMFKlQGw2zYSa+oPKj38zdYbM5jBSh4m1IqRkYQwBQlS54dXTU7XcWzSPYYA6ywtqCwx7PmIh0Lpj0oDaHuUfCkprA0KRIQYRRULMPZ1m3uJjGCipMu5nztYDBpZMcdU9PWEWO5Y1tt+wIj2W4TdHNbxpFF9mfUuYzcZvu87+nfxa7UQxz6f4HtO23RSH2tEtzu9JdDQa3bbROEAkwsBOCPu97hvK/tZYt+XxFrdlk/RP3SSpplu71URtNYZBTO6rSjJQFLYXBF1+Ku+Y56pRbMJkqktQf3FIpsZ2qqJ4xzPDo5WNtTUZjIwuctOcvttqSJic1SaYse1kU3Jmun1Ij5ojKxj9DQl9EbVJFpYdZ+OR5orndYqgbD8MmtJ8Ecp4oYHDQMZ6pGLYDPQNsxkZFpayQOYdRUGPplCWJ1KXnA0BnH219vUlj4zNkEpnX/UiJ8vU1oxIlMOSrsMoarHywjO6MrXQCmEVufd4hQoqdYJsNtVoBnq1OVOlXhhkA+a43h7KcMffYaBZ4ea5y7KB8hwuLd028TqX073I1+jrJ6C6dTeD8nZ4ZDpuP5++C0paKNdlF0bYuoPXTuxF9tLJe7O9j+zwnbHdHnUrebejOkgZb71mJUzz4cKzs6qPrWWeKH0vTYSeNOhLgsYTOkYu/XvJvwP1dyLWAK2GCOLSwyWHQ40OMRwibCuBLRsGG9CBJtNkyO51cxsXjlgz96XKlmr5LXT51jp5rCr5LVQ+bIU8SgIMX2iGi+H/uxB+xDLhMVj18Bf8nwILCVYa/IJFxpnAtvTQCVCac0T2V3vr0LaSvigXasc4kKV4Z8zfp1pDl7j1jW/X22kyrX8hYTJx6HN++22rw4uaj6Ms55LbJPvEpdJUja5/+sme/ju6R7bk86NMODNkhsM6jOyysc1avYPuBTGv01Z/+6vDfdLDSmOBG8OauWpjW+avs+wZBLX3Z5aUzuhfZDPJZpHFIIM9YVCKw6D6vcz/AgAA///XYUKYQSMAAA==", "markdown.tmpl": "H4sIAAAAAAAA/+RWTW+bQBC98yumpocmEfY9sn1o0qiqkihKol6iqlnbYxtpvUvZxWrE8t+r/WBZMNSWIvVSLjD7Mbx57+1ADA85l3zJKVzzZbFDJolMOYumBBjZ4WwkeTaazKMojuGZLCgCX8MVZxKZFFFZ5oRtEMY3KUVRVVFZflynFH/qvXA5g/E92WFVRefwUpYu+PEp9s9nEYBPcodCkI3JAwBg99xytgn33RSUhnuRrcx6n+ULK3bvTfFbIhMpZ0EeXWFCcY8UmmmTr6m4qhL0cwO5nzDfp8tOkcfR1fdzeHlaEkpy+E5ogfD8lqGGIcxgsteDidSDZ9Hp8ni1PRat+TQDQtMNm43ydLOVo/mUwDbH9WwUa1fMn3k2nZD5dJJZf/jNUVmOr1Es8zTTZqqqAEogcvjWpmrnNpOuUa43ZbqG8VciblKkK51QgXkEZWgBBbdkgRQUBDtBRQoSfYG9Qzt0F6iQPZ0fEiOZasoE5e2l3xcKaOMzu9qgMMvLkvFFDu1KbB3XuCYFlUbVqgIXXoKpO5xyTjAA295oGGkZWDWGbZj5TIS+3Re7BeZDDB2y5EkaZqt5dy9jLcJsrNsJSVnKNt0ZC+/fUGenph+SBJCtYOd8CkkyD+xbd5d3eleBnjomwAlsm8oGmT7GX8DEIQOoS63L7zNWwMFAD/R89HbP/9eZrwfWfB30ZiNIi/+OL4PPynFr/sWWdyi3fFW78xF/FShkLc0jiowzgXU8KE1XhW7YjVX4fdAAhrutg9Rtum643Xvteov5pA3HDsmBJMLRXqvhzlIcw+FXWtM7zvTvVs3fPZcoQMHVxUU99I3sSf388Ca3nLmon96ARzg8Gj1nwMJq6A3NYn4FLRkjmMyhPeS00JD98ciycE5jD2OLvx7x7PwJAAD//3dr9Ll0CgAA", + "scalars.json": "H4sIAAAAAAAA/9yXzW4aMRDH7zzFiFMqBZDSlEa9JZGQOOQEOUWp5GVnvW6NTewxzaqq1HfoG/ZJqt0F1gYvpChVk9zQfHg9v/nPWNx1AL53AAC6C6NJT4sFdj9BN9Uukdg9rV1KE9rSvDbMFot45MzG7XxzciY1o+H52vGFLVk8ZZEvgpyNvaBcq6jLuKRYO0aVowPw47SlxiB1X4lBYFNhYN4q8P1ZrMCwjn9dn1DeNTb13Vq0sGRGsERiT6LilAOqmU6F4n0YK8wyMROoCDJtNh5QyBmJJYJy8wSNhd8/f4HIoNDOQCZQpiAsSPEVZQGkIWdl7DppyaRDewrOItjqYiCUJWRpPwI8uHkDXKgI7iDWh+1Fe6iFIuRo4rC9FB/1leDKzUEbGInH8tcJs2DwwQmD6btDPWi0/sJ6MDw/0IPm5k0PpFY82oT4SPvhu10YWDLCC9huxsBP3+3IXvLuGPlHOLgWMbqoGt0zy/EAgaM06Y4RZQuZmERcXCPu/4jkKET2qN05EVxhCkJRPWt9mOZoEebaIGxGWhZ1Cu6OM+VMgUHuJDNQXcG+7fVoj9qPz855eN7G+VWvwEw8YhqR8aX8xgoLWflqJAWh7cNNgK4G5FbvdLZ6OYAZBJ0RKuAGGaGp484+n128vM35zEqtWEakumKJgud0CGb54B6G+WH4epft/mF/uh7f9tL7Cy29wa2UaC2f8q/Lj2vK9K08eqZfZWlHpiKFbnuCCrecfoFT4/BaMmsHIyZt/XN/twOSTa+hdsDcWQJWd36mFTGh4HY66l2sXq+01NjHXiIILifX4zEQPlJMF+GHGmKhnbdczKc2CZuft3wiZGbJDJwS5ZVj3Ooz4aQqbf98VMrfAXbDig0fpgpgJhFkmCnA4oNDNSvXafvUtNG5KggnLYTu7svjYoR2s55OaR+dqsO9i6vxtEbUue/8CQAA//8z+wC/ohEAAA==", } func fetchResource(name string) ([]byte, error) { diff --git a/resources/scalars.json b/resources/scalars.json new file mode 100644 index 00000000..17ffe9d1 --- /dev/null +++ b/resources/scalars.json @@ -0,0 +1,167 @@ +[ + { + "protoType": "double", + "notes": "", + "cppType": "double", + "csType": "double", + "goType": "float64", + "javaType": "double", + "phpType": "float", + "pythonType": "float", + "rubyType": "Float" + }, + { + "protoType": "float", + "notes": "", + "cppType": "float", + "csType": "float", + "goType": "float32", + "javaType": "float", + "phpType": "float", + "pythonType": "float", + "rubyType": "Float" + }, + { + "protoType": "int32", + "notes": "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.", + "cppType": "int32", + "csType": "int", + "goType": "int32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "int64", + "notes": "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.", + "cppType": "int64", + "csType": "long", + "goType": "int64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum" + }, + { + "protoType": "uint32", + "notes": "Uses variable-length encoding.", + "cppType": "uint32", + "csType": "uint", + "goType": "uint32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int/long", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "uint64", + "notes": "Uses variable-length encoding.", + "cppType": "uint64", + "csType": "ulong", + "goType": "uint64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "sint32", + "notes": "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.", + "cppType": "int32", + "csType": "int", + "goType": "int32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "sint64", + "notes": "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.", + "cppType": "int64", + "csType": "long", + "goType": "int64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum" + }, + { + "protoType": "fixed32", + "notes": "Always four bytes. More efficient than uint32 if values are often greater than 2^28.", + "cppType": "uint32", + "csType": "uint", + "goType": "uint32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "fixed64", + "notes": "Always eight bytes. More efficient than uint64 if values are often greater than 2^56.", + "cppType": "uint64", + "csType": "ulong", + "goType": "uint64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum" + }, + { + "protoType": "sfixed32", + "notes": "Always four bytes.", + "cppType": "int32", + "csType": "int", + "goType": "int32", + "javaType": "int", + "phpType": "integer", + "pythonType": "int", + "rubyType": "Bignum or Fixnum (as required)" + }, + { + "protoType": "sfixed64", + "notes": "Always eight bytes.", + "cppType": "int64", + "csType": "long", + "goType": "int64", + "javaType": "long", + "phpType": "integer/string", + "pythonType": "int/long", + "rubyType": "Bignum" + }, + { + "protoType": "bool", + "notes": "", + "cppType": "bool", + "csType": "bool", + "goType": "bool", + "javaType": "boolean", + "phpType": "boolean", + "pythonType": "boolean", + "rubyType": "TrueClass/FalseClass" + }, + { + "protoType": "string", + "notes": "A string must always contain UTF-8 encoded or 7-bit ASCII text.", + "cppType": "string", + "csType": "string", + "goType": "string", + "javaType": "String", + "phpType": "string", + "pythonType": "str/unicode", + "rubyType": "String (UTF-8)" + }, + { + "protoType": "bytes", + "notes": "May contain any arbitrary sequence of bytes.", + "cppType": "string", + "csType": "ByteString", + "goType": "[]byte", + "javaType": "ByteString", + "phpType": "string", + "pythonType": "str", + "rubyType": "String (ASCII-8BIT)" + } +] diff --git a/template.go b/template.go index accda58a..0ec49567 100644 --- a/template.go +++ b/template.go @@ -1,6 +1,7 @@ package protoc_gen_doc import ( + "encoding/json" "fmt" "github.com/pseudomuto/protoc-gen-doc/parser" "sort" @@ -9,7 +10,7 @@ import ( type Template struct { Files []*File `json:"files"` - Scalars []*ScalarValue `json:"scalar_value_types"` + Scalars []*ScalarValue `json:"scalarValueTypes"` } func NewTemplate(pr *parser.ParseResult) *Template { @@ -57,6 +58,15 @@ func NewTemplate(pr *parser.ParseResult) *Template { return &Template{Files: files, Scalars: makeScalars()} } +func makeScalars() []*ScalarValue { + data, _ := fetchResource("scalars.json") + + scalars := make([]*ScalarValue, 0) + json.Unmarshal(data, &scalars) + + return scalars +} + type File struct { Name string `json:"name"` Description string `json:"description"` @@ -279,176 +289,6 @@ func baseName(name string) string { return parts[len(parts)-1] } -func makeScalars() []*ScalarValue { - return []*ScalarValue{ - { - "double", - "", - "double", - "double", - "float64", - "double", - "float", - "float", - "Float", - }, - { - "float", - "", - "float", - "float", - "float32", - "float", - "float", - "float", - "Float", - }, - { - "int32", - "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.", - "int32", - "int", - "int32", - "int", - "integer", - "int", - "Bignum or Fixnum (as required)", - }, - { - "int64", - "Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.", - "int64", - "long", - "int64", - "long", - "integer/string", - "int/long", - "Bignum", - }, - { - "uint32", - "Uses variable-length encoding.", - "uint32", - "uint", - "uint32", - "int", - "integer", - "int/long", - "Bignum or Fixnum (as required)", - }, - { - "uint64", - "Uses variable-length encoding.", - "uint64", - "ulong", - "uint64", - "long", - "integer/string", - "int/long", - "Bignum or Fixnum (as required)", - }, - { - "sint32", - "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.", - "int32", - "int", - "int32", - "int", - "integer", - "int", - "Bignum or Fixnum (as required)", - }, - { - "sint64", - "Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.", - "int64", - "long", - "int64", - "long", - "integer/string", - "int/long", - "Bignum", - }, - { - "fixed32", - "Always four bytes. More efficient than uint32 if values are often greater than 2^28.", - "uint32", - "uint", - "uint32", - "int", - "integer", - "int", - "Bignum or Fixnum (as required)", - }, - { - "fixed64", - "Always eight bytes. More efficient than uint64 if values are often greater than 2^56.", - "uint64", - "ulong", - "uint64", - "long", - "integer/string", - "int/long", - "Bignum", - }, - { - "sfixed32", - "Always four bytes.", - "int32", - "int", - "int32", - "int", - "integer", - "int", - "Bignum or Fixnum (as required)", - }, - { - "sfixed64", - "Always eight bytes.", - "int64", - "long", - "int64", - "long", - "integer/string", - "int/long", - "Bignum", - }, - { - "bool", - "", - "bool", - "bool", - "bool", - "boolean", - "boolean", - "boolean", - "TrueClass/FalseClass", - }, - { - "string", - "A string must always contain UTF-8 encoded or 7-bit ASCII text.", - "string", - "string", - "string", - "String", - "string", - "str/unicode", - "String (UTF-8)", - }, - { - "bytes", - "May contain any arbitrary sequence of bytes.", - "string", - "ByteString", - "[]byte", - "ByteString", - "string", - "str", - "String (ASCII-8BIT)", - }, - } -} - type orderedEnums []*Enum func (oe orderedEnums) Len() int { return len(oe) } From eda962b18f65c363850ac4c2d6a6b497fc7e491f Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Sat, 22 Jul 2017 21:57:57 -0400 Subject: [PATCH 28/50] First stab at top-level godoc --- build/cmd/resources/main.go | 1 + cmd/protoc-gen-doc/main.go | 13 +++++++++++++ doc.go | 15 +++++++++++++++ filters.go | 3 +++ plugin.go | 10 +++++++++- renderer.go | 13 +++++++++++++ resources.go | 1 + template.go | 30 +++++++++++++++++++++++++++++- test/protoc_stubs.go | 3 +++ 9 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 doc.go diff --git a/build/cmd/resources/main.go b/build/cmd/resources/main.go index 009e3e62..92cab5fd 100644 --- a/build/cmd/resources/main.go +++ b/build/cmd/resources/main.go @@ -45,6 +45,7 @@ func main() { defer w.Flush() w.WriteString(fmt.Sprintf(`// AUTOGENERATED CODE. DO NOT EDIT. + package %s import ( diff --git a/cmd/protoc-gen-doc/main.go b/cmd/protoc-gen-doc/main.go index 20eecea9..1d330549 100644 --- a/cmd/protoc-gen-doc/main.go +++ b/cmd/protoc-gen-doc/main.go @@ -1,3 +1,16 @@ +// protoc-gen-doc is used to generate documentation from comments in your proto files. +// +// It is a protoc plugin, and can be invoked by passing `--doc_out` and `--doc_opt` arguments to protoc. +// +// Example: generate HTML documentation +// +// protoc --doc_out=. --doc_opt=html,index.html protos/*.proto +// +// Example: use a custom template +// +// protoc --doc_out=. --doc_opt=custom.tmpl,docs.txt protos/*.proto +// +// For more details, check out the README at https://github.com/pseudomuto/protoc-gen-doc package main import ( diff --git a/doc.go b/doc.go new file mode 100644 index 00000000..528817a1 --- /dev/null +++ b/doc.go @@ -0,0 +1,15 @@ +// protoc-gen-doc is a protoc plugin for generating documentation from your proto files. +// +// Typically this will not be used as a library, though nothing prevents that. Normally it'll be invoked by passing +// `--doc_out` and `--doc_opt` values to protoc. +// +// Example: generate HTML documentation +// +// protoc --doc_out=. --doc_opt=html,index.html protos/*.proto +// +// Example: use a custom template +// +// protoc --doc_out=. --doc_opt=custom.tmpl,docs.txt protos/*.proto +// +// For more details, check out the README at https://github.com/pseudomuto/protoc-gen-doc +package protoc_gen_doc diff --git a/filters.go b/filters.go index 8ea370c8..e9ab51d7 100644 --- a/filters.go +++ b/filters.go @@ -9,16 +9,19 @@ import ( var paraPattern = regexp.MustCompile("(\\n|\\r|\\r\\n)\\s*") +// PFilter splits the content by new lines and wraps each one in a

tag. func PFilter(content string) template.HTML { paragraphs := paraPattern.Split(content, -1) return template.HTML(fmt.Sprintf("

%s

", strings.Join(paragraphs, "

"))) } +// ParaFilter splits the content by new lines and wraps each one in a tag. func ParaFilter(content string) string { paragraphs := paraPattern.Split(content, -1) return fmt.Sprintf("%s", strings.Join(paragraphs, "")) } +// NoBrFilter removes CR and LF from content func NoBrFilter(content string) string { withoutCR := strings.Replace(content, "\r", "", -1) withoutLF := strings.Replace(withoutCR, "\n", "", -1) diff --git a/plugin.go b/plugin.go index 01b5ec38..162dd24f 100644 --- a/plugin.go +++ b/plugin.go @@ -10,13 +10,19 @@ import ( "strings" ) -// , +// PluginOptions encapsulates options for the plugin. The type of renderer, template file, and the name of the output +// file are included. type PluginOptions struct { Type RenderType TemplateFile string OutputFile string } +// ParseOptions parses plugin options from a CodeGeneratorRequest. It does this by splitting the `Parameter` field from +// the request object and parsing out the type of renderer to use and the name of the file to be generated. +// +// The parameter (`--doc_opt`) must be of the format ,. The file will be written to the +// directory specified with the `--doc_out` argument to protoc. func ParseOptions(req *plugin_go.CodeGeneratorRequest) (*PluginOptions, error) { options := &PluginOptions{ Type: RenderTypeHtml, @@ -50,6 +56,8 @@ func ParseOptions(req *plugin_go.CodeGeneratorRequest) (*PluginOptions, error) { return options, nil } +// RunPlugin compiles the documentation and generates the CodeGeneratorResponse to send back to protoc. It does this +// by rendering a template based on the options parsed from the CodeGeneratorRequest. func RunPlugin(request *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGeneratorResponse, error) { result := parser.ParseCodeRequest(request) template := NewTemplate(result) diff --git a/renderer.go b/renderer.go index 24f65d87..a8a0f506 100644 --- a/renderer.go +++ b/renderer.go @@ -8,8 +8,10 @@ import ( text_template "text/template" ) +// An "Enum" for which type of renderer to use. type RenderType int8 +// Available render types. const ( _ RenderType = iota RenderTypeDocBook @@ -18,6 +20,8 @@ const ( RenderTypeMarkdown ) +// NewRenderType creates a RenderType from the supplied string. If the type is not known, (0, error) is returned. It is +// assumed (by the plugin) that invalid render type simply means that the path to a custom template was supplied. func NewRenderType(renderType string) (RenderType, error) { switch renderType { case "docbook": @@ -74,10 +78,19 @@ var funcMap = map[string]interface{}{ "nobr": NoBrFilter, } +// Processor is an interface that is satisfied by all built-in processors (text, html, and json). type Processor interface { Apply(template *Template) ([]byte, error) } +// RenderTemplate renders the template based on the render type. It supports overriding the default input templates by +// supplying a non-empty string as the last parameter. +// +// Example: generating an HTML template (assuming you've got a Template object) +// data, err := RenderTemplate(RenderTypeHtml, &template, "") +// +// Example: generating a custom template (assuming you've got a Template object) +// data, err := RenderTemplate(RenderTypeHtml, &template, "{{range .Files}}{{.Name}}{{end}}") func RenderTemplate(kind RenderType, template *Template, inputTemplate string) ([]byte, error) { if inputTemplate != "" { processor := &textRenderer{inputTemplate} diff --git a/resources.go b/resources.go index 10be0a44..3138e5d9 100644 --- a/resources.go +++ b/resources.go @@ -1,4 +1,5 @@ // AUTOGENERATED CODE. DO NOT EDIT. + package protoc_gen_doc import ( diff --git a/template.go b/template.go index 0ec49567..6c4424c8 100644 --- a/template.go +++ b/template.go @@ -8,11 +8,16 @@ import ( "strings" ) +// Template is a type for encapsulating all the parsed files, messages, fields, enums, services, extensions, etc. into +// an object that will be supplied to a go template. type Template struct { - Files []*File `json:"files"` + // The files that were parsed + Files []*File `json:"files"` + // Details about the scalar values and their respective types in supported languages. Scalars []*ScalarValue `json:"scalarValueTypes"` } +// NewTemplate creates a Template object from the ParseResult. func NewTemplate(pr *parser.ParseResult) *Template { files := make([]*File, 0, len(pr.Files)) @@ -67,6 +72,11 @@ func makeScalars() []*ScalarValue { return scalars } +// File wraps all the relevant parsed info about a proto file. File objects guarantee that their top-level enums, +// extensions, messages, and services are sorted alphabetically based on their "long name". Other values (enum values, +// fields, service methods) will be in the order that they're defined within their respective proto files. +// +// In the case of proto3 files, HasExtensions will always be false, and Extensions will be empty. type File struct { Name string `json:"name"` Description string `json:"description"` @@ -83,6 +93,7 @@ type File struct { Services orderedServices `json:"services"` } +// FileExtension contains details about top-level extensions within a proto(2) file. type FileExtension struct { Name string `json:"name"` LongName string `json:"longName"` @@ -99,6 +110,9 @@ type FileExtension struct { ContainingFullType string `json:"containingFullType"` } +// Message contains details about a protobuf message. +// +// In the case of proto3 files, HasExtensions will always be false, and Extensions will be empty. type Message struct { Name string `json:"name"` LongName string `json:"longName"` @@ -112,6 +126,10 @@ type Message struct { Fields []*MessageField `json:"fields"` } +// MessageField contains details about an individual field within a message. +// +// In the case of proto3 files, DefaultValue will always be empty. Similarly, label will be empty unless the field is +// repeated (in which case it'll be "repeated"). type MessageField struct { Name string `json:"name"` Description string `json:"description"` @@ -122,6 +140,7 @@ type MessageField struct { DefaultValue string `json:"defaultValue"` } +// MessageExtension contains details about message-scoped extensions in proto(2) files. type MessageExtension struct { FileExtension @@ -130,6 +149,7 @@ type MessageExtension struct { ScopeFullType string `json:"scopeFullType"` } +// Enum contains details about enumerations. These can be either top level enums, or nested (defined within a message). type Enum struct { Name string `json:"name"` LongName string `json:"longName"` @@ -138,12 +158,14 @@ type Enum struct { Values []*EnumValue `json:"values"` } +// EnumValue contains details about an individual value within an enumeration. type EnumValue struct { Name string `json:"name"` Number string `json:"number"` Description string `json:"description"` } +// Service contains details about a service definition within a proto file. type Service struct { Name string `json:"name"` LongName string `json:"longName"` @@ -152,6 +174,7 @@ type Service struct { Methods []*ServiceMethod `json:"methods"` } +// ServiceMethod contains details about an individual method within a service. type ServiceMethod struct { Name string `json:"name"` Description string `json:"description"` @@ -163,6 +186,11 @@ type ServiceMethod struct { ResponseFullType string `json:"responseFullType"` } +// ScalarValue contains information about scalar value types in protobuf. The common use case for this type is to know +// which language specific type maps to the protobuf type. +// +// For example, the protobuf type `int64` maps to `long` in C#, and `Bignum` in Ruby. For the full list, take a look at +// https://developers.google.com/protocol-buffers/docs/proto3#scalar type ScalarValue struct { ProtoType string `json:"protoType"` Notes string `json:"notes"` diff --git a/test/protoc_stubs.go b/test/protoc_stubs.go index 25db8196..e310e77f 100644 --- a/test/protoc_stubs.go +++ b/test/protoc_stubs.go @@ -1,3 +1,4 @@ +// Utilities used for testing purposes only. package test import ( @@ -8,6 +9,8 @@ import ( "runtime" ) +// MakeCodeGeneratorRequest loads the request fixture from disk and creates a CodeGenerator request from it. This is +// useful for testing methods/functions that work with the input from protoc. func MakeCodeGeneratorRequest() (*plugin_go.CodeGeneratorRequest, error) { _, filename, _, _ := runtime.Caller(0) filepath := path.Join(path.Dir(filename), "../test/fixtures/generator_request.dat") From 061a13d74e7a89d759df19ceab3f89a553c3fc27 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Tue, 25 Jul 2017 15:31:32 -0400 Subject: [PATCH 29/50] Add make dist for building dists for windows, linux, and osx --- .gitignore | 1 + Makefile | 5 ++++- README.md | 3 ++- dist.sh | 47 +++++++++++++++++++++++++++++++++++++++++++++++ version.go | 3 +++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100755 dist.sh create mode 100644 version.go diff --git a/.gitignore b/.gitignore index 6e6fd594..6d03931a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/dist /gen_fixtures /protoc-gen-doc /test/*.dat diff --git a/Makefile b/Makefile index c94cbaa3..b6b5a91c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: bench test build +.PHONY: bench test build dist generate: @go generate @@ -19,3 +19,6 @@ examples: build @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=json,example.json examples/proto/*.proto @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=markdown,example.md examples/proto/*.proto @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=examples/templates/asciidoc.tmpl,example.txt examples/proto/*.proto + +dist: + @./dist.sh diff --git a/README.md b/README.md index eb4fdd29..5f469d35 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ The plugin executable must be in `PATH` for this to work. Alternatively, you can specify a pre-built/not in `PATH` binary using the `--plugin` option. - protoc --plugin=protoc-gen-doc=./protoc-gen-doc \ + protoc \ + --plugin=protoc-gen-doc=./protoc-gen-doc \ --doc_out=./doc \ --doc_opt=html,index.html \ proto/*.proto diff --git a/dist.sh b/dist.sh new file mode 100755 index 00000000..16b08edd --- /dev/null +++ b/dist.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -euo pipefail + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +package_dist() { + local build_dir="${1}" + local target="${2}" + + pushd "${build_dir}" >/dev/null + tar czf "${target}.tar.gz" "${target}" + mv "${target}.tar.gz" "${DIR}/dist" + popd >/dev/null +} + +build_dist() { + local os="${1}" + local version="${2}" + local go_version="${3}" + + local build=$(mktemp -d /tmp/protoc-gen-doc.XXXXXX) + local target="protoc-gen-doc-${version}.${os}-amd64.${go_version}" + + local ext="" + if [ "${os}" = "windows" ]; then ext=".exe"; fi + + echo -n "Building ${target}..." + GOOS="${os}" GOARCH=amd64 CGO_ENABLED=0 \ + go build -ldflags="-s -w" -o "${build}/${target}/protoc-gen-doc${ext}" ./cmd/... || exit 1 + + package_dist "${build}" "${target}" + echo "done." +} + +main() { + rm -rf "${DIR}/dist" + mkdir -p "${DIR}/dist" + + local app_version=$(cat ${DIR}/version.go | grep "const VERSION" | awk '{print $NF}' | sed 's/"//g') + local go_version=$(go version | awk '{print $3}') + + for target in windows linux darwin; do + build_dist "${target}" "${app_version}" "${go_version}" + done +} + +main "$@" diff --git a/version.go b/version.go new file mode 100644 index 00000000..1f3ed2e7 --- /dev/null +++ b/version.go @@ -0,0 +1,3 @@ +package protoc_gen_doc + +const VERSION = "1.0.0" From c84f823f2441f9eaeaf5bd1e1bdfc3198cff9a4c Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Wed, 26 Jul 2017 08:55:35 -0400 Subject: [PATCH 30/50] Safer handling of paths --- dist.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dist.sh b/dist.sh index 16b08edd..b122cc38 100755 --- a/dist.sh +++ b/dist.sh @@ -29,6 +29,7 @@ build_dist() { go build -ldflags="-s -w" -o "${build}/${target}/protoc-gen-doc${ext}" ./cmd/... || exit 1 package_dist "${build}" "${target}" + rm -rf "${build}" echo "done." } @@ -36,7 +37,7 @@ main() { rm -rf "${DIR}/dist" mkdir -p "${DIR}/dist" - local app_version=$(cat ${DIR}/version.go | grep "const VERSION" | awk '{print $NF}' | sed 's/"//g') + local app_version=$(grep "const VERSION" "${DIR}/version.go" | awk '{print $NF }' | tr -d '"') local go_version=$(go version | awk '{print $3}') for target in windows linux darwin; do From 02b964e7d089a543a01824f6bbfc9a62f5a35b89 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Wed, 26 Jul 2017 10:32:52 -0400 Subject: [PATCH 31/50] Make a docker image for running protoc-gen-doc --- .dockerignore | 2 ++ Dockerfile | 23 +++++++++++++++++++++++ Makefile | 5 ++++- dist.sh => script/dist.sh | 2 +- script/entrypoint.sh | 4 ++++ 5 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile rename dist.sh => script/dist.sh (95%) create mode 100755 script/entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..ce4f1fc3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +/ +!/script diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4b6d7299 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM debian:jessie-slim +LABEL maintainer="david.muto@gmail.com" version="1.0.0" + +WORKDIR / + +ADD https://github.com/google/protobuf/releases/download/v3.3.0/protoc-3.3.0-linux-x86_64.zip ./ +RUN apt-get -q -y update && \ + apt-get -q -y install unzip && \ + unzip protoc-3.3.0-linux-x86_64.zip -d ./usr/local && \ + rm protoc-3.3.0-linux-x86_64.zip && \ + apt-get purge -y unzip && \ + apt-get autoremove + +ADD script/entrypoint.sh ./ + +ADD dist/protoc-gen-doc-1.0.0.linux-amd64.go1.8.1.tar.gz ./ +RUN mv ./protoc-gen-doc-1.0.0.linux-amd64.go1.8.1/protoc-gen-doc /usr/local/bin && \ + rm -rf ./protoc-gen-doc-* + +VOLUME ["/out", "/protos"] + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["--doc_opt=html,index.html"] diff --git a/Makefile b/Makefile index b6b5a91c..56904e72 100644 --- a/Makefile +++ b/Makefile @@ -21,4 +21,7 @@ examples: build @protoc --plugin=protoc-gen-doc --doc_out=examples/doc --doc_opt=examples/templates/asciidoc.tmpl,example.txt examples/proto/*.proto dist: - @./dist.sh + @script/dist.sh + +docker: dist + @docker build -t protoc-gen-doc . diff --git a/dist.sh b/script/dist.sh similarity index 95% rename from dist.sh rename to script/dist.sh index b122cc38..fd015072 100755 --- a/dist.sh +++ b/script/dist.sh @@ -1,7 +1,7 @@ #!/bin/bash set -euo pipefail -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)" package_dist() { local build_dir="${1}" diff --git a/script/entrypoint.sh b/script/entrypoint.sh new file mode 100755 index 00000000..cee002b4 --- /dev/null +++ b/script/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail + +exec protoc --doc_out=/out "$@" protos/*.proto From a48d0a4c088bb69eccbf4615413d4664ecb8bc68 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Wed, 26 Jul 2017 10:49:20 -0400 Subject: [PATCH 32/50] Add documentation for running the container --- README.md | 93 ++++++++++++++++++++++++++++++-------------- script/entrypoint.sh | 1 + 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 5f469d35..4692bd5b 100644 --- a/README.md +++ b/README.md @@ -9,49 +9,48 @@ It supports proto2 and proto3, and can handle having both in the same context (s ## Installation -`go get -u github.com/pseudomuto/protoc-gen-doc` +There is a docker image available (`protoc-gen-doc`) that has everything you need to generate documentation from your +protos. -## Writing Documentation +If you'd like to install this locally, you can `go get` it. -Messages, Fields, Services (and their methods), Enums (and their values), Extensions, and Files can be documented. -Generally speaking, comments come in 2 forms: leading and trailing. +`go get -u github.com/pseudomuto/protoc-gen-doc` -**Leading comments** +## Invoking the Plugin -Leading comments can be used everywhere. +The plugin is invoked by passing the `--doc_out`, and `--doc_opt` options to the `protoc` compiler. The option has the +following format: -```protobuf -/** - * This is a leading comment for a message -*/ -message SomeMessage { - // this is another leading comment - string value = 1; -} -``` + --doc_opt=|, -**Trailing comments** +The format may be one of the built-in ones ( `docbook`, `html`, `markdown` or `json`) +or the name of a file containing a custom [Go template][gotemplate]. -Fields, Service Methods, Enum Values and Extensions support trailing comments. +### Using the Docker Image (Recommended) -```protobuf -enum MyEnum { - DEFAULT = 0; // the default value - OTHER = 1; // the other value -} -``` +The docker image has two volumes: `/out` and `/protos` which are the directory to write the documentation to and the +directory containing your proto files. -Check out the [example protos](examples/proto) to see all the options. +You could generate html docs for the examples by running the following: -## Invoking the Plugin +``` +docker run --rm \ + -v $(pwd)/examples/doc:/out \ + -v $(pwd)/examples/proto:/protos \ + protoc-gen-doc +``` -The plugin is invoked by passing the `--doc_out`, and `--doc_opt` options to the `protoc` compiler. The option has the -following format: +By default html documentation is generated in `/out/index.html`. This can be changed by passing the `--doc_opt` +parameter to the container. - --doc_opt=|, +For example, to generate markdown for the examples: -The format may be one of the built-in ones ( `docbook`, `html`, `markdown` or `json`) -or the name of a file containing a custom [Go template][gotemplate]. +``` +docker run --rm \ + -v $(pwd)/examples/doc:/out \ + -v $(pwd)/examples/proto:/protos \ + protoc-gen-doc --doc_opt=md,docs.md +``` ### Simple Usage @@ -81,6 +80,40 @@ For information about the available template arguments and functions, see [Custo to customize the look of the HTML output, put your CSS in `stylesheet.css` next to the output file and it will be picked up. +## Writing Documentation + +Messages, Fields, Services (and their methods), Enums (and their values), Extensions, and Files can be documented. +Generally speaking, comments come in 2 forms: leading and trailing. + +**Leading comments** + +Leading comments can be used everywhere. + +```protobuf +/** + * This is a leading comment for a message +*/ +message SomeMessage { + // this is another leading comment + string value = 1; +} +``` + +**Trailing comments** + +Fields, Service Methods, Enum Values and Extensions support trailing comments. + +```protobuf +enum MyEnum { + DEFAULT = 0; // the default value + OTHER = 1; // the other value +} +``` + +**File level comments should be leading comments on the syntax directive.** + +Check out the [example protos](examples/proto) to see all the options. + ## Output Example With the input `.proto` files diff --git a/script/entrypoint.sh b/script/entrypoint.sh index cee002b4..9a79e705 100755 --- a/script/entrypoint.sh +++ b/script/entrypoint.sh @@ -1,4 +1,5 @@ #!/bin/bash set -euo pipefail +# this is required because of the wildcard expansion. Passing protos/*.proto in CMD gets escaped -_-. exec protoc --doc_out=/out "$@" protos/*.proto From e0e5564aa2c7a622e20b4d8651502c0af1258b8e Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Wed, 26 Jul 2017 10:52:47 -0400 Subject: [PATCH 33/50] Simplify Dockerfile and make task --- Dockerfile | 5 +---- Makefile | 5 +++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4b6d7299..d33ced1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,12 +11,9 @@ RUN apt-get -q -y update && \ apt-get purge -y unzip && \ apt-get autoremove +ADD dist/protoc-gen-doc /usr/local/bin/ ADD script/entrypoint.sh ./ -ADD dist/protoc-gen-doc-1.0.0.linux-amd64.go1.8.1.tar.gz ./ -RUN mv ./protoc-gen-doc-1.0.0.linux-amd64.go1.8.1/protoc-gen-doc /usr/local/bin && \ - rm -rf ./protoc-gen-doc-* - VOLUME ["/out", "/protos"] ENTRYPOINT ["/entrypoint.sh"] diff --git a/Makefile b/Makefile index 56904e72..9403cc98 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: bench test build dist +.PHONY: bench test build dist docker generate: @go generate @@ -23,5 +23,6 @@ examples: build dist: @script/dist.sh -docker: dist +docker: + @GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/protoc-gen-doc ./cmd/... @docker build -t protoc-gen-doc . From 525992cbf2d75242e417709f8983d99ec8c9c0b5 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Wed, 26 Jul 2017 11:53:59 -0400 Subject: [PATCH 34/50] Have travis build and push docker image for tagged releases --- .travis.yml | 8 ++++++++ Makefile | 3 +-- script/push_to_docker.sh | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100755 script/push_to_docker.sh diff --git a/.travis.yml b/.travis.yml index 643f2481..b65e5699 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ env: - PROTOC_RELEASE="https://github.com/google/protobuf/releases/download/v3.2.0/protoc-3.2.0-linux-x86_64.zip" - PROTOC_TARGET="${HOME}/protoc" - PATH="${PROTOC_TARGET}/bin:${PATH}" + - secure: Tbet2rxD8QgjthAo+bxt41qbF2wUPTx0difGK5p4yQISK/njTuT5cqcxnOa4GIbyKtNtx0EgGnyVcQJiQkmZiF6Sabf0mtqU/CQ4PmVV76e9bHwA/CrTtudibMn16ozxuuxvhNxFOMQEhwcQOkW93M/Q9FZUEw9/CGpRGFfSzuA= cache: - "${HOME}/protoc" @@ -27,5 +28,12 @@ script: - make test - make bench +deploy: + provider: script + script: script/push_to_docker.sh + on: + tags: true + all_branches: true + notifications: email: false diff --git a/Makefile b/Makefile index 9403cc98..1070bea1 100644 --- a/Makefile +++ b/Makefile @@ -24,5 +24,4 @@ dist: @script/dist.sh docker: - @GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/protoc-gen-doc ./cmd/... - @docker build -t protoc-gen-doc . + @script/push_to_docker.sh diff --git a/script/push_to_docker.sh b/script/push_to_docker.sh new file mode 100755 index 00000000..396e2287 --- /dev/null +++ b/script/push_to_docker.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -euo pipefail + +build_binary() { + mkdir -p dist + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/protoc-gen-doc ./cmd/... +} + +build_and_tag_image() { + docker build -t "${1}" . + docker tag "${1}" "${2}:${3}" + docker tag "${1}" "${2}:latest" +} + +push_image() { + # credentials are encrypted in travis.yml + docker login -u "${DOCKER_HUB_USER}" -p "${DOCKER_HUB_PASSWORD}" + docker push "${1}" + docker push "${2}:${3}" + docker push "${2}:latest" +} + +main() { + local sha="${TRAVIS_COMMIT:-}" + if [ -z "${sha}" ]; then sha=$(git rev-parse HEAD); fi + + local repo="pseudomuto/protoc-gen-doc" + local version="$(grep "const VERSION" "version.go" | awk '{print $NF }' | tr -d '"')" + local git_tag="${repo}:${sha}" + + build_binary + build_and_tag_image "${git_tag}" "${repo}" "${version}" + + if [ -n "${DOCKER_HUB_USER:-}" ]; then + push_image "${git_tag}" "${repo}" "${version}" + fi +} + +main "$@" From 6d19e7d10584094549119a4a297469ee5957e795 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Wed, 26 Jul 2017 12:01:18 -0400 Subject: [PATCH 35/50] Remove version from docker file since it's tagged already --- Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index d33ced1e..e9006db3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM debian:jessie-slim -LABEL maintainer="david.muto@gmail.com" version="1.0.0" +LABEL maintainer="pseudomuto " protoc_version="3.3.0" WORKDIR / @@ -8,8 +8,9 @@ RUN apt-get -q -y update && \ apt-get -q -y install unzip && \ unzip protoc-3.3.0-linux-x86_64.zip -d ./usr/local && \ rm protoc-3.3.0-linux-x86_64.zip && \ - apt-get purge -y unzip && \ - apt-get autoremove + apt-get remove --purge -y unzip && \ + apt-get autoremove && \ + rm -rf /var/lib/apt/lists/* ADD dist/protoc-gen-doc /usr/local/bin/ ADD script/entrypoint.sh ./ From 8bd70c4c6ee3644b7587ee780c31a683329a212f Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Wed, 26 Jul 2017 14:13:23 -0400 Subject: [PATCH 36/50] Use the right docker image name in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4692bd5b..21e5bec0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It supports proto2 and proto3, and can handle having both in the same context (s ## Installation -There is a docker image available (`protoc-gen-doc`) that has everything you need to generate documentation from your +There is a docker image available (`pseudomuto/protoc-gen-doc`) that has everything you need to generate documentation from your protos. If you'd like to install this locally, you can `go get` it. @@ -37,7 +37,7 @@ You could generate html docs for the examples by running the following: docker run --rm \ -v $(pwd)/examples/doc:/out \ -v $(pwd)/examples/proto:/protos \ - protoc-gen-doc + pseudomuto/protoc-gen-doc ``` By default html documentation is generated in `/out/index.html`. This can be changed by passing the `--doc_opt` @@ -49,7 +49,7 @@ For example, to generate markdown for the examples: docker run --rm \ -v $(pwd)/examples/doc:/out \ -v $(pwd)/examples/proto:/protos \ - protoc-gen-doc --doc_opt=md,docs.md + pseudomuto/protoc-gen-doc --doc_opt=md,docs.md ``` ### Simple Usage From 0575b24f3cf83d6354ff6dce8f455965d9a14e4f Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Wed, 26 Jul 2017 20:43:32 -0400 Subject: [PATCH 37/50] Grammar is hard sometimes --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 21e5bec0..5eece33d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It supports proto2 and proto3, and can handle having both in the same context (s ## Installation -There is a docker image available (`pseudomuto/protoc-gen-doc`) that has everything you need to generate documentation from your +There is a Docker image available (`pseudomuto/protoc-gen-doc`) that has everything you need to generate documentation from your protos. If you'd like to install this locally, you can `go get` it. @@ -31,7 +31,7 @@ or the name of a file containing a custom [Go template][gotemplate]. The docker image has two volumes: `/out` and `/protos` which are the directory to write the documentation to and the directory containing your proto files. -You could generate html docs for the examples by running the following: +You could generate HTML docs for the examples by running the following: ``` docker run --rm \ @@ -40,10 +40,10 @@ docker run --rm \ pseudomuto/protoc-gen-doc ``` -By default html documentation is generated in `/out/index.html`. This can be changed by passing the `--doc_opt` +By default HTML documentation is generated in `/out/index.html`. This can be changed by passing the `--doc_opt` parameter to the container. -For example, to generate markdown for the examples: +For example, to generate Markdown for the examples: ``` docker run --rm \ From b9cc716ca4c906bdff7ecd31f30d0dcb1087416f Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Thu, 27 Jul 2017 10:37:07 -0400 Subject: [PATCH 38/50] Add support for using @exclude to exclude comments from docs --- build/cmd/gen_fixtures/main.go | 2 +- template.go | 24 ++++++++++++++++-------- template_test.go | 10 ++++++++++ test/fixtures/Vehicle.proto | 12 ++++++++++++ test/fixtures/generator_request.dat | Bin 7518 -> 7998 bytes 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/build/cmd/gen_fixtures/main.go b/build/cmd/gen_fixtures/main.go index 713bc2fd..1738f437 100644 --- a/build/cmd/gen_fixtures/main.go +++ b/build/cmd/gen_fixtures/main.go @@ -17,5 +17,5 @@ func main() { log.Fatalf("Could not read contents from stdin") } - ioutil.WriteFile("fixtures/generator_request.dat", data, 0666) + ioutil.WriteFile("test/fixtures/generator_request.dat", data, 0666) } diff --git a/template.go b/template.go index 6c4424c8..a36a3b80 100644 --- a/template.go +++ b/template.go @@ -24,7 +24,7 @@ func NewTemplate(pr *parser.ParseResult) *Template { for _, f := range pr.Files { file := &File{ Name: f.Name, - Description: f.Comment, + Description: description(f.Comment), Package: f.Package, HasEnums: len(f.Enums) > 0, HasExtensions: len(f.Extensions) > 0, @@ -208,14 +208,14 @@ func parseEnum(pe *parser.Enum) *Enum { Name: baseName(pe.Name), LongName: strings.TrimPrefix(pe.FullName(), pe.Package+"."), FullName: pe.FullName(), - Description: pe.Comment, + Description: description(pe.Comment), } for _, val := range pe.Values { enum.Values = append(enum.Values, &EnumValue{ Name: val.Name, Number: fmt.Sprint(val.Number), - Description: val.Comment, + Description: description(val.Comment), }) } @@ -227,7 +227,7 @@ func parseFileExtension(pe *parser.Extension) *FileExtension { Name: baseName(pe.Name), LongName: strings.TrimPrefix(pe.FullName(), pe.Package+"."), FullName: pe.FullName(), - Description: pe.Comment, + Description: description(pe.Comment), Label: pe.Label, Type: baseName(pe.Type), LongType: strings.TrimPrefix(pe.Type, pe.Package+"."), @@ -245,7 +245,7 @@ func parseMessage(pm *parser.Message) *Message { Name: baseName(pm.Name), LongName: pm.Name, FullName: pm.FullName(), - Description: pm.Comment, + Description: description(pm.Comment), HasExtensions: len(pm.Extensions) > 0, HasFields: len(pm.Fields) > 0, Extensions: make([]*MessageExtension, 0, len(pm.Extensions)), @@ -275,7 +275,7 @@ func parseMessageExtension(pe *parser.Extension) *MessageExtension { func parseMessageField(pf *parser.Field) *MessageField { return &MessageField{ Name: pf.Name, - Description: pf.Comment, + Description: description(pf.Comment), Label: pf.Label, Type: baseName(pf.Type), LongType: strings.TrimPrefix(pf.Type, pf.Package+"."), @@ -289,7 +289,7 @@ func parseService(ps *parser.Service) *Service { Name: ps.Name, LongName: ps.Name, FullName: fmt.Sprintf("%s.%s", ps.Package, ps.Name), - Description: ps.Comment, + Description: description(ps.Comment), } for _, sm := range ps.Methods { @@ -302,7 +302,7 @@ func parseService(ps *parser.Service) *Service { func parseServiceMethod(pm *parser.ServiceMethod) *ServiceMethod { return &ServiceMethod{ Name: pm.Name, - Description: pm.Comment, + Description: description(pm.Comment), RequestType: baseName(pm.RequestType), RequestLongType: strings.TrimPrefix(pm.RequestType, pm.Package+"."), RequestFullType: pm.RequestType, @@ -317,6 +317,14 @@ func baseName(name string) string { return parts[len(parts)-1] } +func description(comment string) string { + if strings.HasPrefix(comment, "@exclude") { + return "" + } + + return comment +} + type orderedEnums []*Enum func (oe orderedEnums) Len() int { return len(oe) } diff --git a/template_test.go b/template_test.go index dbefeb30..a9221cb7 100644 --- a/template_test.go +++ b/template_test.go @@ -188,6 +188,16 @@ func (assert *TemplateTest) TestServiceMethodProperties() { assert.Equal("com.example.Vehicle", method.ResponseFullType) } +func (assert *TemplateTest) TestExcludedComments() { + message := findMessage("ExcludedMessage", vehicleFile) + assert.Empty(message.Description) + assert.Empty(findField("name", message).Description) + assert.Empty(findField("value", message).Description) + + // just checking that it doesn't exclude everything + assert.Equal("the id of this message.", findField("id", message).Description) +} + func findService(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Service { for _, s := range f.Services { if s.Name == name { diff --git a/test/fixtures/Vehicle.proto b/test/fixtures/Vehicle.proto index cf971e70..f596c39f 100644 --- a/test/fixtures/Vehicle.proto +++ b/test/fixtures/Vehicle.proto @@ -47,6 +47,18 @@ message Model { message EmptyMessage { } +/** + * @exclude + * This comment won't be rendered + */ +message ExcludedMessage { + string id = 1; // the id of this message. + string name = 2; // @exclude the name of this message + + /* @exclude the value of this message. */ + int32 value = 3; +} + // The type of model. enum Type { COUPE = 0; // The type is coupe. diff --git a/test/fixtures/generator_request.dat b/test/fixtures/generator_request.dat index 33ecb2f2840e95f5691d758f71927cbf2e3120e3..41f53816b49bab1d6b3a9faee0c9e300f1f17068 100644 GIT binary patch delta 1741 zcmZ9LPj4Gl6vaJn-Z=Ji8YfeGJhsznnv}-sA5#jkD}!u9X;JG&wkxyA!hh-+yOEmM zvK>Gy$Y-eDmJK^3kPw75;v2AF!H!Q*0V{S$ocsL7g?jbQ+&jN>=bq=^D?e4&M1B#> z7jM3(IpI-u6V8j9YPUQ2i9NqDKEuV=0x~J87H&Hhl4UxS6+3@+PANU8sd_EWl z_J&|bzH4|!A^rp>+ZOy}a2&WyJyB;nC9F>d&rbu_#6#GcrH_ zE4g8v-4%?RHs6oDYvgWT7hXMBx(ToI>}c){C&$OZ$-?_|a*7pli(ZUD zDg|aKo(Po*wYep3D+iRyaM)dqsjTShUpEWA40x4U$<{anR^^qIVVpt6FN8&SmEmX%y#l;oDtq85NCQIqv?b;GSk1$kCByI8$2a8#M-h5e8bI(qHad{ zvHZ>{T%%@#6i=_3X_m)mzCotMZOVU~y&8^~tjP{cwHHWisj0UF1xhm}!Wy>JyqFSi zDF#X{In3MwgK}-LW?J`At}UG_iqxoMg#x1mMny3&R9pU%DS<(0wOK1F?1=T+|GcUUM>{jBG5MA4cXz?uPu+ zb>+{lo3hY*7)y$WaIj27)MW$t(!J+Epwtz%xQ?wu(JFiLe)j4^B``pbcb6Hd2l$L0 z7b*s&@pdnfe5El)FPl>tQXS>K{2^QTND0vTyq8~f(_;Joq0wsl3FlQEv_5D%G&W5( zlvi?>YdC^5WP@eMkl+k;$WWjRb;wX)46&HYih+_ZzsTjlppbnwbkzbOv0Q(}phxdfsh0FTdn{Gg&Z(HWKhU~4jDx-g7`8ZC@=yr=>Kn#7|CkB2nK~bVnN6- z!5FO=6!J)46tvQ7VBjwd2Dio%bvj#T+v>-PV0kDzWAD XMG=gda5)VqLO-yQPO^ipc z9*mAh4<0?37}J9{{{o{oZ+h;?TCc55um=S8jGyg-xaS!X&KDQbBlKZK}9c`O94^Aj}7z9S0Y5{}5s81LKMm;j;GYE`27^|8Au_ZI!@-0RTj4i4M3>Jb#!Rgi5x1#et zgQ;asTwFspSk*N4OmAKO@Lc)cb7LIYW_VUKumdiK&9`Yse)Fmh1gvdgiwl@Klp1AA zo=;{PS^(AJ+vAS(3w%b43tfZOSlx=wo@yMUmGE?j%7@*SZ<5*DT7cH(t>h%F2HXCR zrb=!{ol_sQHfYQ0*y>~*X{Hv-cm%0KJL8fe!Rh!VLxIxqONIiYgTX9n2CS~Um-4_+ zC3mUg>H$JxxZMduounHcmkbY#E*KXz110z5msA!ERdSEI0YjDC^Gk*aMlUih2nviI z7l%U0M)Lb& k;RdG=&M~54%Fl(AMdRxW6O56w6nJ2aRx Date: Thu, 27 Jul 2017 10:42:02 -0400 Subject: [PATCH 39/50] Document @exclude usage --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5eece33d..60e81b0e 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,8 @@ message SomeMessage { } ``` +> NOTE: File level comments should be leading comments on the syntax directive. + **Trailing comments** Fields, Service Methods, Enum Values and Extensions support trailing comments. @@ -110,7 +112,26 @@ enum MyEnum { } ``` -**File level comments should be leading comments on the syntax directive.** +**Excluding comments** + +If you want to have some comment in your proto files, but don't want them to be part of the docs, you can simply prefix +the comment with `@exclude`. + +Example: include only the comment for the `id` field + +```protobuf +/** + * @exclude + * This comment won't be rendered + */ +message ExcludedMessage { + string id = 1; // the id of this message. + string name = 2; // @exclude the name of this message + + /* @exclude the value of this message. */ + int32 value = 3; +} +``` Check out the [example protos](examples/proto) to see all the options. From 30fdca692fc8a8e11a82f47f8efdfb9a4e0d6b37 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Thu, 27 Jul 2017 15:39:22 -0400 Subject: [PATCH 40/50] Backfill CHANGELOG.md and add CONTRIBUTING.md --- CHANGELOG.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ae84cc0c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,76 @@ +# v0.9 - February 26, 2017 + +This is the ninth official release. + +**changes** + +* Improve installation instructions for macOS (thanks @guozheng) +* Improve installation instructions for Debian/Ubuntu (thanks @mhaberler) +* Add asciidoc.mustache example template (thanks @ArcEye) +* Don't do HTML escaping in Markdown template (thanks @sunfmin) +* Add support for JSON output + +# v0.8 - February 26, 2016 + +This is the eight official release. + +**changes** + +* Add support for documenting files (#9) +* Add support for default values (#11) +* Add no-exclude flag to ignore @exclude directives (#13) +* Add support for RPC services (#14) (thanks to @murph0 !) + +# v0.7 - January 7, 2016 + +This is the seventh official release. + +**changes** + +* Added support for extensions (thanks @masterzen !) +* Added Custom Templates wiki page +* Added additional distro packages for Debian 8, Ubuntu 15.04 + 15.10 and Fedora 22 + 23 + +# v0.6 - April 8, 2015 + +This is the sixth official release. + +No functional changes were made, but Linux distribution package repositories are now provided for Ubuntu, Arch, Fedora +and openSUSE through the Open Build Service, and an RPM for CentOS 7 here below. + +# v0.5 - December 19, 2014 + +This is the fifth official release. + +**changes** + +* Support exclusion also of enum values (accidental omission in 0.4). + +# v0.4 - December 19, 2014 + +This is the fourth official release. + +**changes** + +* Updated to a newer version of qt-mustache. +* Updated Windows zip to libprotobuf/libprotoc 2.6.1. +* Added support for excluding messages/enums/fields. + +# v0.3 - August 19, 2014 + +This is the third official release. + +**changes** + +* Updated to a newer version of qt-mustache which is more spec compliant. +* Added missing documentation for enums to Markdown output. + +# v0.2 - August 14, 2014 + +This is the second official release. + +* No functional changes were made, but the build system was improved. + +# v0.1 - August 6, 2014 + +* Initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..0042f900 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing + +First off, glad you're here and want to contribute! :heart: + +## Getting Started + +In order to work on this project, you'll need to install a few things: + +1. A recent version of [Go](https://golang.org/doc/install) +1. [Glide](https://github.com/Masterminds/glide#install) - go package manager +1. [protoc](https://github.com/google/protobuf#protocol-compiler-installation) - The protobuf compiler +1. [Docker](https://www.docker.com/) (only required to if you need/want to build the docker container via `make docker`) + +When writing tests, be sure that the package in the test file is suffixed with `_test`. Eg. `protoc_gen_doc_test`. This +ensures that you'll only be testing the public interface. + +## Submitting a PR + +Here are some general guidelines for making PRs for this repo. + +1. [Fork this repo](https://github.com/pseudomuto/protoc-gen-doc/fork) +1. Make a branch off of master (`git checkout -b `) +1. Make focused commits with descriptive messages +1. Add tests that fail without your code, and pass with it +1. GoFmt your code! (see to setup your editor to do this for you) +1. Be sure to run `make examples` so your changes are reflected in the example docs +1. **Ping someone on the PR** (Lots of people, including myself, won't get a notification unless pinged directly) + +Every PR should have a well detailed summary of the changes being made and the reasoning behind them. Make sure to add +at least three sections. + +### What is Changing? + +Make sure you spell out in as much detail as necessary what will happen to which systems when your PR is merged, +what are the expected changes. + +### How is it Changing? + +Include any relevant implementation details, mimize surprises for the reviewers in this section, if you had to take some +unorthodox approaches (read hacks), explain why here. + +### What Could Go Wrong? + +How has this change been tested? In your opinion what is the risk, if any, of merging these changes. + +#### Reviewers should: + +1. Identify anything that the PR author may have missed from above. +2. Test the PR through whatever means necessary, including manually, to verify it is safe to be deployed. +3. Question everything. Never assume that something was tested or fully understood, always question and ask if there is + any uncertainty. +4. Before merging the PR make sure it has _**one**_ of the `Major release`, `Minor release`, or `Patch release` labels + applied to it (useful for the changelog and determining the version for the next release). + +## Release Process + +We follow [Semantic Versioning 2.0.0](http://semver.org/#semantic-versioning-200), and the fact that PRs are tagged with +the type of release makes determining the next version super simple. + +Look through the new (since the last release) PRs that are included in this release to determine the new version. + +* If COUNT(labelled `Major release`) > 0, then it's a MAJOR version bump. +* If COUNT(labelled `Minor release`) > 0, then it's a MINOR version bump. +* PATCH version bump + +### Now that we've got the version: + +* Run `make docker_test` to build the image and generate the examples. There should be no diff after this. +* Update the version in `version.go` +* Update CHANGELOG.md. Be sure to include links to PRs and highlight new features, bug fixes, and any breaking changes. +* Make the commit `git add CHANGELOG.md version.go && git commit -am "Bump version to "` +* Tag this SHA `git tag -a v -m "Version "` +* Finally, push to GitHub `git push && git push --tags` + +Now that the tag is on GitHub, we have a couple more things to do: + +* Create a release based on the tag and copy the entry in CHANGELOG.md into the notes. +* Run `make dist` and add the tar files (in `./dist`) to the release. + +Once CI has run for the tag, Travis will push the image to DockerHub. From 91ff11e8af226a2cd574f52d21b6710ccbb47f24 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Thu, 27 Jul 2017 16:15:05 -0400 Subject: [PATCH 41/50] Adding make lint and fixing all the lint errors --- Makefile | 23 +++++++++++++++++++++++ bench_test.go | 2 +- cmd/protoc-gen-doc/main.go | 2 +- doc.go | 4 ++-- filters.go | 2 +- filters_test.go | 8 ++++---- generate.go | 4 ++-- parser/models.go | 15 +++++++++++++++ parser/parser.go | 5 +++++ plugin.go | 4 ++-- plugin_test.go | 18 +++++++++--------- renderer.go | 24 ++++++++++++------------ renderer_test.go | 28 ++++++++++++++-------------- resources.go | 2 +- template.go | 4 ++-- template_test.go | 24 ++++++++++++------------ test/protoc_stubs.go | 2 +- version.go | 3 ++- 18 files changed, 109 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index 1070bea1..a3c8bdce 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,19 @@ .PHONY: bench test build dist docker +EXAMPLE_DIR=$(shell pwd)/examples +DOCS_DIR=$(EXAMPLE_DIR)/doc +PROTOS_DIR=$(EXAMPLE_DIR)/proto + generate: @go generate +lint: + @golint -set_exit_status ./build/... && \ + golint -set_exit_status ./cmd/... && \ + golint -set_exit_status ./parser/... && \ + golint -set_exit_status ./test/... && \ + golint -set_exit_status . + test: generate @go test -cover $(shell go list ./... | grep -v -E 'build|cmd|test|tools|vendor') @@ -25,3 +36,15 @@ dist: docker: @script/push_to_docker.sh + +docker_test: docker + @rm -f examples/doc/* + @docker run --rm -v $(DOCS_DIR):/out:rw -v $(PROTOS_DIR):/protos:ro pseudomuto/protoc-gen-doc --doc_opt=docbook,example.docbook + @docker run --rm -v $(DOCS_DIR):/out:rw -v $(PROTOS_DIR):/protos:ro pseudomuto/protoc-gen-doc --doc_opt=html,example.html + @docker run --rm -v $(DOCS_DIR):/out:rw -v $(PROTOS_DIR):/protos:ro pseudomuto/protoc-gen-doc --doc_opt=json,example.json + @docker run --rm -v $(DOCS_DIR):/out:rw -v $(PROTOS_DIR):/protos:ro pseudomuto/protoc-gen-doc --doc_opt=markdown,example.md + @docker run --rm \ + -v $(DOCS_DIR):/out:rw \ + -v $(PROTOS_DIR):/protos:ro \ + -v $(EXAMPLE_DIR)/templates:/templates:ro \ + pseudomuto/protoc-gen-doc --doc_opt=/templates/asciidoc.tmpl,example.txt diff --git a/bench_test.go b/bench_test.go index 852c9525..3713db5f 100644 --- a/bench_test.go +++ b/bench_test.go @@ -1,4 +1,4 @@ -package protoc_gen_doc_test +package gendoc_test import ( "github.com/pseudomuto/protoc-gen-doc/parser" diff --git a/cmd/protoc-gen-doc/main.go b/cmd/protoc-gen-doc/main.go index 1d330549..7ac38216 100644 --- a/cmd/protoc-gen-doc/main.go +++ b/cmd/protoc-gen-doc/main.go @@ -33,7 +33,7 @@ func main() { log.Fatal(err) } - resp, err := protoc_gen_doc.RunPlugin(req) + resp, err := gendoc.RunPlugin(req) if err != nil { log.Fatal(err) } diff --git a/doc.go b/doc.go index 528817a1..1cd80c32 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,4 @@ -// protoc-gen-doc is a protoc plugin for generating documentation from your proto files. +// Package gendoc is a protoc plugin for generating documentation from your proto files. // // Typically this will not be used as a library, though nothing prevents that. Normally it'll be invoked by passing // `--doc_out` and `--doc_opt` values to protoc. @@ -12,4 +12,4 @@ // protoc --doc_out=. --doc_opt=custom.tmpl,docs.txt protos/*.proto // // For more details, check out the README at https://github.com/pseudomuto/protoc-gen-doc -package protoc_gen_doc +package gendoc diff --git a/filters.go b/filters.go index e9ab51d7..6296c6ab 100644 --- a/filters.go +++ b/filters.go @@ -1,4 +1,4 @@ -package protoc_gen_doc +package gendoc import ( "fmt" diff --git a/filters_test.go b/filters_test.go index 7ff61ad6..e974c218 100644 --- a/filters_test.go +++ b/filters_test.go @@ -1,4 +1,4 @@ -package protoc_gen_doc_test +package gendoc_test import ( "github.com/pseudomuto/protoc-gen-doc" @@ -25,7 +25,7 @@ func (assert *FilterTest) TestPFilter() { } for input, output := range tests { - assert.Equal(html.HTML(output), protoc_gen_doc.PFilter(input)) + assert.Equal(html.HTML(output), gendoc.PFilter(input)) } } @@ -39,7 +39,7 @@ func (assert *FilterTest) TestParaFilter() { } for input, output := range tests { - assert.Equal(output, protoc_gen_doc.ParaFilter(input)) + assert.Equal(output, gendoc.ParaFilter(input)) } } @@ -51,6 +51,6 @@ func (assert *FilterTest) TestNoBrFilter() { } for input, output := range tests { - assert.Equal(output, protoc_gen_doc.NoBrFilter(input)) + assert.Equal(output, gendoc.NoBrFilter(input)) } } diff --git a/generate.go b/generate.go index bdade6e4..0d1309e1 100644 --- a/generate.go +++ b/generate.go @@ -1,7 +1,7 @@ -package protoc_gen_doc +package gendoc //go:generate go build ./build/cmd/gen_fixtures //go:generate protoc --plugin=protoc-gen-doc=./gen_fixtures --doc_out=./test test/fixtures/Booking.proto test/fixtures/Vehicle.proto //go:generate rm gen_fixtures -//go:generate go run build/cmd/resources/main.go -in resources -out resources.go -pkg protoc_gen_doc +//go:generate go run build/cmd/resources/main.go -in resources -out resources.go -pkg gendoc diff --git a/parser/models.go b/parser/models.go index d4b20802..1f875bd0 100644 --- a/parser/models.go +++ b/parser/models.go @@ -25,6 +25,7 @@ func (po *parsedObject) FullName() string { return fmt.Sprintf("%s.%s", po.Package, po.Name) } +// File represents a parsed file object. All information about a proto file will be encapsulated in a File object. type File struct { parsedObject Enums []*Enum @@ -69,10 +70,12 @@ func (pf *File) getCommentContainers() []commentContainer { return containers } +// HasEnum indicated whether or not a file-level enum exists. func (pf *File) HasEnum(name string) bool { return pf.GetEnum(name) != nil } +// GetEnum finds an enum object by name and returns it. It will return `nil` if not found. func (pf *File) GetEnum(name string) *Enum { for _, enum := range pf.Enums { if enum.Name == name { @@ -83,10 +86,12 @@ func (pf *File) GetEnum(name string) *Enum { return nil } +// HasMessage indicated whether or not this file contains the named message. func (pf *File) HasMessage(name string) bool { return pf.GetMessage(name) != nil } +// GetMessage finds a message by name and returns it. It will return `nil` if not found. func (pf *File) GetMessage(name string) *Message { for _, msg := range pf.Messages { if msg.Name == name { @@ -97,10 +102,12 @@ func (pf *File) GetMessage(name string) *Message { return nil } +// HasService indicated whether or not this file contains the named service. func (pf *File) HasService(name string) bool { return pf.GetService(name) != nil } +// GetService finds a service by name and returns it. It will return `nil` if not found. func (pf *File) GetService(name string) *Service { for _, service := range pf.Services { if service.Name == name { @@ -111,11 +118,13 @@ func (pf *File) GetService(name string) *Service { return nil } +// A Service object to encasulate service details. type Service struct { parsedObject Methods []*ServiceMethod } +// A ServiceMethod object to encapsulate service method details. type ServiceMethod struct { parsedObject ClientStreaming bool @@ -124,6 +133,7 @@ type ServiceMethod struct { ResponseType string } +// A Message object to encapsulate message details. type Message struct { parsedObject Extensions []*Extension @@ -131,6 +141,7 @@ type Message struct { enums []*descriptor.EnumDescriptorProto } +// A Field object to encapsulate message field details. type Field struct { parsedObject Type string @@ -138,6 +149,7 @@ type Field struct { DefaultValue string } +// An Extension object to encapsulate extension details. type Extension struct { Field Label string @@ -146,15 +158,18 @@ type Extension struct { ScopeType string } +// FullName returns the full name of this extension including the containing type func (ext *Extension) FullName() string { return fmt.Sprintf("%s.%s", ext.ContainingType, ext.Name) } +// An Enum object to encapsulate enum details. type Enum struct { parsedObject Values []*EnumValue } +// An EnumValue object to encapsulate enum value details. type EnumValue struct { parsedObject Number int32 diff --git a/parser/parser.go b/parser/parser.go index cd3b6a25..2b176cca 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -4,10 +4,13 @@ import ( "github.com/golang/protobuf/protoc-gen-go/plugin" ) +// A ParseResult contains a set of parsed proto files type ParseResult struct { Files []*File } +// GetFile returns the parsed proto file specified by the name (base name without path) +// e.g. pr.GetFile("Vehicle.proto") func (pr *ParseResult) GetFile(name string) *File { for _, f := range pr.Files { if f.Name == name { @@ -18,6 +21,8 @@ func (pr *ParseResult) GetFile(name string) *File { return nil } +// ParseCodeRequest iterates through all the proto files in the code gen request, parses them, and finally adds them to +// the returned ParseResult func ParseCodeRequest(req *plugin_go.CodeGeneratorRequest) *ParseResult { result := new(ParseResult) diff --git a/plugin.go b/plugin.go index 162dd24f..a98c407a 100644 --- a/plugin.go +++ b/plugin.go @@ -1,4 +1,4 @@ -package protoc_gen_doc +package gendoc import ( "fmt" @@ -25,7 +25,7 @@ type PluginOptions struct { // directory specified with the `--doc_out` argument to protoc. func ParseOptions(req *plugin_go.CodeGeneratorRequest) (*PluginOptions, error) { options := &PluginOptions{ - Type: RenderTypeHtml, + Type: RenderTypeHTML, TemplateFile: "", OutputFile: "index.html", } diff --git a/plugin_test.go b/plugin_test.go index 9ed3b9dd..12507352 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -1,4 +1,4 @@ -package protoc_gen_doc_test +package gendoc_test import ( "github.com/golang/protobuf/proto" @@ -28,10 +28,10 @@ func (assert *PluginTest) TestParseOptionsForBuiltinTemplates() { req := new(plugin_go.CodeGeneratorRequest) req.Parameter = proto.String(kind + "," + file) - options, err := protoc_gen_doc.ParseOptions(req) + options, err := gendoc.ParseOptions(req) assert.Nil(err) - renderType, err := protoc_gen_doc.NewRenderType(kind) + renderType, err := gendoc.NewRenderType(kind) assert.Nil(err) assert.Equal(renderType, options.Type) @@ -44,10 +44,10 @@ func (assert *PluginTest) TestParseOptionsForCustomTemplate() { req := new(plugin_go.CodeGeneratorRequest) req.Parameter = proto.String("/path/to/template.tmpl,/base/name/only/output.md") - options, err := protoc_gen_doc.ParseOptions(req) + options, err := gendoc.ParseOptions(req) assert.Nil(err) - assert.Equal(protoc_gen_doc.RenderTypeHtml, options.Type) + assert.Equal(gendoc.RenderTypeHTML, options.Type) assert.Equal("/path/to/template.tmpl", options.TemplateFile) assert.Equal("output.md", options.OutputFile) } @@ -64,7 +64,7 @@ func (assert *PluginTest) TestParseOptionsWithInvalidValues() { req := new(plugin_go.CodeGeneratorRequest) req.Parameter = proto.String(value) - _, err := protoc_gen_doc.ParseOptions(req) + _, err := gendoc.ParseOptions(req) assert.NotNil(err) } } @@ -73,7 +73,7 @@ func (assert *PluginTest) TestRunPluginForBuiltinTemplate() { req := new(plugin_go.CodeGeneratorRequest) req.Parameter = proto.String("markdown,/base/name/only/output.md") - resp, err := protoc_gen_doc.RunPlugin(req) + resp, err := gendoc.RunPlugin(req) assert.Nil(err) assert.Equal(1, len(resp.File)) @@ -85,7 +85,7 @@ func (assert *PluginTest) TestRunPluginForCustomTemplate() { req := new(plugin_go.CodeGeneratorRequest) req.Parameter = proto.String("resources/html.tmpl,/base/name/only/output.html") - resp, err := protoc_gen_doc.RunPlugin(req) + resp, err := gendoc.RunPlugin(req) assert.Nil(err) assert.Equal(1, len(resp.File)) @@ -97,6 +97,6 @@ func (assert *PluginTest) TestRunPluginWithInvalidOptions() { req := new(plugin_go.CodeGeneratorRequest) req.Parameter = proto.String("html") - _, err := protoc_gen_doc.RunPlugin(req) + _, err := gendoc.RunPlugin(req) assert.NotNil(err) } diff --git a/renderer.go b/renderer.go index a8a0f506..ece90407 100644 --- a/renderer.go +++ b/renderer.go @@ -1,4 +1,4 @@ -package protoc_gen_doc +package gendoc import ( "bytes" @@ -8,15 +8,15 @@ import ( text_template "text/template" ) -// An "Enum" for which type of renderer to use. +// RenderType is an "enum" for which type of renderer to use. type RenderType int8 // Available render types. const ( _ RenderType = iota RenderTypeDocBook - RenderTypeHtml - RenderTypeJson + RenderTypeHTML + RenderTypeJSON RenderTypeMarkdown ) @@ -27,9 +27,9 @@ func NewRenderType(renderType string) (RenderType, error) { case "docbook": return RenderTypeDocBook, nil case "html": - return RenderTypeHtml, nil + return RenderTypeHTML, nil case "json": - return RenderTypeJson, nil + return RenderTypeJSON, nil case "markdown": return RenderTypeMarkdown, nil } @@ -46,9 +46,9 @@ func (rt RenderType) renderer() (Processor, error) { switch rt { case RenderTypeDocBook: return &textRenderer{string(tmpl)}, nil - case RenderTypeHtml: + case RenderTypeHTML: return &htmlRenderer{string(tmpl)}, nil - case RenderTypeJson: + case RenderTypeJSON: return new(jsonRenderer), nil case RenderTypeMarkdown: return &htmlRenderer{string(tmpl)}, nil @@ -61,9 +61,9 @@ func (rt RenderType) template() ([]byte, error) { switch rt { case RenderTypeDocBook: return fetchResource("docbook.tmpl") - case RenderTypeHtml: + case RenderTypeHTML: return fetchResource("html.tmpl") - case RenderTypeJson: + case RenderTypeJSON: return nil, nil case RenderTypeMarkdown: return fetchResource("markdown.tmpl") @@ -87,10 +87,10 @@ type Processor interface { // supplying a non-empty string as the last parameter. // // Example: generating an HTML template (assuming you've got a Template object) -// data, err := RenderTemplate(RenderTypeHtml, &template, "") +// data, err := RenderTemplate(RenderTypeHTML, &template, "") // // Example: generating a custom template (assuming you've got a Template object) -// data, err := RenderTemplate(RenderTypeHtml, &template, "{{range .Files}}{{.Name}}{{end}}") +// data, err := RenderTemplate(RenderTypeHTML, &template, "{{range .Files}}{{.Name}}{{end}}") func RenderTemplate(kind RenderType, template *Template, inputTemplate string) ([]byte, error) { if inputTemplate != "" { processor := &textRenderer{inputTemplate} diff --git a/renderer_test.go b/renderer_test.go index 25f7e873..c7abf07d 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -1,4 +1,4 @@ -package protoc_gen_doc_test +package gendoc_test import ( "github.com/pseudomuto/protoc-gen-doc" @@ -11,7 +11,7 @@ import ( const tempTestDir = "./tmp" -var renderTemplate *protoc_gen_doc.Template +var renderTemplate *gendoc.Template type RendererTest struct { suite.Suite @@ -28,48 +28,48 @@ func (assert *RendererTest) SetupSuite() { os.Mkdir(tempTestDir, os.ModePerm) result := parser.ParseCodeRequest(codeGenRequest) - renderTemplate = protoc_gen_doc.NewTemplate(result) + renderTemplate = gendoc.NewTemplate(result) } func (assert *RendererTest) TestDocBookRenderer() { - _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeDocBook, renderTemplate, "") + _, err := gendoc.RenderTemplate(gendoc.RenderTypeDocBook, renderTemplate, "") assert.Nil(err) } func (assert *RendererTest) TestHtmlRenderer() { - _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeHtml, renderTemplate, "") + _, err := gendoc.RenderTemplate(gendoc.RenderTypeHTML, renderTemplate, "") assert.Nil(err) } func (assert *RendererTest) TestJsonRenderer() { - _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeJson, renderTemplate, "") + _, err := gendoc.RenderTemplate(gendoc.RenderTypeJSON, renderTemplate, "") assert.Nil(err) } func (assert *RendererTest) TestMarkdownRenderer() { - _, err := protoc_gen_doc.RenderTemplate(protoc_gen_doc.RenderTypeMarkdown, renderTemplate, "") + _, err := gendoc.RenderTemplate(gendoc.RenderTypeMarkdown, renderTemplate, "") assert.Nil(err) } func (assert *RendererTest) TestNewRenderType() { - expected := []protoc_gen_doc.RenderType{ - protoc_gen_doc.RenderTypeDocBook, - protoc_gen_doc.RenderTypeHtml, - protoc_gen_doc.RenderTypeJson, - protoc_gen_doc.RenderTypeMarkdown, + expected := []gendoc.RenderType{ + gendoc.RenderTypeDocBook, + gendoc.RenderTypeHTML, + gendoc.RenderTypeJSON, + gendoc.RenderTypeMarkdown, } supplied := []string{"docbook", "html", "json", "markdown"} for idx, input := range supplied { - rt, err := protoc_gen_doc.NewRenderType(input) + rt, err := gendoc.NewRenderType(input) assert.Nil(err) assert.Equal(expected[idx], rt) } } func (assert *RendererTest) TestNewRenderTypeUnknown() { - rt, err := protoc_gen_doc.NewRenderType("/some/template.tmpl") + rt, err := gendoc.NewRenderType("/some/template.tmpl") assert.Zero(rt) assert.NotNil(err) } diff --git a/resources.go b/resources.go index 3138e5d9..9bae398f 100644 --- a/resources.go +++ b/resources.go @@ -1,6 +1,6 @@ // AUTOGENERATED CODE. DO NOT EDIT. -package protoc_gen_doc +package gendoc import ( "bytes" diff --git a/template.go b/template.go index a36a3b80..5f15437c 100644 --- a/template.go +++ b/template.go @@ -1,4 +1,4 @@ -package protoc_gen_doc +package gendoc import ( "encoding/json" @@ -66,7 +66,7 @@ func NewTemplate(pr *parser.ParseResult) *Template { func makeScalars() []*ScalarValue { data, _ := fetchResource("scalars.json") - scalars := make([]*ScalarValue, 0) + var scalars []*ScalarValue json.Unmarshal(data, &scalars) return scalars diff --git a/template_test.go b/template_test.go index a9221cb7..2bda8797 100644 --- a/template_test.go +++ b/template_test.go @@ -1,4 +1,4 @@ -package protoc_gen_doc_test +package gendoc_test import ( "github.com/pseudomuto/protoc-gen-doc" @@ -9,9 +9,9 @@ import ( ) var ( - template *protoc_gen_doc.Template - bookingFile *protoc_gen_doc.File - vehicleFile *protoc_gen_doc.File + template *gendoc.Template + bookingFile *gendoc.File + vehicleFile *gendoc.File ) type TemplateTest struct { @@ -27,7 +27,7 @@ func (assert *TemplateTest) SetupSuite() { assert.Nil(err) result := parser.ParseCodeRequest(codeGenRequest) - template = protoc_gen_doc.NewTemplate(result) + template = gendoc.NewTemplate(result) bookingFile = template.Files[0] vehicleFile = template.Files[1] } @@ -54,7 +54,7 @@ func (assert *TemplateTest) TestFileEnumProperties() { assert.Equal("A flag for the status result.", enum.Description) assert.Equal(2, len(enum.Values)) - expectedValues := []*protoc_gen_doc.EnumValue{ + expectedValues := []*gendoc.EnumValue{ {Name: "OK", Number: "200", Description: "OK result."}, {Name: "BAD_REQUEST", Number: "400", Description: "BAD result."}, } @@ -198,7 +198,7 @@ func (assert *TemplateTest) TestExcludedComments() { assert.Equal("the id of this message.", findField("id", message).Description) } -func findService(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Service { +func findService(name string, f *gendoc.File) *gendoc.Service { for _, s := range f.Services { if s.Name == name { return s @@ -208,7 +208,7 @@ func findService(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Service { return nil } -func findServiceMethod(name string, s *protoc_gen_doc.Service) *protoc_gen_doc.ServiceMethod { +func findServiceMethod(name string, s *gendoc.Service) *gendoc.ServiceMethod { for _, m := range s.Methods { if m.Name == name { return m @@ -218,7 +218,7 @@ func findServiceMethod(name string, s *protoc_gen_doc.Service) *protoc_gen_doc.S return nil } -func findEnum(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Enum { +func findEnum(name string, f *gendoc.File) *gendoc.Enum { for _, enum := range f.Enums { if enum.LongName == name { return enum @@ -228,7 +228,7 @@ func findEnum(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Enum { return nil } -func findExtension(name string, f *protoc_gen_doc.File) *protoc_gen_doc.FileExtension { +func findExtension(name string, f *gendoc.File) *gendoc.FileExtension { for _, ext := range f.Extensions { if ext.LongName == name { return ext @@ -238,7 +238,7 @@ func findExtension(name string, f *protoc_gen_doc.File) *protoc_gen_doc.FileExte return nil } -func findMessage(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Message { +func findMessage(name string, f *gendoc.File) *gendoc.Message { for _, m := range f.Messages { if m.LongName == name { return m @@ -248,7 +248,7 @@ func findMessage(name string, f *protoc_gen_doc.File) *protoc_gen_doc.Message { return nil } -func findField(name string, m *protoc_gen_doc.Message) *protoc_gen_doc.MessageField { +func findField(name string, m *gendoc.Message) *gendoc.MessageField { for _, f := range m.Fields { if f.Name == name { return f diff --git a/test/protoc_stubs.go b/test/protoc_stubs.go index e310e77f..10dc88eb 100644 --- a/test/protoc_stubs.go +++ b/test/protoc_stubs.go @@ -1,4 +1,4 @@ -// Utilities used for testing purposes only. +// Package test container utilities used for testing purposes only. package test import ( diff --git a/version.go b/version.go index 1f3ed2e7..9e9e4c0f 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,4 @@ -package protoc_gen_doc +package gendoc +// VERSION is the version of protoc-gen-doc being used. const VERSION = "1.0.0" From a44ce5f2a23fb0edc867a2fc2612ceb63a3955c1 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Thu, 27 Jul 2017 19:52:25 -0400 Subject: [PATCH 42/50] Bump version to 1.0.0-alpha --- .travis.yml | 6 ++++-- version.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b65e5699..603e3456 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: go -sudo: false +sudo: required + +services: + - docker env: global: @@ -14,7 +17,6 @@ cache: go: - 1.8.x - - master install: - if [ ! -d "${PROTOC_TARGET}" ]; then curl -fsSL "$PROTOC_RELEASE" > "${PROTOC_TARGET}.zip"; fi diff --git a/version.go b/version.go index 9e9e4c0f..8dfdcd8c 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gendoc // VERSION is the version of protoc-gen-doc being used. -const VERSION = "1.0.0" +const VERSION = "1.0.0-alpha" From 5b856b7e1e0ea2af787cde4a3cfbb81cf3e8ff99 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Tue, 1 Aug 2017 08:43:32 -0400 Subject: [PATCH 43/50] Add dependencies task for make --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a3c8bdce..5172e37d 100644 --- a/Makefile +++ b/Makefile @@ -17,10 +17,13 @@ lint: test: generate @go test -cover $(shell go list ./... | grep -v -E 'build|cmd|test|tools|vendor') +dependencies: + @glide install + bench: @go test -bench=. -build: generate +build: dependencies @go build ./cmd/... examples: build From 2f4cb21fcbc0c8328d2f090a1c6b3467c494385b Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Tue, 1 Aug 2017 11:54:11 -0400 Subject: [PATCH 44/50] Update docker to support single/multi file generation --- README.md | 21 ++++++++++++++++++--- script/entrypoint.sh | 8 ++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 60e81b0e..287de43f 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,10 @@ docker run --rm \ pseudomuto/protoc-gen-doc ``` -By default HTML documentation is generated in `/out/index.html`. This can be changed by passing the `--doc_opt` -parameter to the container. +By default HTML documentation is generated in `/out/index.html` for all `.proto` files in the `/protos` volume. This can +be changed by passing the `--doc_opt` parameter to the container. -For example, to generate Markdown for the examples: +For example, to generate Markdown for all the examples: ``` docker run --rm \ @@ -52,6 +52,21 @@ docker run --rm \ pseudomuto/protoc-gen-doc --doc_opt=md,docs.md ``` +You can also generate documentation for a single file. This can be done by passing the file(s) to the command: + +``` +docker run --rm \ + -v $(pwd)/examples/doc:/out \ + -v $(pwd)/examples/proto:/protos \ + pseudomuto/protoc-gen-doc --doc_opt=md,docs.md /protos/Booking.proto [OPTIONALLY LIST MORE FILES] +``` + +_**Remember**_: Paths should be from within the container, not the host! + +> NOTE: Due to the way wildcard expansion works with docker you cannot use a wildcard path (e.g. `protos/*.proto`) in +the file list. To get around this, if no files are passed, the container will generate docs for `protos/*.proto`, which +can be changed by mounting different volumes. + ### Simple Usage For example, to generate HTML documentation for all `.proto` files in the `proto` directory into `doc/index.html`, type: diff --git a/script/entrypoint.sh b/script/entrypoint.sh index 9a79e705..5313fef8 100755 --- a/script/entrypoint.sh +++ b/script/entrypoint.sh @@ -1,5 +1,9 @@ #!/bin/bash set -euo pipefail -# this is required because of the wildcard expansion. Passing protos/*.proto in CMD gets escaped -_-. -exec protoc --doc_out=/out "$@" protos/*.proto +# this is required because of the wildcard expansion. Passing protos/*.proto in CMD gets escaped -_-. So instead leaving +# off the [FILES] will put protos/*.proto in from here which will expand correctly. +args=("$@") +if [ "${#args[@]}" -lt 2 ]; then args+=(protos/*.proto); fi + +exec protoc --doc_out=/out "${args[@]}" From 288a176e3ce32948a248dec58990e45200e11fe4 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Tue, 1 Aug 2017 12:11:35 -0400 Subject: [PATCH 45/50] Bump version to 1.0.0-beta --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 8dfdcd8c..20f725b1 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gendoc // VERSION is the version of protoc-gen-doc being used. -const VERSION = "1.0.0-alpha" +const VERSION = "1.0.0-beta" From 87afa99859487502d775a5f510c4dbc223f7d8d2 Mon Sep 17 00:00:00 2001 From: Arnold Schrijver Date: Wed, 2 Aug 2017 10:00:12 +0200 Subject: [PATCH 46/50] edit markdown.tmpl, improved toc line-breaks --- resources/markdown.tmpl | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/resources/markdown.tmpl b/resources/markdown.tmpl index 48d3b7db..eb38e1d3 100644 --- a/resources/markdown.tmpl +++ b/resources/markdown.tmpl @@ -3,22 +3,17 @@ ## Table of Contents {{range .Files}} -{{$file_name := .Name}} -* [{{.Name}}](#{{.Name}}) - {{range .Messages}} - * [{{.LongName}}](#{{.FullName}}) +{{$file_name := .Name}}- [{{.Name}}](#{{.Name}}) + {{range .Messages}} - [{{.LongName}}](#{{.FullName}}) {{end}} - {{range .Enums}} - * [{{.LongName}}](#{{.FullName}}) + {{range .Enums}} - [{{.LongName}}](#{{.FullName}}) {{end}} - {{range .Extensions}} - * [File-level Extensions](#{{$file_name}}-extensions) + {{range .Extensions}} - [File-level Extensions](#{{$file_name}}-extensions) {{end}} - {{range .Services}} - * [{{.Name}}](#{{.FullName}}) + {{range .Services}} - [{{.Name}}](#{{.FullName}}) {{end}} {{end}} -* [Scalar Value Types](#scalar-value-types) +- [Scalar Value Types](#scalar-value-types) {{range .Files}} {{$file_name := .Name}} From 3989b59aa7e77b5f379048538519ad8ad5edd415 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Tue, 8 Aug 2017 20:28:34 -0400 Subject: [PATCH 47/50] Update examples with new code --- Makefile | 2 +- examples/doc/example.md | 47 ++++++++++++++--------------------------- resources.go | 2 +- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 5172e37d..7b687412 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ dependencies: bench: @go test -bench=. -build: dependencies +build: dependencies generate @go build ./cmd/... examples: build diff --git a/examples/doc/example.md b/examples/doc/example.md index 5f561f1c..d0572e2c 100644 --- a/examples/doc/example.md +++ b/examples/doc/example.md @@ -3,52 +3,37 @@ ## Table of Contents - -* [Booking.proto](#Booking.proto) - - * [Booking](#com.example.Booking) - - * [BookingStatus](#com.example.BookingStatus) - - * [EmptyBookingMessage](#com.example.EmptyBookingMessage) +- [Booking.proto](#Booking.proto) + - [Booking](#com.example.Booking) + - [BookingStatus](#com.example.BookingStatus) + - [EmptyBookingMessage](#com.example.EmptyBookingMessage) + - [BookingService](#com.example.BookingService) - * [BookingService](#com.example.BookingService) - - -* [Customer.proto](#Customer.proto) - - * [Address](#com.example.Address) - - * [Customer](#com.example.Customer) +- [Customer.proto](#Customer.proto) + - [Address](#com.example.Address) + - [Customer](#com.example.Customer) - -* [Vehicle.proto](#Vehicle.proto) - - * [Manufacturer](#com.example.Manufacturer) - - * [Model](#com.example.Model) - - * [Vehicle](#com.example.Vehicle) - - * [Vehicle.Category](#com.example.Vehicle.Category) - - - * [Manufacturer.Category](#com.example.Manufacturer.Category) +- [Vehicle.proto](#Vehicle.proto) + - [Manufacturer](#com.example.Manufacturer) + - [Model](#com.example.Model) + - [Vehicle](#com.example.Vehicle) + - [Vehicle.Category](#com.example.Vehicle.Category) + - [Manufacturer.Category](#com.example.Manufacturer.Category) - * [File-level Extensions](#Vehicle.proto-extensions) + - [File-level Extensions](#Vehicle.proto-extensions) -* [Scalar Value Types](#scalar-value-types) +- [Scalar Value Types](#scalar-value-types) diff --git a/resources.go b/resources.go index 9bae398f..491ca1db 100644 --- a/resources.go +++ b/resources.go @@ -13,7 +13,7 @@ import ( var embeddedResources = map[string]string{ "docbook.tmpl": "H4sIAAAAAAAA/+xZ30/bOhR+719h5fEiEq64SFeTW6TBqmkChIDt3U1OW2uOndlOAUX93ycnIb8Tl7UUtvGCZJ/P58TH5/uOa/DpQ8jQCqSigo+df90jBwH3RUD5Yux8vZse/u+cTkaYSE19BpMRQlhTzWByLYUWvmDoXPhxCFwTTQXHXmYdIZQkkvAFIHdKGaj12ixV4BuUMReOksS9IiGs15W1ZnVEJEHuOShf0sisSl1U/F6CUmSRuy6dIxqMnSRxpzFjmWMnc1mNeCH4oiPqUFxjo3PkfiZqSoEFqpjHmswYoLkkIYwdwlgRsAiJfUaU4iRsRS8NKHPb+CDjYiFFHCFfMDV2/qs4RwibyQh8Y7yngV6OnX8cb2vEkXtiBx03IXoJJKjOIISluK/PIISBa/k4SXeLvWzQDbl7jGAYcUFmwIYhlZPsBGKv8Y3Ya20E65kIGusq9V2rBuvGKwU/9N2YUf4dmT/Ay4o2KTEVnVdRNsSegU2G/ZkVJlu2uJ0MyEr/HOYkZvobYbGJanCTfO4DSpKm3UsBSQI86Anayr1Jawqvn0c9+9jLGFHQ2ksJWFK46qEg7acHDdzI3O6JewVKQ4DKCBYOn+yDw2+D5UVOtmX6R6IsiKs4nIF8ZTHoqDJrjl5JENr+zgTXhHLKFw3PpeH5opMdy1+lOtirXXSqprJQeBzu7+qyK6VLk2yTt+N9yNuWumT29hvISZbvnUvJdrTcF6l6aJQP+tp6/UdGUd7mV8ghgxWw/j6NKZ8LGRI2yJb3Tv7eyd87+R/VyWu830R88hq5Bbmi/q89Qbx8D+/o35egl+JtvDG8uGBle0X2Tn8DP2JQGtml6wZUJLiCDaAvrk/5Ue5BnPL8NJQkn91aqp5y2nKfTT9bpt7ehaVhqEhJ54PorU8YkdlVOy20Ol3tl5T+K4qNhh32Extgt/Y2B1qnlJ+0G0mhRT8Tn+4SQpsE9gPODg6sTr6QFbGCrh/1UvA+WKPcWmxvc73sMmlB1Jnel5an1pM+z1foVBkP7cFohEmYFXUWRRt5M5nbCJhlrxfaYmuTqw2m1nna0eErlBxhr/j/xs8AAAD//2FyhLQRGQAA", "html.tmpl": "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", - "markdown.tmpl": "H4sIAAAAAAAA/+RWTW+bQBC98yumpocmEfY9sn1o0qiqkihKol6iqlnbYxtpvUvZxWrE8t+r/WBZMNSWIvVSLjD7Mbx57+1ADA85l3zJKVzzZbFDJolMOYumBBjZ4WwkeTaazKMojuGZLCgCX8MVZxKZFFFZ5oRtEMY3KUVRVVFZflynFH/qvXA5g/E92WFVRefwUpYu+PEp9s9nEYBPcodCkI3JAwBg99xytgn33RSUhnuRrcx6n+ULK3bvTfFbIhMpZ0EeXWFCcY8UmmmTr6m4qhL0cwO5nzDfp8tOkcfR1fdzeHlaEkpy+E5ogfD8lqGGIcxgsteDidSDZ9Hp8ni1PRat+TQDQtMNm43ydLOVo/mUwDbH9WwUa1fMn3k2nZD5dJJZf/jNUVmOr1Es8zTTZqqqAEogcvjWpmrnNpOuUa43ZbqG8VciblKkK51QgXkEZWgBBbdkgRQUBDtBRQoSfYG9Qzt0F6iQPZ0fEiOZasoE5e2l3xcKaOMzu9qgMMvLkvFFDu1KbB3XuCYFlUbVqgIXXoKpO5xyTjAA295oGGkZWDWGbZj5TIS+3Re7BeZDDB2y5EkaZqt5dy9jLcJsrNsJSVnKNt0ZC+/fUGenph+SBJCtYOd8CkkyD+xbd5d3eleBnjomwAlsm8oGmT7GX8DEIQOoS63L7zNWwMFAD/R89HbP/9eZrwfWfB30ZiNIi/+OL4PPynFr/sWWdyi3fFW78xF/FShkLc0jiowzgXU8KE1XhW7YjVX4fdAAhrutg9Rtum643Xvteov5pA3HDsmBJMLRXqvhzlIcw+FXWtM7zvTvVs3fPZcoQMHVxUU99I3sSf388Ca3nLmon96ARzg8Gj1nwMJq6A3NYn4FLRkjmMyhPeS00JD98ciycE5jD2OLvx7x7PwJAAD//3dr9Ll0CgAA", + "markdown.tmpl": "H4sIAAAAAAAA/+RWz2/aMBS+5694IzusqgL3CjisXTVNbVW1aJdqWg08IJKxs9hBq2L/75N/JHaADKRKuyyX+Nl5L9/7vs9OUngsueQLTuGGL6otMklkzlkyJsDIFicDyYvBaJokaQozMqcIfAXXnElkUiR1XRK2Rhje5hSF1kldf1zlFH+aXLiawPCBbFHrDF7q2o9/fErb8UUC0Na4RyHI2pQBcAl3nK3jpNuK0jgR2VLruMQXVm3flf9bIhM5Z00R01dGcYcUwpotFvrUOsN2rafwM5a7fBH3dhpXc8/g5XlBKCnhO6EVwuytQINB2MlsZyYzaSYvkrMVCQK3WIzM4wIIzddsMijz9UYOpmMCmxJXk0FqjDCd8WI8ItPxqHCWaJOTuh7eoFiUeWH8o3UEJQjbeWvo2hvMlguaHS2Zr2D4lYjbHOnSFFRgh6AsLaDgjsyRgoIoE1SiIDMXuDt0Q3+Bitkz9SGzKqrQJqjWWOZ9sYAuvnBPWxT28bpmfF5CtxPXxw2uSEWlVVVr8OEV2L7jJe8EC7DrjcBIbN1EBbcGZj4TYW4P1XaOZR9Dhyy1JPWzFd59lLEOYS42JwjJWc7W+ysO3r+hzi2NP2QZIFvC1vsUsmwa2dcfKu/1rgKzdEqAM9i2nfUyfYq/iIlDBtC02rR/zFgRBz0HYMvH0aPz/3Xm64E1X3u9GQTp8L/ny/BNOcOaf7HlPcoNXzbufMJfFQrZSPOEouBMYBP3SrOvwn64H6v4+2AA9J+2HtL+oeunu2eve95hPivh1CY5kER42hs1/F5KUzj8Sht6h4X5w2r4e+ASBSi4vrxspr6RHWnGj29yw5mPjtMb8QiHW+PIHnCwAr2xWezfnyNjAKMpdKe8FgZyuz2KIl4z2OPY4W9mWnb+BAAA///ASSPYZwoAAA==", "scalars.json": "H4sIAAAAAAAA/9yXzW4aMRDH7zzFiFMqBZDSlEa9JZGQOOQEOUWp5GVnvW6NTewxzaqq1HfoG/ZJqt0F1gYvpChVk9zQfHg9v/nPWNx1AL53AAC6C6NJT4sFdj9BN9Uukdg9rV1KE9rSvDbMFot45MzG7XxzciY1o+H52vGFLVk8ZZEvgpyNvaBcq6jLuKRYO0aVowPw47SlxiB1X4lBYFNhYN4q8P1ZrMCwjn9dn1DeNTb13Vq0sGRGsERiT6LilAOqmU6F4n0YK8wyMROoCDJtNh5QyBmJJYJy8wSNhd8/f4HIoNDOQCZQpiAsSPEVZQGkIWdl7DppyaRDewrOItjqYiCUJWRpPwI8uHkDXKgI7iDWh+1Fe6iFIuRo4rC9FB/1leDKzUEbGInH8tcJs2DwwQmD6btDPWi0/sJ6MDw/0IPm5k0PpFY82oT4SPvhu10YWDLCC9huxsBP3+3IXvLuGPlHOLgWMbqoGt0zy/EAgaM06Y4RZQuZmERcXCPu/4jkKET2qN05EVxhCkJRPWt9mOZoEebaIGxGWhZ1Cu6OM+VMgUHuJDNQXcG+7fVoj9qPz855eN7G+VWvwEw8YhqR8aX8xgoLWflqJAWh7cNNgK4G5FbvdLZ6OYAZBJ0RKuAGGaGp484+n128vM35zEqtWEakumKJgud0CGb54B6G+WH4epft/mF/uh7f9tL7Cy29wa2UaC2f8q/Lj2vK9K08eqZfZWlHpiKFbnuCCrecfoFT4/BaMmsHIyZt/XN/twOSTa+hdsDcWQJWd36mFTGh4HY66l2sXq+01NjHXiIILifX4zEQPlJMF+GHGmKhnbdczKc2CZuft3wiZGbJDJwS5ZVj3Ooz4aQqbf98VMrfAXbDig0fpgpgJhFkmCnA4oNDNSvXafvUtNG5KggnLYTu7svjYoR2s55OaR+dqsO9i6vxtEbUue/8CQAA//8z+wC/ohEAAA==", } From 4d22a70af24ac7357d13d323afca78c4d80edfce Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Tue, 8 Aug 2017 20:36:21 -0400 Subject: [PATCH 48/50] Bump version to 1.0.0-rc1 --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 20f725b1..97c5b701 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gendoc // VERSION is the version of protoc-gen-doc being used. -const VERSION = "1.0.0-beta" +const VERSION = "1.0.0-rc" From a309cf1ca2ddb6d6cce01fab970e6bcb9541b1ba Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Tue, 26 Sep 2017 15:21:01 -0400 Subject: [PATCH 49/50] Update README with instructions for go get --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 287de43f..004c7f3b 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,12 @@ It supports proto2 and proto3, and can handle having both in the same context (s ## Installation -There is a Docker image available (`pseudomuto/protoc-gen-doc`) that has everything you need to generate documentation from your -protos. +There is a Docker image available (`docker pull pseudomuto/protoc-gen-doc`) that has everything you need to generate +documentation from your protos. If you'd like to install this locally, you can `go get` it. -`go get -u github.com/pseudomuto/protoc-gen-doc` +`go get -u github.com/pseudomuto/protoc-gen-doc/cmd/...` ## Invoking the Plugin From 23825e5a950ee78e3c24495061a28942f4801813 Mon Sep 17 00:00:00 2001 From: "David Muto (pseudomuto)" Date: Tue, 26 Sep 2017 15:56:17 -0400 Subject: [PATCH 50/50] Update NoBB to include space separator --- examples/doc/example.txt | 2 +- filters.go | 11 +++++++---- filters_test.go | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/doc/example.txt b/examples/doc/example.txt index 36388aab..24e182fb 100644 --- a/examples/doc/example.txt +++ b/examples/doc/example.txt @@ -8,7 +8,7 @@ === Booking -Represents the booking of a vehicle.Vehicles are some cool shit. But drive carefully! +Represents the booking of a vehicle. Vehicles are some cool shit. But drive carefully! |=========================================== diff --git a/filters.go b/filters.go index 6296c6ab..1799af33 100644 --- a/filters.go +++ b/filters.go @@ -7,7 +7,10 @@ import ( "strings" ) -var paraPattern = regexp.MustCompile("(\\n|\\r|\\r\\n)\\s*") +var ( + paraPattern = regexp.MustCompile("(\\n|\\r|\\r\\n)\\s*") + spacePattern = regexp.MustCompile("( )+") +) // PFilter splits the content by new lines and wraps each one in a

tag. func PFilter(content string) template.HTML { @@ -23,8 +26,8 @@ func ParaFilter(content string) string { // NoBrFilter removes CR and LF from content func NoBrFilter(content string) string { - withoutCR := strings.Replace(content, "\r", "", -1) - withoutLF := strings.Replace(withoutCR, "\n", "", -1) + withoutCR := strings.Replace(content, "\r", " ", -1) + withoutLF := strings.Replace(withoutCR, "\n", " ", -1) - return strings.Replace(withoutLF, "\r\n", "", -1) + return spacePattern.ReplaceAllString(withoutLF, " ") } diff --git a/filters_test.go b/filters_test.go index e974c218..64f34c24 100644 --- a/filters_test.go +++ b/filters_test.go @@ -48,6 +48,7 @@ func (assert *FilterTest) TestNoBrFilter() { "My content": "My content", "My content \r\nHere.": "My content Here.", "My\n content\r right\r\n here.": "My content right here.", + "My\ncontent\rright\r\nhere.": "My content right here.", } for input, output := range tests {