Last active
December 13, 2015 20:18
-
-
Save jtacoma/4969086 to your computer and use it in GitHub Desktop.
gozmqgen: some scripts for generating ZMQ-related Go code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.cache | |
getsockopt-*.go | |
setsockopt-*.go | |
socket*.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// +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}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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