Skip to content

Instantly share code, notes, and snippets.

@jtacoma
Last active December 13, 2015 20:18
Show Gist options
  • Save jtacoma/4969086 to your computer and use it in GitHub Desktop.
Save jtacoma/4969086 to your computer and use it in GitHub Desktop.
gozmqgen: some scripts for generating ZMQ-related Go code.
.cache
getsockopt-*.go
setsockopt-*.go
socket*.go
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"text/template"
)
var modelsource = flag.String("model", "", "Name of model file or - to read STDIN.")
var templsource = flag.String("template", "", "Name of template file or - to read STDIN.")
func mustRead(name string) []byte {
var raw []byte
var err error
if name == "-" {
raw, err = ioutil.ReadAll(os.Stdin)
} else {
raw, err = ioutil.ReadFile(name)
}
if err != nil {
fmt.Errorf(err.Error())
}
return raw
}
func main() {
flag.Parse()
if len(*modelsource) == 0 {
fmt.Errorf("'model' is required.")
}
if len(*templsource) == 0 {
fmt.Errorf("'template' is required.")
}
if *modelsource == "-" && *templsource == "-" {
fmt.Errorf("'model' and 'template' cannot both be STDIN.")
}
raw_json := mustRead(*modelsource)
model := make(map[string]interface{})
if err := json.Unmarshal(raw_json, &model); err != nil {
fmt.Errorf(err.Error())
}
raw_template := mustRead(*templsource)
t, err := template.New("main").Parse(string(raw_template))
if err != nil {
fmt.Errorf(err.Error())
}
err = t.Execute(os.Stdout, model)
if err != nil {
fmt.Errorf(err.Error())
}
}
#!/usr/bin/env bash
for v in 2.1 2.2 3.2 ; do
n=`echo $v | tr . _`
./zgen --zmq $v --page setsockopt,getsockopt | go run got.go -model - -template socket.template > socket_$n.go
done
// +build {{.build}}
//
{{range .copyright}}// {{.}}
{{end}}
package zmqutil
import (
"time"
zmq "github.com/alecthomas/gozmq"
)
// This file was {{/*NOT */}}generated automatically. Changes made here will {{/*NOT */}}be lost.
// Socket Option Getters{{with .getsockopt}}{{range .options}}
// {{.summary}}
//
// See: {{.cite}}
//
func (s *Socket) {{.nicename}}() ({{.gotype}}, error) {
{{if .duration}}ms, err := s.base.GetSockOpt{{.ztype}}(zmq.{{.shortname}})
return time.Duration(ms) * time.Millisecond, err{{else}}return s.base.GetSockOpt{{.ztype}}(zmq.{{.shortname}}){{end}}
}{{end}}{{end}}
// Socket Option Setters{{with .setsockopt}}{{range .options}}
// {{.summary}}
//
// See: {{.cite}}
//
func (s *Socket) Set{{.nicename}}(value {{.gotype}}) error {
{{if .duration}}return s.base.SetSockOpt{{.ztype}}(zmq.{{.shortname}}, {{.lowtype}}(time.Duration(value)/time.Millisecond)){{else}}return s.base.SetSockOpt{{.ztype}}(zmq.{{.shortname}}, value){{end}}
}{{end}}{{end}}
#!/usr/bin/env python3
from html.parser import HTMLParser
from http import client
from os import path
import argparse
import json
import os
import re
import subprocess
import sys
import textwrap
## Configuration
parser = argparse.ArgumentParser(description='Generate socket option setters.')
parser.add_argument('--zmq', dest='zmq', default='3.2',
help='Version of libzmq (default is the latest stable version i.e. 3.2)')
parser.add_argument('--page', dest='page', default='setsockopt',
help='Which manual to generate code from (default is setsockopt)')
args = parser.parse_args()
gotypes = {
'binary data': {None:'string'},
'character string': {None:'string'},
'int': {None:'int', 'milliseconds':'time.Duration'},
'int on POSIX systems, SOCKET on Windows': {None:'int'},
'int64_t': {None:'int64', 'milliseconds':'time.Duration'},
'uint64_t': {None:'uint64'},
'uint32_t': {None:'uint32'},
}
ztypes = {
'binary data': {None:'String'},
'character string': {None:'String'},
'int': {None:'Int'},
'int on POSIX systems, SOCKET on Windows': {None:'Int'},
'int64_t': {None:'Int64'},
'uint64_t': {None:'UInt64'},
'uint32_t': {None:'UInt32'},
}
lowtypes = {
'int64_t': 'int64',
'uint64_t': 'uint64',
'uint32_t': 'uint32',
}
replacements = {
'hwm': 'HWM',
'Ipv4only': 'IPV4Only',
'msg': 'Msg',
'pub': 'PUB',
'size': 'Size',
'Tcp': 'TCP',
'timeo': 'Timeout',
}
noset = [
'Subscribe',
'Unsubscribe',
]
cachedir = path.join('.cache', 'codegen')
fixedtypes = {
'EVENTS': 'uint64_t',
'FD': 'int',
'RATE': 'int64_t',
'RECOVERY_IVL': 'int64_t',
'SNDBUF': 'uint64_t',
'RCVBUF': 'uint64_t',
'RCVMORE': 'uint64_t',
'TYPE': 'uint64_t',
}
fixedunits = {
'XPUB_VERBOSE': 'boolean',
}
ignore = [
'DELAY_ATTACH_ON_CONNECT',
'FD',
'IPV4ONLY',
'LAST_ENDPOINT',
'MAXMSGSIZE',
'MULTICAST_HOPS',
'ROUTER_MANDATORY',
'TCP_ACCEPT_FILTER',
'XPUB_VERBOSE',
]
buildtags = {
'2.1': 'zmq_2_1',
'2.2': '!zmq_2_1,!zmq_3_x',
'3.2': 'zmq_3_x',
}
## Code
def fix(s):
for key in replacements:
s = s.replace(key, replacements[key])
return s
def load_manual(version, funcname):
pagename = '%s:%s' % (version, funcname)
pagename = pagename.replace('.', '-').replace('_', '-')
cache = path.join(cachedir, pagename)
if path.exists(cache):
return open(cache, 'r').read(), pagename
host = 'api.zeromq.org'
conn = client.HTTPConnection('api.zeromq.org')
conn.request('GET', '/' + pagename)
rep = conn.getresponse()
if rep.status != 200:
raise Exception('%s/%s %s %s' % (host, pagename, rep.status, rep.reason))
data = rep.read()
data = str(data, encoding='utf-8') #TODO: get encoding from headers
if not path.exists(cachedir):
os.makedirs(cachedir)
cachefile = open(cache, 'w')
cachefile.write(data)
cachefile.close()
return data, pagename
class Option (object):
typ = ''
unit = ''
def __init__(self, const):
self.const = const
self.zconst = const.split('_', 1)[-1]
self.name = ''.join(map(lambda s: s[0] + s[1:].lower(), const.split('_')[1:]))
self.name = fix(self.name)
self.desc = []
def describe(self, line):
if line.strip():
self.desc.append(line)
def comment(self, cite=None, width=80):
desc = self.desc[1:]
if cite:
desc = [cite] + [''] + desc[1:]
result = ''
for line in desc:
if line.startswith(' '):
result += '%s\n' % line.rstrip()
else:
wrapped = textwrap.wrap(line, width)
result += '\n'.join(wrapped) + '\n%s\n' % wrapped
return result
def summary(self):
return '%s: %s.' % (self.const, self.desc[0])
def pod(self):
return {
'fullname':self.const,
'shortname':self.zconst,
'nicename':self.name,
'summary':self.summary(),
'description':self.comment(width=72).splitlines(),
'ctype':self.typ,
'gotype':self.gotype,
'ztype':self.ztype,
'lowtype':self.lowtype,
'anchor':self.anchor,
'duration':getattr(self, 'duration', False),
}
def __str__(self):
return self.name
def __repr__(self):
return '<Option name=%s>' % self.name
class OptionsBuilder:
def __init__(self):
self.options = []
def maybe_add(self, name, info):
if not name.strip().startswith('ZMQ_'):
return False
self.options.append(Option(name))
self.options[-1].describe(info)
return True
def describe(self, info):
if self.options:
self.options[-1].describe(info)
def set_property(self, name, value):
if not self.options:
return False
name = name.strip()
option = self.options[-1]
if name=='Option value type':
option.typ = value
elif name=='Option value unit':
option.unit = value
if option.typ and option.unit:
option.typ = fixedtypes.get(option.zconst, option.typ)
option.lowtype = lowtypes.get(option.typ, option.typ)
gomap = gotypes[option.typ]
option.gotype = gomap.get(option.unit, gomap[None])
zmap = ztypes[option.typ]
option.ztype = zmap.get(option.unit, zmap[None])
if option.gotype=='time.Duration':
option.duration = True
option.describe(' {:<25s} {:s}\n'.format(name, value))
def set_anchor(self, anchor):
self.options[-1].anchor = anchor
class ManualParser (HTMLParser):
state = None
text = ''
text2 = ''
def __init__(self):
super(ManualParser, self).__init__()
self.builder = OptionsBuilder()
def handle_starttag(self, tag, attrs):
attrd = dict(attrs)
if tag=='h3':
self.state = 'title'
self.text = ''
self.text2 = attrd.get('id', 'unknown')
elif self.state=='describing' and tag=='table' and attrd.get('class')=='wiki-content-table':
self.builder.describe(self.text)
self.text = ''
self.state = 'properties'
elif self.state=='properties' and tag=='td':
self.state = 'property-name'
elif self.state=='property-name' and tag=='td':
self.state = 'property-value'
def handle_endtag(self, tag):
if self.state=='title':
if tag=='h3':
if self.builder.maybe_add(*(self.text.split(': ', 1))):
self.state = 'describing'
self.builder.set_anchor(self.text2)
else:
self.state = None
self.text = ''
self.text2 = ''
elif self.state=='describing' and tag in ['p', 'li']:
self.builder.describe(self.text)
self.text = ''
elif self.state=='properties' and tag=='table':
self.state = None
elif self.state=='property-value' and tag=='td':
self.builder.set_property(self.text, self.text2)
self.state = 'properties'
self.text = ''
self.text2 = ''
def handle_data(self, data):
if self.state=='title':
self.text += data
elif self.state=='describing':
self.text += data
elif self.state=='property-name':
self.text += data
elif self.state=='property-value':
self.text2 += data
def parse_options(content):
parser = ManualParser()
parser.feed(content)
return parser.builder.options
def generateo(option, cite=None):
d = dict()
d.update(option.__dict__)
if option.name in noset:
d['set'] = ''
else:
d['set'] = 'Set'
d['comment'] = option.comment(cite=cite).strip()
return gotmpl % d
## Main
def main():
data = {
'build': buildtags[str(args.zmq)],
'version': args.zmq,
}
for page in args.page.split(','):
manual, pagename = load_manual(args.zmq, 'zmq_'+page)
cite = 'http://api.zeromq.org/%s' % pagename
options = [o.pod() for o in parse_options(manual) if o.zconst not in ignore]
for option in options:
option['cite'] = '%s#%s' % (cite, option.get('anchor'))
data[page] = {
'cite': cite,
'options': options,
}
print(json.dumps(data, indent=4))
if __name__=='__main__': main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment