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
zcmaJ_+g2ON6;)Tagj8T5+JI#Nc1;6gM%c0~?B$6kv0;QVII*#pz{AB4ETJwz$GW9k
za!mdp@5x`}H}a4l$y@#*`&3uAT9}m=FHWDTy-%IH%vajp*Wt*Q!Q+A7ANIE%|0*xN
zR!3HpSjuM20xPDR)L;L$#{d6Wd%D%{R^-=achHf}cYOBuUeDENKVQ9Z?L23mTQr2R
zk#68G=RXEdzw5iQlhEWyW3O=DCB<0_{}vf;9V5+W4rUCxp91L+SvsHUKruCCn(`5``5?+yK{M&e#+JK@eKkd*YfMi@xJr(GGe
zd~Y!H`aPZ9$xo%<@!4wxc7dCp<&`R)T!a3kCwyskMp1c$!b|p}QC{X2C7+$+>Dv2`
zA1HwM^Ps-FvtKZ(|2BADd*(p;U%ZyIf6H^f$e}95z`i^2;AGyGZV2KN!+ybYJFcs5
z-*DunYa0eGZgURWD1nwIc(3)2()@&0-}%FTOFUy+X6nM=KXGHmHqDe>dRpc}S6U!K
zPoI0e3(-wfTp%8cFJYq#Dx7Dz!E7^~z0GT!Wm2{^lUg)NF939SDWiiT2;GJA0wsln
z3?fw^nmtzpqt<1m0yXDmCS};>Om>m~z^zP*rED|Lo|l$IL!#n)ffxcekVDaL3soj$
z#SBl=QC!StW+Mr9o{ZG^E0!
z2BOCo?nER8ND48D0g?hp%8CRB7ud7X3Ow_+-~Swl(LgjKFNpIi;kofc7#J5a1YRuqpe}4y`3uf0xO*wJVw5%kx+8q~=aCGCq8s)~(eC@AjX^>YOhgPWw#ZXU**p9Z
zw=Cu2Wj5;w6*A8y&yth%9Wm@H7+80Ur|Gz5TgzULS>-y?!PwYkk15|5lwW#4JCDvjpGJP#~JJNw_4OHZ5gCe;Os?gJ8DjLQS)oFroE@{$$HGgz_SjX`-%s`1GetiLQiOc&Me&KRr~@I$T$%pn?QaLBbz{ekW&c<
zn@s{+#R{X4E!*4xazUsu{td1L+zs|%UBBJ}?naD@6fie(_aYr|H-L*x04z9IW(B9H
zf_`E0QnCvhBBxB8=j1UPDh
zj^M&pSR*7M4Yv5Sz7Ey1Z>|T@ZT8~plv2;)>(q0e;d&JhGi~c->WxwQ{(4a*TQ<28
z$wrNmil4&!5^p_)lNEEOK*XYTz7CIT#PDcEfZ+n=9M^?LV9QtzVLdN{ZW8$
z`9gjjO260agmx1)m|jiIBKb2UQ60awO3ppy*a=K{OatL^o==6xas9P5KbG?D)!2e_udLvrE|Zvi)NF+6fjEpjtif$6
z^B^L9Yq|$Q0#8l%KuE~L+UNz!^=qO0*ApKsELfB))(%teT>o}V<0d=4~R*wiwRPr7R=Z48BMfaw|
z9tw#t(c5U`;nqHu7B%v2oFc=^<`B8tqZFlIF*H>v>#6zdXJ`2vSc$#Iwi@iCS^DXE
z-9$rgY;RpnfCDN>hqBx`^hd4F6dsy?fC6xX+Zgj1k|>W3tR-jZv52;3Gy_*wga)FU
zv&e9D7SB1b8exj75AgGW6_$XK0e&z&t5R0UaK*vhk}3g`p*{rJ{R9~elbB3dogk!K
zIGdfO4^*Fsr*45x79uDCRC1{6o|ZRj3V8b`V`
zpalGpE)7TkKSE&dDR8Kc0X|n2zlQr|IEZ!4S*i|>*-<#NAR=RqV_>rT7z=}n38U5P
z7{Ct|FzVn0!1u?%_>JTRL;Q}x&j=Cze2Qr(^H11uL_?W>qBGxOt#_J`97ALc!Scv!X65*Ek5`9f9b_!R0XH(BqF0Zd>WHc6`blavs4wFf(%0k
zk-!}O1hV<@o5mTdDhx+}{fV7M*cSZtNmm$@!2YBw3=(Yr1nhqr3LI9rY`HWg)Gs|=
z{fB`?qJ=c>cGb=nZkDp+V$Pw`@Li(Z@Rh~@7tJ>2fLbBj9uDD`1lCZGe2rOxZU0Xr
zY^fdZv*eSIdU2&+7!pAV9umftE@m#eqt4KKG-&$G3%@zIe5nhNb^@3iOJw}?oK6H<
z30(V5B;lEEX^H=+UI~A+vC3?U^X3JO$ng(YtkNA!KbKbD#3qb7qULC^3_ROru3Gy^
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": "H4sIAAAAAAAA/8xabW/juBH+nl8xp22R3t7Ksp2XTb2yCzSbRVHcpotLtrh+KmiJtoilSFWkskmD/PeC1BspSo6d2NgiHyIOqWeGM88Mh4LDnz7+4/L2X1+uIJEpXRwdheV/gDDBKFYPAKEkkuLFl5xLHnEKH3lUpJhJJAlnYVDOlitTLBFECcoFlnPv6+0n/8Krpihh3yDHdO4J+UCxSDCWHsiHDM89ie9lEAnhQZLj1dxLpMxmQbDiTIrRmvM1xSgjYhTxVC37ywqlhD7Mvy4LJovZ6Xj87v14/O50PCYSURJ5QaVTayqfAZY8foDHagDwncQymcH5GKcfGmGK8jVhM5jgFFAheTsTccrzGbyZTqetUBnol8bMwCvN8d6BQEz4Audk1S7NUBwTtvaXXEqezuC0Vft0VD0kE8M+jf0dk3UiZ8B4niLaoi15HuO8AZtk9yA4JTG8QQgNKx2PzvC9q3ZqqN0HsuHH0RlOYeyqPPkhO0WGVsU5P8YRzzWPlWaG3Xifnb/H0zMHSaIlxS6bJuPxHzv0EOS/eAYXprzaU8QpRZnAM6ifXDUqC4dc9X48NjBR9G2d84LFfm16HKk/F1MngsxnTCZ+lBAa/wnfYfazSQIXbLVUfy5Y7HDHClIURU6QqujAtCdCMoasGyTCYsykTkqXYS63FISxt8nPQ3jjDxC8hWsOpQA4gxXJhYQMCFMwb4MudvAWbnXk+QpWBNNYtItGWuCXzJBxxwT16ie1oH3BYI1ZDJ5Dm1Zotw8ZfjXYSQX2K1pi2oN2vgvYaQX2EYsoJ5lKqx5Is672OhbfS8wE4cx0biPc5OCretG2ftmI+hJHbwSsnf1XJPYDWDv8ukiXOO+BPNsV8WxPIWRFCneIFliMzCCyIt0Uv2uUbu+YAazpcz7ZCe1kP/4QEaIoLz2iex7LLeWsr2d9PVubkhu1K6nK/klP52DqijiTWDVOrYY3kke+kiPCcA4FNWApEdLXjZJW3T0H64OV4lW3BFPCsF9bNbFOuJ7q3FoCC6AEFtZpbB1sS07jvi1+IhSDOhEJW0NM7qzaS5Ut5dQzx3JMREbRw6w8xHduNeq9narOxu1w+gzq6bC6fraN8iNM6WZMp5dBlKzZDHLlwy1xDfYkGI4/H7+D46tjQCyG49+PYYniNRb6MEww3PJLw+F6rsfTo3OTIg07bHFjFGGaREvKo28fjgaYZb9r7jXCTOL8w/Mssnqxc0UGp9G7+PMSnV5sbqhWq3F0Ybzb0Fz3M+rSUD75Vp70tEV2N9VQL0cxKYRKs3s7+GFQXWXK0U++D18FziEqhOQpXN7cgO+/4KLVrhgpqb43hUF59VOPqlWslSYTIPHc09c9b/A2mEya9dNFU5Muq5oUBsm0nlcJrAHN2uTVt7WwoPVsIwN4fMwRW2MYqVIgnp6aCTX1B5Uf/2bqDJnNYaQOE2tFSMnCGAKEqHLDm8fHarm3aB7DAHWWF9QWGPZ8xkKgdcekAbU9yj8VlNYGhCJDDCKKhJh7Os28xecwUFJl3K+crQcMLJniqnt8xCx2LGtsv2JFeijDrw5qeNMovsz6ljBPT37bdfbv5PdqJ4p5PsV3mLbtptjXjm5wfkeig9Hopo3GHiIRBnZC2O9131D2t8a6LY+3uCmbpH/qJkk13dqtJmqrMQxicldVkoGisLkg6PJTecc8V41iEyZTXYL6i0MyNbZTFcVbnhkerWysBpl62WgjFUa2ODrqMKCnkITJSW2HGeBOSiUnhuWblSl1ZAWjvyGhr6Q23cKy92x801z2vE45lO0nQlOaL0IZLzRwGMhYj1Q0m4G+azYjw8pSFsi8oyjo0RTK8mzq0rShgrOv1r6+NJKxGVzp7Kte5OSb2poRjnJYEncYRS1WXnhGlxvGKnIf8QoVVOpUeXqqRjPQq82ZKgl18B0NjqOH0txxdRhoQrjJ7hJsoEaHS0u3zbnODXUn3jX6+rmnrt7NoLwiHpiJmw+p/ws2WiiXZStG2LqD107sxPPSyTsTvY/nsBXR+xUdgu32qFvJu23V/sp46zora5pPGJ6dWn2ULZNFKX1pNvTkQl8mNO7QgXJzoDcDtuD/Vuwa4NYQS1yOuAxx+NFhh8OGTXWwpcRgKzrQblo02bp4buLCAQvnrlTZUDJfQ5fXFstDlcrXUHm/ZfIgCTB8tRmuiD+kGn7GMuExWEXxN/yfAgsJVi78hkXGmcC2dN9ZUJpzwBSo9tbhbiV9UULUjnEgS/HWmD+mZEOXvfUFcNvLajKtfzBhMnHo6377qavDi5qPoyznktsku+ZSaapGl7/8Yk//Hd0hW/LlQSacGTLDYR1GdtnYpq7eQfeWmNe5qz8F1uE+6mGlscCNYc1ctbEN85dZ9gyC2vszS0pn9C+ymWSzyGKQwZ4wKMVhUP185n8BAAD//1GAliZQIwAA",
+ "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}}
+
+ {{.LongName}} Fields
+
+
+
+
+
+
+
+ Field
+ Type
+ Label
+ Description
+
+
+
+ {{range .Fields}}
+
+ {{.Name}}
+ {{.LongType}}
+ {{.Label}}
+ {{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}}
+
+ {{end}}
+
+
+
+ {{end}}
+ {{if .HasExtensions}}
+
+ {{.LongName}} 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}}
+
+ {{.LongName}} 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}}
+
+ {{.Name}} 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_description}}{{#p}}{{file_description}}{{/p}}{{/file_description}}
- {{#file_messages}}
- {{message_long_name}}
- {{#p}}{{message_description}}{{/p}}
- {{#message_has_fields}}
-
-
- Field Type Label Description
-
-
- {{#message_fields}}
-
- {{field_name}}
- {{field_long_type}}
- {{field_label}}
- {{#p}}{{field_description}}{{#field_default_value}} Default: {{field_default_value}}{{/field_default_value}}{{/p}}
-
- {{/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}}
- {{#p}}{{extension_description}}{{#extension_default_value}} Default: {{extension_default_value}}{{/extension_default_value}}{{/p}}
-
- {{/message_extensions}}
-
-
- {{/message_has_extensions}}
- {{/file_messages}}
- {{#file_enums}}
- {{enum_long_name}}
- {{#p}}{{enum_description}}{{/p}}
-
-
- Name Number Description
-
-
- {{#enum_values}}
-
- {{value_name}}
- {{value_number}}
- {{#p}}{{value_description}}{{/p}}
-
- {{/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}}
- {{#p}}{{extension_description}}{{#extension_default_value}} Default: {{extension_default_value}}{{/extension_default_value}}{{/p}}
-
- {{/file_extensions}}
-
-
- {{/file_has_extensions}}
- {{#file_services}}
- {{service_name}}
- {{#p}}{{service_description}}{{/p}}
-
- {{/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/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}}
+
+ {{.Description}}
+
+ {{range .Messages}}
+ {{.LongName}}
+ {{.Description}}
+
+ {{if .HasFields}}
+
+
+ Field Type Label Description
+
+
+ {{range .Fields}}
+
+ {{.Name}}
+ {{.LongType}}
+ {{.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}}
+
+ {{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/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}}
-
- {{message_long_name}} 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}}
-
- {{message_long_name}} 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}}
-
- {{enum_long_name}} 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}}
-
- {{service_name}} 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}}
{{.LongName}} 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}}
{{.LongName}} 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}}
{{.Name}} 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 @@
- {{.Description}}
+ {{p .Description}}
{{range .Messages}}
{{.LongName}}
- {{.Description}}
+ {{p .Description}}
{{if .HasFields}}
@@ -226,7 +226,7 @@
{{.Name}}
{{.LongType}}
{{.Label}}
- {{.Description}} {{if .DefaultValue}}Default: {{.DefaultValue}}{{end}}
+ {{.Description}} {{if .DefaultValue}}Default: {{.DefaultValue}}{{end}}
{{end}}
@@ -256,7 +256,7 @@
{{range .Enums}}
{{.LongName}}
- {{.Description}}
+ {{p .Description}}
Name Number Description
@@ -295,7 +295,7 @@
{{range .Services}}
{{.Name}}
- {{.Description}}
+ {{p .Description}}
Method Name Request Type Response Type Description
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 completely fictional.
+
Booking
Represents the booking of a vehicle. Vehicles are some cool shit. But drive carefully!
+
Booking 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.
+
BookingStatus 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.
+
Address 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.
+
Customer 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 testing This 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.
+
Manufacturer 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.
+
Model 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.
+
Vehicle 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.
+
+
+
Vehicle 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".
+
Vehicle.Category 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 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
- 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.
-
-
- Field Type Label Description
-
-
-
- id
- int32
- required
- Unique booking status ID.
-
-
- description
- string
- required
- Booking status description. E.g. "Active".
-
-
-
- BookingService
- Service for handling vehicle bookings.
-
-
- Method Name Request Type Response Type Description
-
-
-
- BookVehicle
- Booking
- BookingStatus
- Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.
-
-
-
-
- This file has messages for describing a customer.
Author: Elvis Stansvik
- Address
- Represents a mail address.
-
-
- Field Type Label Description
-
-
-
- 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.
-
-
- Field Type Label Description
-
-
-
- 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 Top
-
- Special cases for testing
This is of course entirely fictional/useless model.
- Empty
- Represents a message with no fields
-
- Messages describing manufacturers / vehicles.
- Manufacturer
- Represents a manufacturer of cars.
-
-
- Field Type Label Description
-
-
-
- id
- int32
- required
- The unique manufacturer ID.
-
-
- code
- string
- required
- A manufacturer code, e.g. "DKL4P".
-
-
- details
- string
- optional
- Manufacturer details (minimum orders et.c.).
-
-
- category
- Manufacturer.Category
- optional
- Manufacturer category. Default: CATEGORY_EXTERNAL
-
-
-
- Model
- Represents a vehicle model.
-
-
- Field Type Label Description
-
-
-
- id
- string
- required
- The unique model ID.
-
-
- model_code
- string
- required
- The car model code, e.g. "PZ003".
-
-
- model_name
- string
- required
- 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.
-
-
- Field Type Label Description
-
-
-
- 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
-
-
- daily_hire_rate_cents
- 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".
-
-
- Field Type Label Description
-
-
-
- code
- string
- required
- Category code. E.g. "S".
-
-
- description
- string
- required
- Category name. E.g. "Sedan".
-
-
-
- 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.
-
-
-
- File-level Extensions
-
-
- Extension Type Base Number Description
-
-
-
- country
- string
- Manufacturer
- 100
- Manufacturer country. Default: "China"
-
-
-
+
+
+
+ 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!
+
+
+
+
+ Field Type Label Description
+
+
+
+
+ vehicle_id
+ int32
+
+ ID of booked vehicle.
+
+
+
+ customer_id
+ int32
+
+ Customer that booked the vehicle.
+
+
+
+ status
+ BookingStatus
+
+ Status of the booking.
+
+
+
+ confirmation_sent
+ bool
+
+ Has booking confirmation been sent?
+
+
+
+ payment_received
+ bool
+
+ Has payment been received?
+
+
+
+
+
+
+
+
+ BookingStatus
+ Represents the status of a vehicle booking.
+
+
+
+
+ Field Type Label Description
+
+
+
+
+ id
+ int32
+
+ Unique booking status ID.
+
+
+
+ description
+ 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
+ BookingStatus
+ Used to book a vehicle. Pass in a Booking and a BookingStatus will be returned.
+
+
+
+
+
+
+
+
+ This file has messages for describing a customer.
+
+
+ Address
+ Represents a mail address.
+
+
+
+
+ Field Type Label Description
+
+
+
+
+ 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.
+
+
+
+
+ Field Type Label Description
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Messages describing manufacturers / vehicles.
+
+
+ Manufacturer
+ Represents a manufacturer of cars.
+
+
+
+
+ Field Type Label Description
+
+
+
+
+ id
+ int32
+ required
+ The unique manufacturer ID.
+
+
+
+ code
+ string
+ required
+ A manufacturer code, e.g. "DKL4P".
+
+
+
+ details
+ string
+ optional
+ Manufacturer details (minimum orders et.c.).
+
+
+
+ category
+ Manufacturer.Category
+ optional
+ Manufacturer category.
+
+
+
+
+
+
+
+
+ Model
+ Represents a vehicle model.
+
+
+
+
+ Field Type Label Description
+
+
+
+
+ id
+ string
+ required
+ The unique model ID.
+
+
+
+ model_code
+ string
+ required
+ The car model code, e.g. "PZ003".
+
+
+
+ model_name
+ string
+ required
+ 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.
+
+
+
+
+ Field Type Label Description
+
+
+
+
+ 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.
+
+
+
+ daily_hire_rate_cents
+ 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".
+
+
+
+
+ Field Type Label Description
+
+
+
+
+ code
+ string
+ required
+ Category code. E.g. "S".
+
+
+
+ description
+ string
+ required
+ Category name. E.g. "Sedan".
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+ File-level Extensions
+
+
+ Extension Type Base Number Description
+
+
+
+
+ country
+ string
+ Manufacturer
+ 100
+ Manufacturer country. Default: China
+
+
+
+
+
+
+
+
Scalar Value Types
@@ -658,113 +786,129 @@ 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
+
+
-