cmd/cue/cmd: support binary file type
- add special binary import mode
- allow specifying custom extensions for import
- encoder/decoder for binary
Note that there are no default file extensions for binary.
Change-Id: Ieeb90edc38b47a05e25e736479730e857345d853
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9570
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index 7caf830..1840f97 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -19,6 +19,7 @@
"io"
"os"
"path/filepath"
+ "regexp"
"strings"
"golang.org/x/text/language"
@@ -357,8 +358,12 @@
outMode filetypes.Mode
fileFilter string
+ reFile *regexp.Regexp
+ encoding build.Encoding
interpretation build.Interpretation
+ overrideDefault bool
+
noMerge bool // do not merge individual data files.
loadCfg *load.Config
@@ -378,12 +383,21 @@
if err := p.parseFlags(); err != nil {
return nil, err
}
+ re, err := regexp.Compile(p.cfg.fileFilter)
+ if err != nil {
+ return nil, err
+ }
+ cfg.reFile = re
cfg.loadCfg.Tags = flagInject.StringArray(cmd)
return p, nil
}
+func (p *buildPlan) matchFile(file string) bool {
+ return p.cfg.reFile.MatchString(file)
+}
+
type decoderInfo struct {
file *build.File
d *encoding.Decoder // may be nil if delayed
@@ -407,9 +421,21 @@
// returned slices. It is up to the caller to Close any of the decoders that are
// returned.
func (p *buildPlan) getDecoders(b *build.Instance) (schemas, values []*decoderInfo, err error) {
- for _, f := range b.OrphanedFiles {
+ files := b.OrphanedFiles
+ if p.cfg.overrideDefault {
+ files = append(files, b.UnknownFiles...)
+ }
+ for _, f := range files {
+ if !p.matchFile(f.Filename) && f.Filename != "-" {
+ continue
+ }
+ if p.cfg.overrideDefault {
+ f.Encoding = p.cfg.encoding
+ f.Interpretation = p.cfg.interpretation
+ }
switch f.Encoding {
- case build.Protobuf, build.YAML, build.JSON, build.JSONL, build.Text:
+ case build.Protobuf, build.YAML, build.JSON, build.JSONL,
+ build.Text, build.Binary:
if f.Interpretation == build.ProtobufJSON {
// Need a schema.
values = append(values, &decoderInfo{f, nil})
diff --git a/cmd/cue/cmd/flags.go b/cmd/cue/cmd/flags.go
index f0ab776..edfa8dd 100644
--- a/cmd/cue/cmd/flags.go
+++ b/cmd/cue/cmd/flags.go
@@ -44,6 +44,7 @@
flagFiles flagName = "files"
flagProtoPath flagName = "proto_path"
flagProtoEnum flagName = "proto_enum"
+ flagExt flagName = "ext"
flagWithContext flagName = "with-context"
flagOut flagName = "out"
flagOutFile flagName = "outfile"
diff --git a/cmd/cue/cmd/help.go b/cmd/cue/cmd/help.go
index a34742c..aadbca6 100644
--- a/cmd/cue/cmd/help.go
+++ b/cmd/cue/cmd/help.go
@@ -189,8 +189,10 @@
textproto .textproto Text-based protocol buffers.
proto .proto Protocol Buffer definitions.
go .go Go source files.
- text .txt Raw text file; the evaluated
- value must be of type string.
+ text .txt Raw text file; the evaluated value
+ must be of type string.
+ binary Raw binary file; the evaluated value
+ must be of type string or bytes.
OpenAPI, JSON Schema and Protocol Buffer definitions are
always interpreted as schema. YAML and JSON are always
diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go
index c57c3c0..15f2432 100644
--- a/cmd/cue/cmd/import.go
+++ b/cmd/cue/cmd/import.go
@@ -55,6 +55,8 @@
json Look for JSON files (.json, .jsonl, .ldjson).
yaml Look for YAML files (.yaml .yml).
text Look for text files (.txt).
+ binary Look for files with extensions specified by --ext
+ and interpret them as binary.
jsonschema Interpret JSON, YAML or CUE files as JSON Schema.
openapi Interpret JSON, YAML or CUE files as OpenAPI.
auto Look for JSON or YAML files and interpret them as
@@ -65,7 +67,9 @@
proto Convert Protocol buffer definition files and
transitive dependencies.
-For user-specified files the modes only affect the
+Using the --ext flag in combination with a mode causes matched files to be
+interpreted as the format indicated by the mode, overriding any other meaning
+attributed to that extension.
auto mode
@@ -101,6 +105,11 @@
The module root is implicitly added as an import path.
+Binary mode
+
+Loads matched files as binary.
+
+
JSON/YAML mode
The -f option allows overwriting of existing files. This only
@@ -249,6 +258,7 @@
cmd.Flags().Bool(string(flagFiles), false, "split multiple entries into different files")
cmd.Flags().Bool(string(flagDryrun), false, "only run simulation")
cmd.Flags().BoolP(string(flagRecursive), "R", false, "recursively parse string values")
+ cmd.Flags().StringArray(string(flagExt), nil, "match files with these extensions")
return cmd
}
@@ -261,11 +271,18 @@
interpretation: build.Auto,
loadCfg: &load.Config{DataFiles: true},
}
+
var mode string
+ extensions := flagExt.StringArray(cmd)
if len(args) >= 1 && !strings.ContainsAny(args[0], `/\:.`) {
c.interpretation = ""
+ if len(extensions) > 0 {
+ c.overrideDefault = true
+ }
+
mode = args[0]
args = args[1:]
+ c.encoding = build.Encoding(mode)
switch mode {
case "proto":
c.fileFilter = `\.proto$`
@@ -275,14 +292,24 @@
c.fileFilter = `\.(yaml|yml)$`
case "text":
c.fileFilter = `\.txt$`
+ case "binary":
+ if len(extensions) == 0 {
+ return errors.Newf(token.NoPos,
+ "use of --ext flag required in binary mode")
+ }
case "auto", "openapi", "jsonschema":
c.interpretation = build.Interpretation(mode)
+ c.encoding = "yaml"
case "data":
// default mode for encoding/ no interpretation.
+ c.encoding = ""
default:
return errors.Newf(token.NoPos, "unknown mode %q", mode)
}
}
+ if len(extensions) > 0 {
+ c.fileFilter = `\.(` + strings.Join(extensions, "|") + `)$`
+ }
b, err := parseArgs(cmd, args, c)
exitOnErr(cmd, err, true)
diff --git a/cmd/cue/cmd/orphans.go b/cmd/cue/cmd/orphans.go
index 13f7c62..810125c 100644
--- a/cmd/cue/cmd/orphans.go
+++ b/cmd/cue/cmd/orphans.go
@@ -17,7 +17,6 @@
import (
"fmt"
"path/filepath"
- "regexp"
"strconv"
"cuelang.org/go/cue"
@@ -89,13 +88,8 @@
var files []*ast.File
- re, err := regexp.Compile(b.cfg.fileFilter)
- if err != nil {
- return err
- }
-
for _, di := range a {
- if !i.User && !re.MatchString(filepath.Base(di.file.Filename)) {
+ if !i.User && !b.matchFile(filepath.Base(di.file.Filename)) {
continue
}
diff --git a/cmd/cue/cmd/testdata/script/import_binary.txt b/cmd/cue/cmd/testdata/script/import_binary.txt
new file mode 100644
index 0000000..e22371d
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/import_binary.txt
@@ -0,0 +1,33 @@
+cue import binary --ext crt .
+cmp x.cue out/expect.cue
+
+cue export bin.cue --out binary
+cmp stdout out/bin
+
+# TODO: txtarscript should distinguish final newline
+cue export str.cue --out binary
+cmp stdout out/str
+
+-- x.crt --
+1234
+-- y.crt2 --
+// Skip this file, wrong extension.
+-- bin.cue --
+'''
+ foo
+
+ '''
+-- str.cue --
+"""
+ foo
+
+ """
+-- out/bin --
+foo
+-- out/str --
+foo
+-- out/expect.cue --
+'''
+ 1234
+
+ '''
diff --git a/cue/build/file.go b/cue/build/file.go
index ecfdfb0..7a5d6d7 100644
--- a/cue/build/file.go
+++ b/cue/build/file.go
@@ -35,6 +35,7 @@
YAML Encoding = "yaml"
JSONL Encoding = "jsonl"
Text Encoding = "text"
+ Binary Encoding = "binary"
Protobuf Encoding = "proto"
TextProto Encoding = "textproto"
BinaryProto Encoding = "pb"
diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go
index dacf6e7..4d9b0ba 100644
--- a/internal/encoding/encoder.go
+++ b/internal/encoding/encoder.go
@@ -219,6 +219,17 @@
return err
}
+ case build.Binary:
+ e.concrete = true
+ e.encValue = func(v cue.Value) error {
+ b, err := v.Bytes()
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(b)
+ return err
+ }
+
default:
return nil, fmt.Errorf("unsupported encoding %q", f.Encoding)
}
diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go
index 79e2fe5..522f023 100644
--- a/internal/encoding/encoding.go
+++ b/internal/encoding/encoding.go
@@ -31,6 +31,7 @@
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/format"
+ "cuelang.org/go/cue/literal"
"cuelang.org/go/cue/parser"
"cuelang.org/go/cue/token"
"cuelang.org/go/encoding/json"
@@ -251,6 +252,11 @@
b, err := ioutil.ReadAll(r)
i.err = err
i.expr = ast.NewString(string(b))
+ case build.Binary:
+ b, err := ioutil.ReadAll(r)
+ i.err = err
+ s := literal.Bytes.WithTabIndent(1).Quote(string(b))
+ i.expr = ast.NewLit(token.STRING, s)
case build.Protobuf:
paths := &protobuf.Config{
Paths: cfg.ProtoPath,
diff --git a/internal/filetypes/types.cue b/internal/filetypes/types.cue
index 935deb3..6bc5d2b 100644
--- a/internal/filetypes/types.cue
+++ b/internal/filetypes/types.cue
@@ -201,6 +201,10 @@
encoding: "text"
form: "data"
}
+ binary: {
+ encoding: "binary"
+ form: "data"
+ }
go: {
encoding: "code"
interpretation: ""
@@ -310,6 +314,11 @@
stream: false
}
+encodings: binary: {
+ forms.data
+ stream: false
+}
+
encodings: toml: {
forms.data
stream: false
diff --git a/internal/filetypes/types.go b/internal/filetypes/types.go
index 1bb1ac0..2093cd0 100644
--- a/internal/filetypes/types.go
+++ b/internal/filetypes/types.go
@@ -41,5 +41,5 @@
return v
}
-// Data size: 1707 bytes.
-var cuegenInstanceData = []byte("\x01\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xacX_\x8f\u0736\x11\x97\xce.P\x11i\xdf\xf2X`,\x03Azpu\u0203\x11c\x81\x83\xe1\xc4v\u15e6(\u04a7 8p\xa5\xd1.\x1b\x89TI*\xb9Cn\x1f\u06a6i?P\xbfF\xbfR\xae\x18\x92\x12%\xad\xee\xeck|/\xb7;?\xcep\xe6G\xce\x1f\xee\xafn\xfeu\x92\x9e\xdc\xfc;Io\xfe\x9e$\x9f\xde\xfc\xedA\x9a~ \xa4\xb1\\\x96\xf8\x92[N\xf2\xf4A\xfa\xf0OJ\xd9\xf4$I\x1f\xfe\x91\xdb}\xfaA\x92\xfe\xe2\xb5h\u04247?&I\xf2\x9b\x9b\u007f\x9e\xa4\u9bff\xfa\xba\ucc68E\x134\u007fL\u049b\x1f\x92\xe4\xe3\x9b\u007f<H\xd3_F\xf9\x0fIz\x92>\xfc\x03o\x91\f=tB\x96$\xc9O\x1f\xfe\x97<I\u04d34\xcd\xecU\x87\xa6({L\u007f\xfa\xf0?\x1d/\xbf\xe1;\x84m/\x9a\x8a\xb1\xb33x\x01\xb4?\x94Jk4\x9d\x92\x95\x01\xab\x80\xc3\xef\x95_T\x10\\\xb0\xc7\xf4o\x03\u07f3\x8c\xb6\x97\xbc\xc5\r\x84?c\xb5\x90;\x96\xa1,U%\xe4n\x04\x1e\xbf\n\x12\x96\tiQw\x1a-\xb7B\xc9\xe7\x1bx\xfcf&aY\xadt\xfb|T%\xed\xd7J\xb7,\xb3|g\x9e\xbb\x8d\xb3\xaf\xfcN_o\xc6-\x0f\xec\xe0\x82x\x895\xef\x1b\v\u0080\xdd#\x90\x8b\xd0\x1b\xac\xa0V\x1a\x8c\xad\x84\x04.+\xfa\xa4z[\xc0\x97{\x04\x83\xd6\n\xb93Pa\x87\xb2\"+JF\xedVU\x14u0\xbc\x01\x17?|4'\xe04\xff]\x0e\u05c37\x87\t\x9fod\xad\xa0\xc2ZH4\xb0W\xdf\x01\xf7f\x85\x01G\x13V\u03a1\x91\x16\xac\x02\u0164\xe8\xa2u\xdfXVq\xcb#+\xa7V\xf7\b\xd7P\xf3\xc6 \xcb4\u05a8Q\x96h6\xc7`yU6\x1eX\xd1t\xae\tb\x9eVl\x95jX\xa6:\xfa\xce\x1b\xaf\xe2e\xa5\x92\xc6j.\xa4\x8d\xeb\xbeA\xec\x02/f\x13dB\x96\xaa\xed\x1a\xb4\xeeZ\x04Y\xdb)m\a\x0f\xbc\xccX\x8d\xbc\x1d\x9c\xf2\xb2J\x95&\x86\xe8e\xdcZ-\xb6\xbd\xf5\x018\x99\xa7\x97\xce\xc5\xd0\xe1\xd1\xc1y\x1f\xdc!W\xa2v\\XP\x1dj\xee#\xf1\xab\vvvF\xaa_\xee\xd1 Xl\xbb\x86[4\xc05\xba\x03\x90t\x1aV\xc1\x16\xa1\x97\xa2\x16H\xe7\x02\u073a\u02e0\x95\xb2\xa0j\xb0{a\xc8H\xa9d-v\xbd\u07e1`n\x03w^Bv\xbd\xf5\xf74\xde\x1a\xfa6\u024b\u04fc\xec\x91n\xcc\x05\u024b\xa2`Yv`Y\u05a0\x85K8\xf7\u02e7t,N-\x9b\xf1\xb2\x04\xc9\xd2\xe4\x0e]\xb2\xb8\xb5\t\xae\x94=\xddZJ5S\x98r\x8f-\x0f\u0390.^Z\x94\xc6_\t\xb7:/\xfeb\x94\xcc\u00f7E\x0eS4\xbc\xb7j\f\xe7\xe0U\xaex\xdb\xdcW\xe5~\x1a\a\xca\xfb\f/\xe9v\xbd\x95p\x17\xc1-\x8c_|\xb2\xc6y`\xf5t\x95\xf3%\xb8\xe4\xfc\u24f7\xb0N\xf9\x1c9?\xb0L\xf5\x9d\x1d.\x8e\xf7\xea\xd9\xd3\xf7\xef\u05b3\xa7\xf7\xf5\v\xbf\xa5J\xf0\xff_\xe7\x8b\xcf^\xbc\xff0>{\xf1\x960jAi?\x8d\xa3\xc2\xfag\x85\xf1\xf4\xd3\u03df\xbd\xf7\xd4tV\uf65fC\xaf{5\xa4)\xb4\xbc3\xbe\xad\xc4\u0525B\x16\n\xa3\x87:M\x05\xd1\n\xaa\x83\x8b\f\xcf\xf3i\xbf\xbd`YNc\xc2(\xa4\xceK\x02\x16\vA\x94\x93`\x00\x9a\x80\x8c@CHSE\xa59\"oEB\xf1\x88\xd6H\xc0\xc6\x12\xb1\x02\xd8K;\a,^Z\x02v*F\u701d\"q\xa7\x95US\u007f\x9d\xc0Y\xc2K;\xa0\xa3\xa59\xba\x9d\xf8\x1cQ\x96Qs\xf9\xe2\xe5\x17\x1b\xa0@\f\xfe\xf5\x89\x13\xe5\u01600*m\x85\xec\xb6pv\x06[!\xb9\xbe\xea\xb6\xe3\xd00\x8cJ d%J\u07df\xfc\x01\xd2m\xe0\xd659\x8d\x9dF\x83\x92\x06\x17\xe0t\xb4;\xcd\u06c2\x8d\x83\xd6\x06\x1e\x9d\xe7\xb97)a>bA\x85\x16u;\x99HJ\u0516\v9\xd8\x01\xb3W}SQ\x1f\x9c\xcd%gg\xf0Zi\x18\x86\xd9'\xe0jE\u02ef\x16+\x81SO6\xa5\x16[\uf7ff\xc1O\u0efd(\xf7 \xac\xc1\xa6v=\x94KR-\x95\xfc\x16\xb5\xf5\u0357\xc3\xe7\u007f~\x154\n\xb6\x98\x0e\u01c1\xcf\u0344\xd3K\x1b\xe4\xb5\x1bNg\xc3\xe30\x84-F\xb6\xbcV\xca\xdff?rz\xad\xdco\x9c\x87\u3833\xf2\xd9U\xaa\xb6\xa5A\xad\x11\x12\xbd\u062a\xe3\xbc\"\xc0e\x947\xe3\x93\xd9[\x1f-S\n\xef4\xef\xf63\xd4I<X\xf1\xdd\f\xaa\xf8n\x00,_ 6\x18t\xf5\xe2{6\xadf\xae\x989\x90\xa2<BC\xe8\x01nV\xf1\xc6/\xa0\x14;\xc2]\x86:\xd8]\xfe#\xdcg\x90[0f\xc8\u0462\x98j\xb4\xd0%K\xb7\xa5\xd9\xd8\xcd\xec(\xec\x1e5\x11=\xe4BH\x17\x18L<\x015\xc3Y\xd6m7p:\xdf\xc5\xff\xe5C\xa6\xe5\xecx\xb8\xc8i\u007f\xb8\x865\xc5G\xe7w\xab:q\x88r5\xc0|<0\xe7G<\xb4\xdd\x11!\xc1Uz\x17\xdc\xe6f6\u07b1,k\xb8\xdbdG\xf4\x85.G\xaa\xef\xc5\xea\xf0\xb2\nvi\xf8\xf2\xf8\x91\xba\x9b\xcbV6\x9c\xcd\\\xe1\x9eM\xf3\xe2\xc8P\\\xf0.\xe6T\x87\x92w\xe2\x16[\x01}\aC>\xd3]\xab\x1d\x1fj\xa1\xe5R\xa9\xe5M\xe3\xc1\x02\xdeX\xa8\x14\x1a\x90\u0282\x90e\xd3W\xe8\u07c9J\xb7\xf0\xe6e\xc1\xdc:\xe7\x90{\xa5\xd2{\xfc||\xaa\x8e\x95\xc8yO-\xf7b\xadN\xc0\xe8e\xa0\x02\xae!w\u04cc\xfb4\u0509\xc5\x03j90\u035fa\xcb)d\xfe\xe8[\xa2\xf3\xe7\xdf\xc73\xf8\xb7\xf0\xd1R\u00b2\xc5\xe3pio\xfeL\\\xa2\xf3\xc7\xe1\x02=P\u0156\xc3\xfc9\x1d\x87\x8e\xf8\n\x1c\x1d\xed\xb7\x1eU\xb4\u007fT\x8a\xe3\x01x\xae\x89u*\xc1\xfe\xbf\xcb\xdd\xc5c\x9c|>\xe2|\x9d\xeb;\xbdY\xf0\xb8\xce\xdf:o1\x9eY\xf70\x85\x8ba\x12\u06e3\xf3x\x85\x86\x1f\x06\xa6\xca\xd3\x0eC\x8f\x81\u0752\x97G\xe7\xa1!\u037d\x1d\u071a\xfd\x121\xc65\xfd\x05b5\x80U^F\xbf\x0el>\x1f\x8f\xddnH\x82\x18A\xecu\xf19\xb3\xc8\x16\x9f$p=\x9c\xdbt\x98\x1f\xfc\x98\xce\xf0\xd1xl\x84srgnP\x1az\xcb\xf3\u07ba\xea\u03f80v\x8f\xd5u\xd1\a\xab\u06bb\f\u0185\x93\x8e\xbbH\x9cw\xea\xd23\ubdf4\xec\t\xadq\u07e1Y\xdemf\xdaR\u05ec\xc4>\xb6p\xfe(\xd0\x03\x9b\x17\xff{\x14`\xf7\xd6\xf1\x9dm\xbe\u02f2U\xddJ\xe0\x9dM\u9775V\xc9Z^\x91\x03K\x92\xff\x05\x00\x00\xff\xff\xa6\xedNA]\x16\x00\x00")
+// Data size: 1713 bytes.
+var cuegenInstanceData = []byte("\x01\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xacX\u074f\u0736\x11\x97\xce.P\x11i\u07da\xb7\x02c\x19\b\u0483\xabE\x1e\x8c\x18\v\x1c\f'\xb6\v\xbf4E\x91>\x05\xc1\x81+\x8dv\xd9H\xa4JR\xc9\x1dr\xfb\xd06M\xfbW{\x8b!\xa9o\xdd\xd9\xd7\xfa^nw~\x9c\xe1\xccp>\xf7W\xa7\u007f\x9f\xc5g\xa7\xffD\xf1\xe9\x1fQ\xf4\xf9\xe9\xef\x0f\xe2\xf8#!\x8d\xe52\u01d7\xdcr\xa2\xc7\x0f\xe2\x87\u007fV\xca\xc6gQ\xfc\xf0O\xdc\x1e\u23e2\xf8\x17\xafE\x85&>\xfd\x1cE\xd1oO\xff:\x8b\xe3_\u007f\xf3m\xdebV\x8a*p\xfe\x1c\u0167\x9f\xa2\xe8\xd3\xd3?\x1f\xc4\xf1/\a\xfaOQ|\x16?\xfc#\xaf\x91\x04=tD\x16E\xd1\u06cf3\xd2$\x8e\xcf\xe28\xb1\xd7\r\x9a,o1~\xfb\xf1o\x1a\x9e\u007f\xc7\xf7\b\xbbVT\x05c\x9b\r\xbc\x00\xba\x1fr\xa55\x9aF\xc9\u0080U\xc0\xe1\x0f\xca\x1f\xca\b\xce\xd8c\xfa\xb7\x85\x1fYB\xd7K^\xe3\x16\u009f\xb1Z\xc8=KP\xe6\xaa\x10r\xdf\x03\x8f_\x05\nK\x84\xb4\xa8\x1b\x8d\x96[\xa1\xe4\xf3-<~3\xa1\xb0\xa4T\xba~\u07b3\x12\xf7k\xa5k\x96X\xbe7\xcf\xdd\xc5\xc97\xfe\xa6o\xb7\xfd\x95GvtF\xbc\u0112\xb7\x95\x05a\xc0\x1e\x10HEh\r\x16P*\r\xc6\x16B\x02\x97\x05}R\xad\xcd\xe0\xeb\x03\x82Ak\x85\xdc\x1b(\xb0AY\x90\x14%\a\xeeZ\x15du\x10\xbc\x05g?|2u\xc0y\xfa\xfb\x14n:m\x8e#\u007f\xbe\x91\xa5\x82\x02K!\xd1\xc0A\xfd\x00\u070b\x15\x06\x9c\x9b\xb0p\n\xf5n\xc1\"\xb8\x98\x18\x9d\xb5\xee\x1bK\nn\xf9\xe0\x95s\xab[\x84\x1b(ye\x90%\x1aK\xd4(s4\xdb%\x98_\xe7\x95\aV8\x9dj\x82<O'vJU,Q\r}\xe7\x95g\xf1\xb4\\Ic5\x17\xd2\x0e\xe7\xbeCl\x82_\xcc6\u0404\xccU\xddTh]X\x04Z\xdd(m;\r<\xcdX\x8d\xbc\xee\x94\xf2\xb4B\xe5f0\xd1\u04f8\xb5Z\xecZ\xeb\rp4\xef^z\x17C\x8fG\x0f\xe7up\x8f\\\x88\xd2\xf9\u0082jPso\x89?\x9d\xb1\u0346X\xbf>\xa0A\xb0X7\x15\xb7h\x80kt\x0f \xe95\xac\x82\x1dB+E)\x90\xde\x05\xb8u\xc1\xa0\x95\xb2\xa0J\xb0\aaHH\xaed)\xf6\xad\xbf!c\xee\x02\xf7^B6\xad\xf5q:D\r}\x1b\xe5\xc5y\x9a\xb7H\x11sI\xf4,\xcbX\x92\x1cY\x92Th\xe1\n.\xfc\xf1\xb1;f\xaf\x96L\xfc2\aI\xd2(\x86\xae\xd8p\xb5\t\xaa\xe4-E-\xa5\x9a\xc9L~\xc0\x9a\ae\x88\x17\xaf,J\xe3C\u009dN\xb3\xbf\x1a%\xd3\xf0m\x96\xc3d\ro\xad\xea\xcd9z\x96k^W\xf7e\xb9\x1f\u01d1\xf2>\xc1+\x8a\xaew:\xdcYp\x8b\xc7/?[\xf3y\xf0\xea\xf9\xaa\xcf\xe7\xe0\xdc\u75df\xbd\xc3\xeb\x94\u03c3\u03cf,Qmc\xbb\xc0\xf1Z={\xfa\xe1\xd5z\xf6\xf4\xbez\xe1\xf7T\t\xfe\xf7p\xbe\xfc\xe2\u01477\xe3\x8b\x17\xef0\xa3\x14\x94\xf6c;\n,\xff/3\x9e~\xfe\xe5\xb3\x0f\x9e\x9aN\xea=\xf3\xb3\xebu\xaf\xba4\x85\x9a7\u01b7\x95!u\xa9\x90\x85\xc2\xe8\xa1FSA\xb4\x82\xea\xe0,\xc3\xd3t\xdco/Y\x92\u0498\xd0\x13\xa9\xf3\x12\x81\r\x85`\xa0\x13\xa1\x03\xaa\x80\xf4@EHU\fLSD\u078a\x84\xe21H#\x02\xebK\xc4\n`\xaf\xec\x14\xb0xe\t\u062b\xc1:\a\xec\x15\x91\x1b\xad\xac\x1a\xeb\xeb\bN\x12^\xd9\x0e\xed%M\xd1\xddH\xe7\x01e\t5\x97\xaf^~\xb5\x052\xc4\xe0\u07de8R\x9au\f=\xd3N\xc8f\a\x9b\r\xec\x84\xe4\xfa\xba\xd9\xf5CC7*\x81\x90\x85\xc8}\u007f\xf2\x0fH\xd1\xc0\xadkr\x1a\x1b\x8d\x06%\r.\xc0\xe9i\xf7\x9a\xd7\x19\xeb\a\xad-<\xbaHS/R\xc2t\u0102\x02-\xeaz4\x91\xe4\xa8-\x17\xb2\x93\x03\xe6\xa0\u06aa\xa0>8\x99K6\x1bx\xad4t\xc3\xec\x13p\xb5\xa2\xe6\u05f3\x93\xc0\xa9'\x9b\\\x8b\x9d\xd7\xcfG\xf0\x13\xf8\xe1 \xf2\x03\bk\xb0*]\x0f\xe5\x92Xs%\xbfGm}\xf3\xe5\xf0\xe5_^\x05\x8e\x8c\u0366\xc3~\xe0s3\xe18h\x03\xbdt\xc3\xe9dx\uc1b0\xd9\u0216\x96J\xf9h\xf6#\xa7\xe7J\xfd\xc5ix\x0ez+\x9f]\xb9\xaak\x1a\xd4*!\u0453\xadZ\xe6\x15\x01.\xa3\xbc\x18\x9f\xcc^z/\x99Rx\xafys\x98\xa0\x8e\xe2\xc1\x82\xef'P\xc1\xf7\x1d`\xf9\f\xb1A\xa0\xab\x17?\xb2q5s\xc5\u0301d\xe5\x02\r\xa6\a\xb8Z\xc5+\u007f\x80Rl\x81\xbb\fu\xb0\v\xfe\x05\xee3\xc8\x1d\xe83dqhH5:\u84a5\xd9\xd1l\xecfv\x14\xf6\x80\x9a\x1c\xdd\xe5BH\x17\xe8D<\x015\xc1Y\xd2\xec\xb6p>\xbd\xc5\xff\xa5]\xa6\xa5l9\\\xa4t?\xdc\xc0\x1a\u38cb\xbbY\x1d9X\xb9j`\xda?\x98\xd3cx4/v\xc1\xe3\u0277r\xed\x17n\f\x06\xd26q\x9bqI\x1f\x99IRqw\u035e\x9c\x1ez#\xb1~\x10\xa9\xdd>\x16\xe4\xd2\xc8\xe6\xf1\x05\xbb\x9b\xe6V.\x9cLj!:\xc7\u0674\x104\x1cx\x1fq\xaaA\xc9\x1bq\x8b\xac\x80\xbe\x87 _\x1f\\\x83\xee\u05fb\u0428\xa9@\xf3\xaa\xf2`\x06o,\x14\n\rHeA\u023cj\v\xf4\u06e5\xd25\xbcy\x991w\xce)\xe4v[\xda\xe2/\xfa\x05\xb7\xaf_N{j\u0517k\xd5\x05z-\x83+\xe0\x06R7\x03\xb9O]u\x99\xad]\xf31k\xba\xbc\xcdg\x97\xe9\xaa8G\xa7K\xe3\xa7\x13\xf8w\xf0\u025c\u0092\xd9J9\x977].\xe7\xe8t\xa5\x9c\xa1G\xaa\xf3\xb2\x9bZ\xc7C\xd4\xc2_\xc1G\x8b\xfb\u05ad\x1a\xe4/\n\xf8\xf0\x00\xde\xd7\xe4u*\xdc\xfe\xbf\xcb\xdd\xd9\nO:/|\xbe\xee\xeb;\xb5\x99\xf9q\xdd\u007f\xeb~\x1b\xec\x99\xf4\x1c\x939\x1bF\xb6=\xba\x18B\xa8\xfb9a\xcc<\xeeK\xb4B\xec\xe7~yt\x11\xda\xd8T\xdbN\xad\xc9\xef\x17\xbd]\xe3\xdf-V\rX\xf5K\xaf\u05d1M\xa7\xea\xbeGvI0X0t\xc8a\t\x9ae\x8bO\x12\xb8\xe9\xdem\xbc\x02tz\x8c'\xffA\xf8\xd0>\xa7\u039d\xa8Ai\xe8%O;\xf2\xaa>\xfd\xc1\xa1\u7b1e\x1bt\x18\xb7\x9aw\x1c\xb5\xaa\xbe\xeb\xee\xe1\u0a25\xcfr\xec\xbd\u0180\x89\xf4[f\x82\xd1\v\xccm\xa1F\u007f\x97\x98q\xcf^\x932\xb4\xbc\x99\xf2\vC\x8fl\xda'\xeeQ\xab\xdd2\xe5\x9b\xe0\xf4\x96yW\xbb\u0541w\xf6\xaf\xf7\xe6Zu\xd6<\x9a\x8e,\x8a\xfe\x1b\x00\x00\xff\xff\x8e\xf7,\xe9\xbe\x16\x00\x00")