├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── build.sh ├── go_template ├── __init__.py ├── bind │ ├── template.go │ ├── template.h │ └── template.so ├── utils.py └── wrapper.py ├── requirements.txt ├── setup.py ├── setup_requirements.txt └── tests ├── __init__.py ├── output.txt ├── sample.tmpl ├── test.txt ├── test_utils.py └── values.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: python 4 | 5 | python: 6 | - 3.6 7 | 8 | install: 9 | - pip install -r setup_requirements.txt 10 | 11 | script: 12 | - python -m unittest discover 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Harsh Jain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-template 2 | 3 | [![PyPI Version](https://img.shields.io/pypi/v/go_template.svg)](https://pypi.python.org/pypi/go_template) 4 | [![Wheel Status](https://img.shields.io/badge/wheel-yes-brightgreen.svg)](https://pypi.python.org/pypi/go_template) 5 | ![Build Status](https://img.shields.io/travis/SixQuant/project-template-python/master.svg) 6 | 7 | ## Overview 8 | Python bindings for go text/template 9 | 10 | 11 | 12 | ## Quickstart 13 | 14 | ### Pip 15 | `go_template` works Python 2.7, 3.5, 3.6, 3.7. 16 | ``` 17 | pip install go_template 18 | ``` 19 | 20 | ## Example 21 | 22 | Content of sample.tmpl 23 | ``` 24 | {{.Count}} items are made of {{.Material}} 25 | ``` 26 | Content of values.yml 27 | ``` 28 | Count: 12 29 | Material: Wool 30 | ``` 31 | 32 | 1) Print rendered output to stdout 33 | ``` 34 | >>> import go_template 35 | >>> go_template.render_template('tests/sample.tmpl','tests/values.yml','') 36 | 12 items are made of Wool 37 | ``` 38 | 39 | 2) Get rendered output in a file 40 | ``` 41 | >>> import go_template 42 | >>> go_template.render_template('tests/sample.tmpl','tests/values.yml','output.txt') 43 | 44 | ``` 45 | Content of output.txt 46 | ``` 47 | 12 items are made of Wool 48 | ``` 49 | 50 | 51 | __NOTE__: Paths provided to render_template should either be absolute path or relative to directory where it is ran. 52 | 53 | ## Build shared library 54 | 55 | 56 | For building a fresh shared object of text/template, you must have golang^1.5 installed. 57 | 58 | ``` 59 | ./build.sh 60 | ``` 61 | 62 | This will create [template.so](https://github.com/harsh-98/go-template/blob/master/bind/template.so) in the `bind` folder. 63 | 64 | ## Motivation 65 | Currently, there is no python package which exposes golang `text/template` functionality to python. And I am in the process of learning about interoperability between different languages. So, I started working on this as a learning project. 66 | 67 | ## Explanation 68 | Golang library cannot be directly used in python. Firstly, we have to compile it as shared object or archive for interoperability with C. And then create python bindings for this C object. 69 | 70 | [CPython](https://github.com/python/cpython) is the original Python implementation and provides cpython API for creating python wrapper, but the wrapping code is in C. There is a library [gopy](https://github.com/go-python/gopy) which exactly uses this approach. But it works only on go1.5 and for python2. 71 | 72 | If we want to write the wrapping code in python, there are [Cython](https://cython.org/) and [ctypes](https://docs.python.org/3/library/ctypes.html). Ctypes allow directly importing the C library and calling functions through its interface. This project uses `ctypes` for calling go functions. 73 | 74 | When a golang library is compiled as shared object, [cgo](https://golang.org/cmd/cgo/) handles exposing functions and data type conversion. Using ctypes, we can only modify simple data type, string, int, float, bool. I tried converting python class to golang struct, but it failed. 75 | 76 | So, I created a golang wrapper over text/template library, which takes simple datatypes. And handles complex operation in this layer. Then a python wrapper over this layer using `ctypes`. 77 | 78 | It is far from complete and doesn't use the best approach. Currently, it has only one function which takes path of template and value file. And depending on the third argument, either writes to stdout if empty or to file if given its path. 79 | 80 | ## License 81 | 82 | This project is licensed under [MIT](https://github.com/harsh-98/go-template/blob/master/LICENSE) License. -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | go build -buildmode=c-shared -o bind/template.so bind/*.go 2 | -------------------------------------------------------------------------------- /go_template/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | __version__ = '0.0.3' 4 | 5 | from .wrapper import render_template -------------------------------------------------------------------------------- /go_template/bind/template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | import ( 5 | "fmt" 6 | "os" 7 | "io" 8 | "path" 9 | "gopkg.in/yaml.v2" 10 | "text/template" 11 | "io/ioutil" 12 | ) 13 | 14 | type store struct{ 15 | FileName string ; 16 | Values map[string]interface{}; 17 | } 18 | 19 | func tpl(fileName string, vals interface{}, output string) error { 20 | name := path.Base(fileName) 21 | tmpl, err := template.New(name).ParseFiles(fileName) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | var file io.Writer 27 | if output != "" { 28 | f, _ :=os.Create(output) 29 | defer f.Close() 30 | file = f 31 | } else { 32 | file = os.Stdout 33 | } 34 | 35 | err = tmpl.Execute(file, vals) 36 | if err != nil { 37 | return err 38 | } 39 | return nil 40 | } 41 | 42 | func (s *store)getValues() { 43 | yamlFile, err := ioutil.ReadFile(s.FileName) 44 | if err != nil { 45 | fmt.Printf("yamlFile.Get err #%v ", err) 46 | } 47 | err = yaml.Unmarshal(yamlFile, &s.Values) 48 | if err != nil { 49 | panic(err) 50 | } 51 | } 52 | 53 | //export RenderTemplate 54 | func RenderTemplate(template, fileName, output string){ 55 | s := store{FileName: fileName} 56 | s.getValues() 57 | err := tpl(template, s.Values, output) 58 | if err != nil { 59 | panic(err) 60 | } 61 | } 62 | 63 | func main(){ 64 | // RenderTemplate("sample.tmpl", "values.yml", "") 65 | } -------------------------------------------------------------------------------- /go_template/bind/template.h: -------------------------------------------------------------------------------- 1 | /* Code generated by cmd/cgo; DO NOT EDIT. */ 2 | 3 | /* package command-line-arguments */ 4 | 5 | 6 | #line 1 "cgo-builtin-prolog" 7 | 8 | #include /* for ptrdiff_t below */ 9 | 10 | #ifndef GO_CGO_EXPORT_PROLOGUE_H 11 | #define GO_CGO_EXPORT_PROLOGUE_H 12 | 13 | typedef struct { const char *p; ptrdiff_t n; } _GoString_; 14 | 15 | #endif 16 | 17 | /* Start of preamble from import "C" comments. */ 18 | 19 | 20 | 21 | 22 | /* End of preamble from import "C" comments. */ 23 | 24 | 25 | /* Start of boilerplate cgo prologue. */ 26 | #line 1 "cgo-gcc-export-header-prolog" 27 | 28 | #ifndef GO_CGO_PROLOGUE_H 29 | #define GO_CGO_PROLOGUE_H 30 | 31 | typedef signed char GoInt8; 32 | typedef unsigned char GoUint8; 33 | typedef short GoInt16; 34 | typedef unsigned short GoUint16; 35 | typedef int GoInt32; 36 | typedef unsigned int GoUint32; 37 | typedef long long GoInt64; 38 | typedef unsigned long long GoUint64; 39 | typedef GoInt64 GoInt; 40 | typedef GoUint64 GoUint; 41 | typedef __SIZE_TYPE__ GoUintptr; 42 | typedef float GoFloat32; 43 | typedef double GoFloat64; 44 | typedef float _Complex GoComplex64; 45 | typedef double _Complex GoComplex128; 46 | 47 | /* 48 | static assertion to make sure the file is being used on architecture 49 | at least with matching size of GoInt. 50 | */ 51 | typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; 52 | 53 | typedef _GoString_ GoString; 54 | typedef void *GoMap; 55 | typedef void *GoChan; 56 | typedef struct { void *t; void *v; } GoInterface; 57 | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; 58 | 59 | #endif 60 | 61 | /* End of boilerplate cgo prologue. */ 62 | 63 | #ifdef __cplusplus 64 | extern "C" { 65 | #endif 66 | 67 | 68 | extern void RenderTemplate(GoString p0, GoString p1, GoString p2); 69 | 70 | #ifdef __cplusplus 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /go_template/bind/template.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsh-98/go-template/203c02b565c1a9e289dc41227be3f72e6be2885f/go_template/bind/template.so -------------------------------------------------------------------------------- /go_template/utils.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | def sha256sum(filename): 4 | h = hashlib.sha256() 5 | b = bytearray(128*1024) 6 | mv = memoryview(b) 7 | with open(filename, 'rb', buffering=0) as f: 8 | for n in iter(lambda : f.readinto(mv), 0): 9 | h.update(mv[:n]) 10 | return h.hexdigest() -------------------------------------------------------------------------------- /go_template/wrapper.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | import os 3 | 4 | root_dir = os.path.dirname(__file__) 5 | shared_lib = os.path.join(root_dir, 'bind', 'template.so') 6 | lib = cdll.LoadLibrary(shared_lib) 7 | 8 | class GoString(Structure): 9 | _fields_ = [("p", c_char_p), ("n", c_longlong)] 10 | 11 | def get_go_string(val): 12 | return GoString(val.encode('utf-8'), len(val)) 13 | 14 | def get_go_path(file): 15 | if not os.path.isabs(file) and file: 16 | file = os.path.join(os.getcwd(),file) 17 | return get_go_string(file) 18 | 19 | def render_template(template, value_file, output): 20 | template = get_go_path(template) 21 | value_file = get_go_path(value_file) 22 | output = get_go_path(output) 23 | 24 | lib.RenderTemplate.argtypes = [GoString, GoString, GoString] 25 | lib.RenderTemplate(template, value_file, output) 26 | 27 | # render_template('tests/sample.tmpl', 'tests/values.yml','') -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | #Dependency 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | import go_template 4 | 5 | def install_deps(): 6 | """Reads requirements.txt and preprocess it 7 | to be feed into setuptools. 8 | This is the only possible way (we found) 9 | how requirements.txt can be reused in setup.py 10 | using dependencies from private github repositories. 11 | Links must be appendend by `-{StringWithAtLeastOneNumber}` 12 | or something like that, so e.g. `-9231` works as well as 13 | `1.1.0`. This is ignored by the setuptools, but has to be there. 14 | Returns: 15 | list of packages and dependency links. 16 | """ 17 | with open('requirements.txt', 'r') as f: 18 | packages = f.readlines() 19 | new_pkgs = [] 20 | for resource in packages: 21 | new_pkgs.append(resource.strip()) 22 | return new_pkgs 23 | 24 | def readme(): 25 | with open('README.md','r') as f: 26 | text = f.read() 27 | return text 28 | setup( 29 | name='go-template', 30 | version=go_template.__version__, 31 | packages=find_packages(exclude=("tests",)), 32 | description='python bindings for go template', 33 | author='harsh', 34 | long_description=readme(), 35 | long_description_content_type="text/markdown", 36 | author_email='harshjniitr@gmail.com', 37 | license='MIT', 38 | url='https://github.com/harsh-98/go-template', 39 | keywords='golang template bindings wrapper', 40 | install_requires=install_deps(), 41 | package_data={'go_template': ['bind/template.so']}, # 42 | # include_package_data=True, # 43 | # data_files=[('bind', ['bind/template.so']), ('',['requirements.txt'])], # 44 | classifiers=['Development Status :: 3 - Alpha', 45 | 'Programming Language :: Python :: 2.6', 46 | 'Programming Language :: Python :: 2.7', 47 | 'Programming Language :: Python :: 3.4', 48 | 'Programming Language :: Python :: 3.5', 49 | 'Programming Language :: Python :: 3.6', 50 | 'Programming Language :: Python :: 3.7', 51 | 'License :: OSI Approved :: MIT License'], 52 | setup_requires=['wheel'] 53 | ) 54 | -------------------------------------------------------------------------------- /setup_requirements.txt: -------------------------------------------------------------------------------- 1 | twine==1.13.0 2 | wheel 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsh-98/go-template/203c02b565c1a9e289dc41227be3f72e6be2885f/tests/__init__.py -------------------------------------------------------------------------------- /tests/output.txt: -------------------------------------------------------------------------------- 1 | 12 items are made of Wool -------------------------------------------------------------------------------- /tests/sample.tmpl: -------------------------------------------------------------------------------- 1 | {{.Count}} items are made of {{.Material}} -------------------------------------------------------------------------------- /tests/test.txt: -------------------------------------------------------------------------------- 1 | 12 items are made of Wool -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | import go_template 6 | from go_template.utils import sha256sum 7 | 8 | class TestMethods(unittest.TestCase): 9 | def test_add(self): 10 | test_dir = os.path.dirname(__file__) 11 | go_template.render_template( 12 | os.path.join(test_dir, 'sample.tmpl'), 13 | os.path.join(test_dir, 'values.yml'), 14 | os.path.join(test_dir, 'output.txt')) 15 | 16 | output_hash = sha256sum(os.path.join(test_dir, 'output.txt')) 17 | test_hash = sha256sum(os.path.join(test_dir, 'test.txt')) 18 | self.assertEqual(output_hash, test_hash) 19 | 20 | if __name__ == '__main__': 21 | unittest.main() 22 | -------------------------------------------------------------------------------- /tests/values.yml: -------------------------------------------------------------------------------- 1 | Count: 12 2 | Material: Wool --------------------------------------------------------------------------------