diff --git a/Makefile b/Makefile index 1ee38db..ae999a3 100644 --- a/Makefile +++ b/Makefile @@ -169,8 +169,10 @@ test_sqlite3: ## test sqlite3 code generation +set_release: ## populate release info + ./release.sh -gen_readme: ## generate readme file +gen_readme: set_release ## generate readme file go run github.com/smallnest/gen/readme \ --sqltype=sqlite3 \ --connstr "./example/sample.db" \ @@ -178,4 +180,10 @@ gen_readme: ## generate readme file --table invoices -release: fmt gen install example gen_readme ## prepare release +release: gen_readme fmt gen install example ## prepare release + $(info ************ Release completed) + +git_sync: ## git sync upstream + git fetch upstream + git checkout master + git merge upstream/maste \ No newline at end of file diff --git a/README.md b/README.md index 36f5923..1f8b86e 100644 --- a/README.md +++ b/README.md @@ -243,12 +243,67 @@ The `gen` tool provides functionality to layout your own project format. Users h via the command `gen --save ./mytemplates`. This will save the embedded templates for local editing. Then you would specify the `--templateDir=` option when generating a project. * Passing `--exec=../sample.gen` on the command line will load the `sample.gen` script and execute it. The script has access to the table information and other info passed to `gen`. This allows developers to customize the generation of code. You could loop through the list of tables and invoke -`GenerateTableFile` or `GenerateFile`. +`GenerateTableFile` or `GenerateFile`. You can also perform operations such as mkdir, copy, touch, pwd. -You can also populate the context used by templates with extra data by passing the `--contect=` option. The json file will be used to populate the context used when parsing templates. +### Example - generate files from a template looping thru a map of tables. +Loop thru map of tables, key is the table name and value is ModelInfo. Creating a file using the table ModelInfo. +`tableInfos := map[string]*ModelInfo` +`GenerateTableFile(tableInfos map[string]*ModelInfo, tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool)` + +``` + +{{ range $tableName , $table := .tableInfos }} + {{$i := inc }} + {{$name := toUpper $table.TableName -}} + {{$filename := printf "My%s.go" $name -}} + + {{ GenerateTableFile $.tableInfos $table.TableName "custom.go.tmpl" "test" $filename true}}{{- end }} + +``` + +### Example - generate file from a template. +`GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool, overwrite bool)` +``` + +{{ GenerateFile "custom.md.tmpl" "test" "custom.md" false false }} + +``` + +### Example - make a directory. +``` + +{{ mkdir "test/alex/test/mkdir" }} + +``` + +### Example - touch a file. +``` + +{{ touch "test/alex/test/mkdir/alex.txt" }} + +``` + +### Example - display working directory. +``` + +{{ pwd }} + +``` + +### Example - copy a file or directory from source to a target directory. +``` + +{{ copy "../_test" "test" }} + +``` + + +You can also populate the context used by templates with extra data by passing the `--context=` option. The json file will be used to populate the context used when parsing templates. + +### File Generation ```gotemplate // Loop through tables and print out table name and various forms of the table name @@ -270,24 +325,25 @@ You can also populate the context used by templates with extra data by passing t {{- end }} -// GenerateTableFile(tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool) -// GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool) string +// GenerateTableFile(tableInfos map[string]*ModelInfo, tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool) +// GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool, overwrite bool) The following info is available within use of the exec template. "AdvancesSample" string "\n{{ range $i, $table := .tables }}\n {{$singular := singular $table -}}\n {{$plural := pluralize $table -}}\n {{$title := title $table -}}\n {{$lower := toLower $table -}}\n {{$lowerCamel := toLowerCamelCase $table -}}\n {{$snakeCase := toSnakeCase $table -}}\n {{ printf \"[%-2d] %-20s %-20s %-20s %-20s %-20s %-20s %-20s\" $i $table $singular $plural $title $lower $lowerCamel $snakeCase}}{{- end }}\n\n\n{{ range $i, $table := .tables }}\n {{$name := toUpper $table -}}\n {{$filename := printf \"My%s\" $name -}}\n {{ printf \"[%-2d] %-20s %-20s\" $i $table $filename}}\n {{ GenerateTableFile $table \"custom.go.tmpl\" \"test\" $filename true}}\n{{- end }}\n" - "Config" *dbmeta.Config &dbmeta.Config{SQLType:"sqlite3", SQLConnStr:"./example/sample.db", SQLDatabase:"main", Module:"github.com/alexj212/test", ModelPackageName:"model", ModelFQPN:"github.com/alexj212/test/model", AddJSONAnnotation:true, AddGormAnnotation:true, AddProtobufAnnotation:true, AddXMLAnnotation:true, AddDBAnnotation:true, UseGureguTypes:false, JSONNameFormat:"snake", XMLNameFormat:"snake", ProtobufNameFormat:"", DaoPackageName:"dao", DaoFQPN:"github.com/alexj212/test/dao", APIPackageName:"api", APIFQPN:"github.com/alexj212/test/api", GrpcPackageName:"", GrpcFQPN:"", Swagger:(*dbmeta.SwaggerInfoDetails)(0xc0008ba360), ServerPort:8080, ServerHost:"127.0.0.1", ServerScheme:"http", ServerListen:":8080", Verbose:false, OutDir:".", Overwrite:true, LineEndingCRLF:false, CmdLine:"/tmp/go-build469802175/b001/exe/readme --sqltype=sqlite3 --connstr ./example/sample.db --database main --table invoices", CmdLineWrapped:"/tmp/go-build469802175/b001/exe/readme \\\n --sqltype=sqlite3 \\\n --connstr \\\n ./example/sample.db \\\n --database \\\n main \\\n --table \\\n invoices", CmdLineArgs:[]string{"/tmp/go-build469802175/b001/exe/readme", "--sqltype=sqlite3", "--connstr", "./example/sample.db", "--database", "main", "--table", "invoices"}, FileNamingTemplate:"{{.}}", ModelNamingTemplate:"{{FmtFieldName .}}", FieldNamingTemplate:"{{FmtFieldName (stringifyFirstChar .) }}", string:"", ContextMap:map[string]interface {}{"GenHelp":"Usage of gen:\n\tgen [-v] --sqltype=mysql --connstr \"user:password@/dbname\" --database --module=example.com/example [--json] [--gorm] [--guregu] [--generate-dao] [--generate-proj]\ngit fetch up\n sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n\n\nOptions:\n --sqltype=mysql sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n -c, --connstr=nil database connection string\n -d, --database=nil Database to for connection\n -t, --table= Table to build struct from\n -x, --exclude= Table(s) to exclude\n --templateDir= Template Dir\n --save= Save templates to dir\n --model=model name to set for model package\n --model_naming={{FmtFieldName .}} model naming template to name structs\n --field_naming={{FmtFieldName (stringifyFirstChar .) }} field naming template to name structs\n --file_naming={{.}} file_naming template to name files\n --dao=dao name to set for dao package\n --api=api name to set for api package\n --grpc=grpc name to set for grpc package\n --out=. output dir\n --module=example.com/example module path\n --overwrite Overwrite existing files (default)\n --no-overwrite disable overwriting files\n --windows use windows line endings in generated files\n --no-color disable color output\n --context= context file (json) to populate context with\n --mapping= mapping file (json) to map sql types to golang/protobuf etc\n --exec= execute script for custom code generation\n --json Add json annotations (default)\n --no-json Disable json annotations\n --json-fmt=snake json name format [snake | camel | lower_camel | none]\n --xml Add xml annotations (default)\n --no-xml Disable xml annotations\n --xml-fmt=snake xml name format [snake | camel | lower_camel | none]\n --gorm Add gorm annotations (tags)\n --protobuf Add protobuf annotations (tags)\n --proto-fmt=snake proto name format [snake | camel | lower_camel | none]\n --gogo-proto= location of gogo import \n --db Add db annotations (tags)\n --guregu Add guregu null types\n --copy-templates Copy regeneration templates to project directory\n --mod Generate go.mod in output dir\n --makefile Generate Makefile in output dir\n --server Generate server app output dir\n --generate-dao Generate dao functions\n --generate-proj Generate project readme and gitignore\n --rest Enable generating RESTful api\n --run-gofmt run gofmt on output dir\n --listen= listen address e.g. :8080\n --scheme=http scheme for server url\n --host=localhost host for server\n --port=8080 port for server\n --swagger_version=1.0 swagger version\n --swagger_path=/ swagger base path\n --swagger_tos= swagger tos url\n --swagger_contact_name=Me swagger contact name\n --swagger_contact_url=http://me.com/terms.html swagger contact url\n --swagger_contact_email=me@me.com swagger contact email\n -v, --verbose Enable verbose output\n --name_test= perform name test using the --model_naming or --file_naming options\n -h, --help Show usage message\n --version Show version\n\n", "tableInfos":map[string]*dbmeta.ModelInfo{"invoices":(*dbmeta.ModelInfo)(0xc000191540)}}, TemplateLoader:(dbmeta.TemplateLoader)(0x8a55b0)} + "Config" *dbmeta.Config &dbmeta.Config{SQLType:"sqlite3", SQLConnStr:"./example/sample.db", SQLDatabase:"main", Module:"github.com/alexj212/test", ModelPackageName:"model", ModelFQPN:"github.com/alexj212/test/model", AddJSONAnnotation:true, AddGormAnnotation:true, AddProtobufAnnotation:true, AddXMLAnnotation:true, AddDBAnnotation:true, UseGureguTypes:false, JSONNameFormat:"snake", XMLNameFormat:"snake", ProtobufNameFormat:"", DaoPackageName:"dao", DaoFQPN:"github.com/alexj212/test/dao", APIPackageName:"api", APIFQPN:"github.com/alexj212/test/api", GrpcPackageName:"", GrpcFQPN:"", Swagger:(*dbmeta.SwaggerInfoDetails)(0xc000470480), ServerPort:8080, ServerHost:"127.0.0.1", ServerScheme:"http", ServerListen:":8080", Verbose:false, OutDir:".", Overwrite:true, LineEndingCRLF:false, CmdLine:"/tmp/go-build665746825/b001/exe/readme --sqltype=sqlite3 --connstr ./example/sample.db --database main --table invoices", CmdLineWrapped:"/tmp/go-build665746825/b001/exe/readme \\\n --sqltype=sqlite3 \\\n --connstr \\\n ./example/sample.db \\\n --database \\\n main \\\n --table \\\n invoices", CmdLineArgs:[]string{"/tmp/go-build665746825/b001/exe/readme", "--sqltype=sqlite3", "--connstr", "./example/sample.db", "--database", "main", "--table", "invoices"}, FileNamingTemplate:"{{.}}", ModelNamingTemplate:"{{FmtFieldName .}}", FieldNamingTemplate:"{{FmtFieldName (stringifyFirstChar .) }}", string:"", ContextMap:map[string]interface {}{"GenHelp":"Usage of gen:\n\tgen [-v] --sqltype=mysql --connstr \"user:password@/dbname\" --database --module=example.com/example [--json] [--gorm] [--guregu] [--generate-dao] [--generate-proj]\ngit fetch up\n sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n\n\nOptions:\n --sqltype=mysql sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n -c, --connstr=nil database connection string\n -d, --database=nil Database to for connection\n -t, --table= Table to build struct from\n -x, --exclude= Table(s) to exclude\n --templateDir= Template Dir\n --save= Save templates to dir\n --model=model name to set for model package\n --model_naming={{FmtFieldName .}} model naming template to name structs\n --field_naming={{FmtFieldName (stringifyFirstChar .) }} field naming template to name structs\n --file_naming={{.}} file_naming template to name files\n --dao=dao name to set for dao package\n --api=api name to set for api package\n --grpc=grpc name to set for grpc package\n --out=. output dir\n --module=example.com/example module path\n --overwrite Overwrite existing files (default)\n --no-overwrite disable overwriting files\n --windows use windows line endings in generated files\n --no-color disable color output\n --context= context file (json) to populate context with\n --mapping= mapping file (json) to map sql types to golang/protobuf etc\n --exec= execute script for custom code generation\n --json Add json annotations (default)\n --no-json Disable json annotations\n --json-fmt=snake json name format [snake | camel | lower_camel | none]\n --xml Add xml annotations (default)\n --no-xml Disable xml annotations\n --xml-fmt=snake xml name format [snake | camel | lower_camel | none]\n --gorm Add gorm annotations (tags)\n --protobuf Add protobuf annotations (tags)\n --proto-fmt=snake proto name format [snake | camel | lower_camel | none]\n --gogo-proto= location of gogo import \n --db Add db annotations (tags)\n --guregu Add guregu null types\n --copy-templates Copy regeneration templates to project directory\n --mod Generate go.mod in output dir\n --makefile Generate Makefile in output dir\n --server Generate server app output dir\n --generate-dao Generate dao functions\n --generate-proj Generate project readme and gitignore\n --rest Enable generating RESTful api\n --run-gofmt run gofmt on output dir\n --listen= listen address e.g. :8080\n --scheme=http scheme for server url\n --host=localhost host for server\n --port=8080 port for server\n --swagger_version=1.0 swagger version\n --swagger_path=/ swagger base path\n --swagger_tos= swagger tos url\n --swagger_contact_name=Me swagger contact name\n --swagger_contact_url=http://me.com/terms.html swagger contact url\n --swagger_contact_email=me@me.com swagger contact email\n -v, --verbose Enable verbose output\n --name_test= perform name test using the --model_naming or --file_naming options\n -h, --help Show usage message\n --version Show version\n\n", "tableInfos":map[string]*dbmeta.ModelInfo{"invoices":(*dbmeta.ModelInfo)(0xc00057b400)}}, TemplateLoader:(dbmeta.TemplateLoader)(0x89ff10)} "DatabaseName" string "main" "GenHelp" string "Usage of gen:\n\tgen [-v] --sqltype=mysql --connstr \"user:password@/dbname\" --database --module=example.com/example [--json] [--gorm] [--guregu] [--generate-dao] [--generate-proj]\ngit fetch up\n sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n\n\nOptions:\n --sqltype=mysql sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n -c, --connstr=nil database connection string\n -d, --database=nil Database to for connection\n -t, --table= Table to build struct from\n -x, --exclude= Table(s) to exclude\n --templateDir= Template Dir\n --save= Save templates to dir\n --model=model name to set for model package\n --model_naming={{FmtFieldName .}} model naming template to name structs\n --field_naming={{FmtFieldName (stringifyFirstChar .) }} field naming template to name structs\n --file_naming={{.}} file_naming template to name files\n --dao=dao name to set for dao package\n --api=api name to set for api package\n --grpc=grpc name to set for grpc package\n --out=. output dir\n --module=example.com/example module path\n --overwrite Overwrite existing files (default)\n --no-overwrite disable overwriting files\n --windows use windows line endings in generated files\n --no-color disable color output\n --context= context file (json) to populate context with\n --mapping= mapping file (json) to map sql types to golang/protobuf etc\n --exec= execute script for custom code generation\n --json Add json annotations (default)\n --no-json Disable json annotations\n --json-fmt=snake json name format [snake | camel | lower_camel | none]\n --xml Add xml annotations (default)\n --no-xml Disable xml annotations\n --xml-fmt=snake xml name format [snake | camel | lower_camel | none]\n --gorm Add gorm annotations (tags)\n --protobuf Add protobuf annotations (tags)\n --proto-fmt=snake proto name format [snake | camel | lower_camel | none]\n --gogo-proto= location of gogo import \n --db Add db annotations (tags)\n --guregu Add guregu null types\n --copy-templates Copy regeneration templates to project directory\n --mod Generate go.mod in output dir\n --makefile Generate Makefile in output dir\n --server Generate server app output dir\n --generate-dao Generate dao functions\n --generate-proj Generate project readme and gitignore\n --rest Enable generating RESTful api\n --run-gofmt run gofmt on output dir\n --listen= listen address e.g. :8080\n --scheme=http scheme for server url\n --host=localhost host for server\n --port=8080 port for server\n --swagger_version=1.0 swagger version\n --swagger_path=/ swagger base path\n --swagger_tos= swagger tos url\n --swagger_contact_name=Me swagger contact name\n --swagger_contact_url=http://me.com/terms.html swagger contact url\n --swagger_contact_email=me@me.com swagger contact email\n -v, --verbose Enable verbose output\n --name_test= perform name test using the --model_naming or --file_naming options\n -h, --help Show usage message\n --version Show version\n\n" "NonPrimaryKeyNamesList" []string []string{"CustomerId", "InvoiceDate", "BillingAddress", "BillingCity", "BillingState", "BillingCountry", "BillingPostalCode", "Total"} "NonPrimaryKeysJoined" string "CustomerId,InvoiceDate,BillingAddress,BillingCity,BillingState,BillingCountry,BillingPostalCode,Total" "PrimaryKeyNamesList" []string []string{"InvoiceId"} "PrimaryKeysJoined" string "InvoiceId" + "ReleaseHistory" string "- v0.9.26 (07/31/2020)\n - Release scripting\n - Added custom script functions to copy, mkdir, touch, pwd\n - Fixed custom script exec example\n- v0.9.25 (07/26/2020)\n - Adhere json-fmt flag for all JSON response so when camel or lower_camel is specified, fields name in GetAll variant and DDL info will also have the same name format\n - Fix: Build information embedded through linker in Makefile is not consistent with the variable defined in main file.\n - Added --scheme and --listen options. This allows compiled binary to be used behind reverse proxy.\n - In addition, template for generating URL was fixed, i.e. when PORT is 80, then PORT is omitted from URL segment.\n- v0.9.24 (07/13/2020)\n - Fixed array bounds issue parsing mysql db meta\n- v0.9.23 (07/10/2020)\n - Added postgres types: bigserial, serial, smallserial, bigserial, float4 to mapping.json\n- v0.9.22 (07/08/2020)\n - Modified gogo.proto check to use GOPATH not hardcoded.\n - Updated gen to error exit on first error encountered\n - Added color output for error\n - Added --no-color option for non colorized output\n- v0.9.21 (07/07/2020)\n - Repacking templates, update version number in info.\n- v0.9.20 (07/07/2020)\n - Fixed render error in router.go.tmpl\n - upgraded project to use go.mod 1.14\n- v0.9.19 (07/07/2020)\n - Added --windows flag to write files with CRLF windows line endings, otherwise they are all unix based LF line endings\n- v0.9.18 (06/30/2020)\n - Fixed naming in templates away from hard coded model package.\n- v0.9.17 (06/30/2020)\n - Refactored template loading, to better report error in template\n - Added option to run gofmt on output directory\n- v0.9.16 (06/29/2020)\n - Fixes to router.go.tmpl from calvinchengx\n - Added postgres db support for inet and timestamptz\n- v0.9.15 (06/23/2020)\n - Code cleanup using gofmt name suggestions.\n - Template updates for generated code cleanup using gofmt name suggestions.\n- v0.9.14 (06/23/2020)\n - Added model comment on field line if available from database.\n - Added exposing TableInfo via api call.\n- v0.9.13 (06/22/2020)\n - fixed closing of connections via defer\n - bug fixes in sqlx generated code\n- v0.9.12 (06/14/2020)\n - SQLX changed MustExec to Exec and checking/returning error\n - Updated field renaming if duplicated, need more elegant renaming solution.\n - Added exclude to test.sh\n- v0.9.11 (06/13/2020)\n - Added ability to pass field, model and file naming format\n - updated test scripts\n - Fixed sqlx sql query placeholders\n- v0.9.10 (06/11/2020)\n - Bug fix with retrieving varchar length from mysql\n - Added support for mysql unsigned decimal - maps to float\n- v0.9.9 (06/11/2020)\n - Fixed issue with mysql and table named `order`\n - Fixed internals in GetAll generation in gorm and sqlx.\n- v0.9.8 (06/10/2020)\n - Added ability to set file naming convention for models, dao, apis and grpc `--file_naming={{.}}`\n - Added ability to set struct naming convention `--model_naming={{.}}`\n - Fixed bug with Makefile generation removing quoted conn string in `make regen`\n- v0.9.7 (06/09/2020)\n - Added grpc server generation - WIP (looking for code improvements)\n - Added ability to exclude tables\n - Added support for unsigned from mysql ddl.\n- v0.9.6 (06/08/2020)\n - Updated SQLX codegen\n - Updated templates to split code gen functions into seperate files\n - Added code_dao_gorm, code_dao_sqlx to be generated from templates\n- v0.9.5 (05/16/2020)\n - Added SQLX codegen by default, split dao templates.\n - Renamed templates\n- v0.9.4 (05/15/2020)\n - Documentation updates, samples etc.\n- v0.9.3 (05/14/2020)\n - Template bug fixes, when using custom api, dao and model package.\n - Set primary key if not set to the first column\n - Skip code gen if primary key column is not int or string\n - validated codegen for mysql, mssql, postgres and sqlite3\n - Fixed file naming if table ends with _test.go renames to _tst.go\n - Fix for duplicate field names in struct due to renaming\n - Added Notes for columns and tables for situations where a primary key is set since not defined in db\n - Fixed issue when model contained field that had were named the same as funcs within model.\n- v0.9.2 (05/12/2020)\n - Code cleanup gofmt, etc.\n- v0.9.1 (05/12/2020)\n- v0.9 (05/12/2020)\n - updated db meta data loading fetching default values\n - added default value to GORM tags\n - Added protobuf .proto generation\n - Added test app to display meta data\n - Cleanup DDL generation\n - Added support for varchar2, datetime2, float8, USER_DEFINED\n- v0.5\n" "ShortStructName" string "i" "StructName" string "Invoices" "SwaggerInfo" *dbmeta.SwaggerInfoDetails &dbmeta.SwaggerInfoDetails{Version:"1.0.0", Host:"127.0.0.1:8080", BasePath:"/", Title:"Sample CRUD api for main db", Description:"Sample CRUD api for main db", TOS:"My Custom TOS", ContactName:"", ContactURL:"", ContactEmail:""} - "TableInfo" *dbmeta.ModelInfo &dbmeta.ModelInfo{Index:0, IndexPlus1:1, PackageName:"model", StructName:"Invoices", ShortStructName:"i", TableName:"invoices", Fields:[]string{"//[ 0] InvoiceId integer null: false primary: true isArray: false auto: true col: integer len: -1 default: []\n InvoiceID int32 `gorm:\"primary_key;AUTO_INCREMENT;column:InvoiceId;type:integer;\" json:\"invoice_id\" xml:\"invoice_id\" db:\"InvoiceId\" protobuf:\"int32,0,opt,name=InvoiceId\"`", "//[ 1] CustomerId integer null: false primary: false isArray: false auto: false col: integer len: -1 default: []\n CustomerID int32 `gorm:\"column:CustomerId;type:integer;\" json:\"customer_id\" xml:\"customer_id\" db:\"CustomerId\" protobuf:\"int32,1,opt,name=CustomerId\"`", "//[ 2] InvoiceDate datetime null: false primary: false isArray: false auto: false col: datetime len: -1 default: []\n InvoiceDate time.Time `gorm:\"column:InvoiceDate;type:datetime;\" json:\"invoice_date\" xml:\"invoice_date\" db:\"InvoiceDate\" protobuf:\"google.protobuf.Timestamp,2,opt,name=InvoiceDate\"`", "//[ 3] BillingAddress nvarchar(70) null: true primary: false isArray: false auto: false col: nvarchar len: 70 default: []\n BillingAddress sql.NullString `gorm:\"column:BillingAddress;type:nvarchar;size:70;\" json:\"billing_address\" xml:\"billing_address\" db:\"BillingAddress\" protobuf:\"string,3,opt,name=BillingAddress\"`", "//[ 4] BillingCity nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingCity sql.NullString `gorm:\"column:BillingCity;type:nvarchar;size:40;\" json:\"billing_city\" xml:\"billing_city\" db:\"BillingCity\" protobuf:\"string,4,opt,name=BillingCity\"`", "//[ 5] BillingState nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingState sql.NullString `gorm:\"column:BillingState;type:nvarchar;size:40;\" json:\"billing_state\" xml:\"billing_state\" db:\"BillingState\" protobuf:\"string,5,opt,name=BillingState\"`", "//[ 6] BillingCountry nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingCountry sql.NullString `gorm:\"column:BillingCountry;type:nvarchar;size:40;\" json:\"billing_country\" xml:\"billing_country\" db:\"BillingCountry\" protobuf:\"string,6,opt,name=BillingCountry\"`", "//[ 7] BillingPostalCode nvarchar(10) null: true primary: false isArray: false auto: false col: nvarchar len: 10 default: []\n BillingPostalCode sql.NullString `gorm:\"column:BillingPostalCode;type:nvarchar;size:10;\" json:\"billing_postal_code\" xml:\"billing_postal_code\" db:\"BillingPostalCode\" protobuf:\"string,7,opt,name=BillingPostalCode\"`", "//[ 8] Total numeric null: false primary: false isArray: false auto: false col: numeric len: -1 default: []\n Total float64 `gorm:\"column:Total;type:numeric;\" json:\"total\" xml:\"total\" db:\"Total\" protobuf:\"float,8,opt,name=Total\"`"}, DBMeta:(*dbmeta.dbTableMeta)(0xc0004012c0), Instance:(*struct { InvoiceDate time.Time "json:\"invoice_date\""; BillingPostalCode string "json:\"billing_postal_code\""; BillingCountry string "json:\"billing_country\""; Total float64 "json:\"total\""; InvoiceID int "json:\"invoice_id\""; CustomerID int "json:\"customer_id\""; BillingAddress string "json:\"billing_address\""; BillingCity string "json:\"billing_city\""; BillingState string "json:\"billing_state\"" })(0xc000020c00), CodeFields:[]*dbmeta.FieldInfo{(*dbmeta.FieldInfo)(0xc000276640), (*dbmeta.FieldInfo)(0xc000276780), (*dbmeta.FieldInfo)(0xc0002768c0), (*dbmeta.FieldInfo)(0xc000276a00), (*dbmeta.FieldInfo)(0xc000276b40), (*dbmeta.FieldInfo)(0xc000276c80), (*dbmeta.FieldInfo)(0xc000276dc0), (*dbmeta.FieldInfo)(0xc000276f00), (*dbmeta.FieldInfo)(0xc000277040)}} + "TableInfo" *dbmeta.ModelInfo &dbmeta.ModelInfo{Index:0, IndexPlus1:1, PackageName:"model", StructName:"Invoices", ShortStructName:"i", TableName:"invoices", Fields:[]string{"//[ 0] InvoiceId integer null: false primary: true isArray: false auto: true col: integer len: -1 default: []\n InvoiceID int32 `gorm:\"primary_key;AUTO_INCREMENT;column:InvoiceId;type:integer;\" json:\"invoice_id\" xml:\"invoice_id\" db:\"InvoiceId\" protobuf:\"int32,0,opt,name=InvoiceId\"`", "//[ 1] CustomerId integer null: false primary: false isArray: false auto: false col: integer len: -1 default: []\n CustomerID int32 `gorm:\"column:CustomerId;type:integer;\" json:\"customer_id\" xml:\"customer_id\" db:\"CustomerId\" protobuf:\"int32,1,opt,name=CustomerId\"`", "//[ 2] InvoiceDate datetime null: false primary: false isArray: false auto: false col: datetime len: -1 default: []\n InvoiceDate time.Time `gorm:\"column:InvoiceDate;type:datetime;\" json:\"invoice_date\" xml:\"invoice_date\" db:\"InvoiceDate\" protobuf:\"google.protobuf.Timestamp,2,opt,name=InvoiceDate\"`", "//[ 3] BillingAddress nvarchar(70) null: true primary: false isArray: false auto: false col: nvarchar len: 70 default: []\n BillingAddress sql.NullString `gorm:\"column:BillingAddress;type:nvarchar;size:70;\" json:\"billing_address\" xml:\"billing_address\" db:\"BillingAddress\" protobuf:\"string,3,opt,name=BillingAddress\"`", "//[ 4] BillingCity nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingCity sql.NullString `gorm:\"column:BillingCity;type:nvarchar;size:40;\" json:\"billing_city\" xml:\"billing_city\" db:\"BillingCity\" protobuf:\"string,4,opt,name=BillingCity\"`", "//[ 5] BillingState nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingState sql.NullString `gorm:\"column:BillingState;type:nvarchar;size:40;\" json:\"billing_state\" xml:\"billing_state\" db:\"BillingState\" protobuf:\"string,5,opt,name=BillingState\"`", "//[ 6] BillingCountry nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingCountry sql.NullString `gorm:\"column:BillingCountry;type:nvarchar;size:40;\" json:\"billing_country\" xml:\"billing_country\" db:\"BillingCountry\" protobuf:\"string,6,opt,name=BillingCountry\"`", "//[ 7] BillingPostalCode nvarchar(10) null: true primary: false isArray: false auto: false col: nvarchar len: 10 default: []\n BillingPostalCode sql.NullString `gorm:\"column:BillingPostalCode;type:nvarchar;size:10;\" json:\"billing_postal_code\" xml:\"billing_postal_code\" db:\"BillingPostalCode\" protobuf:\"string,7,opt,name=BillingPostalCode\"`", "//[ 8] Total numeric null: false primary: false isArray: false auto: false col: numeric len: -1 default: []\n Total float64 `gorm:\"column:Total;type:numeric;\" json:\"total\" xml:\"total\" db:\"Total\" protobuf:\"float,8,opt,name=Total\"`"}, DBMeta:(*dbmeta.dbTableMeta)(0xc00009b2c0), Instance:(*struct { BillingState string "json:\"billing_state\""; BillingCountry string "json:\"billing_country\""; BillingPostalCode string "json:\"billing_postal_code\""; Total float64 "json:\"total\""; CustomerID int "json:\"customer_id\""; InvoiceDate time.Time "json:\"invoice_date\""; BillingAddress string "json:\"billing_address\""; BillingCity string "json:\"billing_city\""; InvoiceID int "json:\"invoice_id\"" })(0xc0005f7e00), CodeFields:[]*dbmeta.FieldInfo{(*dbmeta.FieldInfo)(0xc0005c6640), (*dbmeta.FieldInfo)(0xc0005c6780), (*dbmeta.FieldInfo)(0xc0005c68c0), (*dbmeta.FieldInfo)(0xc0005c6a00), (*dbmeta.FieldInfo)(0xc0005c6b40), (*dbmeta.FieldInfo)(0xc0005c6c80), (*dbmeta.FieldInfo)(0xc0005c6dc0), (*dbmeta.FieldInfo)(0xc0005c6f00), (*dbmeta.FieldInfo)(0xc0005c7040)}} "TableName" string "invoices" "apiFQPN" string "github.com/alexj212/test/api" "apiPackageName" string "api" @@ -307,13 +363,13 @@ The following info is available within use of the exec template. "serverScheme" string "http" "sqlConnStr" string "./example/sample.db" "sqlType" string "sqlite3" - "tableInfos" map[string]*dbmeta.ModelInfo map[string]*dbmeta.ModelInfo{"invoices":(*dbmeta.ModelInfo)(0xc000191540)} + "tableInfos" map[string]*dbmeta.ModelInfo map[string]*dbmeta.ModelInfo{"invoices":(*dbmeta.ModelInfo)(0xc00057b400)} "updateSql" string "UPDATE `invoices` set CustomerId = ?, InvoiceDate = ?, BillingAddress = ?, BillingCity = ?, BillingState = ?, BillingCountry = ?, BillingPostalCode = ?, Total = ? WHERE InvoiceId = ?" ``` -## Struct naming +### Struct naming The ability exists to set a template that will be used for generating a struct name. By passing the flag `--model_naming={{.}}` The struct will be named the table name. Various functions can be used in the template to modify the name such as @@ -363,6 +419,10 @@ Table Name: registration_source |ms sql |y | y | y | y | y | y| n ## Version History +- v0.9.26 (07/31/2020) + - Release scripting + - Added custom script functions to copy, mkdir, touch, pwd + - Fixed custom script exec example - v0.9.25 (07/26/2020) - Adhere json-fmt flag for all JSON response so when camel or lower_camel is specified, fields name in GetAll variant and DDL info will also have the same name format - Fix: Build information embedded through linker in Makefile is not consistent with the variable defined in main file. @@ -441,7 +501,6 @@ Table Name: registration_source - Fix for duplicate field names in struct due to renaming - Added Notes for columns and tables for situations where a primary key is set since not defined in db - Fixed issue when model contained field that had were named the same as funcs within model. - - v0.9.2 (05/12/2020) - Code cleanup gofmt, etc. - v0.9.1 (05/12/2020) @@ -455,6 +514,7 @@ Table Name: registration_source - v0.5 + ## Contributors - [alexj212](https://github.com/alexj212) - a big thanks to alexj212 for his contributions diff --git a/_test/dbmeta/main.go b/_test/dbmeta/main.go index e72899a..d5cf069 100644 --- a/_test/dbmeta/main.go +++ b/_test/dbmeta/main.go @@ -30,7 +30,7 @@ func init() { goopt.Description = func() string { return "ORM and RESTful meta data viewer for SQl databases" } - goopt.Version = "v0.9.25 (07/26/2020)" + goopt.Version = "v0.9.26 (07/31/2020)" goopt.Summary = `dbmeta [-v] --sqltype=mysql --connstr "user:password@/dbname" --database sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ] diff --git a/code_http.md b/code_http.md index 035df26..d337a6e 100644 --- a/code_http.md +++ b/code_http.md @@ -138,7 +138,7 @@ func GetInvoices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // @Failure 400 {object} api.HTTPError // @Failure 404 {object} api.HTTPError // @Router /invoices [post] -// echo '{"invoice_date": "2311-01-24T01:05:44.420640458-05:00","billing_postal_code": "EOINbzuauKmMHiaDymdYsvXDx","billing_country": "cqtEUexVoitsOvqknKNnhShPI","total": 0.932417421427433,"invoice_id": 50,"customer_id": 37,"billing_address": "UgIfqiLZTewHKFZZuTZDwdHpf","billing_city": "mWRUstvZAmHtOuvtsJzocfFFw","billing_state": "gInXWdcekISCnmaDBcsDRWcyB"}' | http POST "http://127.0.0.1:8080/invoices" X-Api-User:user123 +// echo '{"billing_state": "USFzllBypZZJDjtbuDWBnWcrU","billing_country": "KgrvVzwkLcFjnchqPwVoHWkRT","billing_postal_code": "ScJIRKwUTRltasmLeQNMLxJYS","total": 0.03391519763659607,"customer_id": 66,"invoice_date": "2123-11-18T21:39:12.15674111-08:00","billing_address": "nPoieNSaqVDVMBQvfwSXcYdtM","billing_city": "ObTQBQeAscRjOcAkHOOKmkHXU","invoice_id": 7}' | http POST "http://127.0.0.1:8080/invoices" X-Api-User:user123 func AddInvoices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { ctx := initializeContext(r) invoices := &model.Invoices{} @@ -192,7 +192,7 @@ func AddInvoices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // @Failure 400 {object} api.HTTPError // @Failure 404 {object} api.HTTPError // @Router /invoices/{argInvoiceID} [put] -// echo '{"invoice_date": "2311-01-24T01:05:44.420640458-05:00","billing_postal_code": "EOINbzuauKmMHiaDymdYsvXDx","billing_country": "cqtEUexVoitsOvqknKNnhShPI","total": 0.932417421427433,"invoice_id": 50,"customer_id": 37,"billing_address": "UgIfqiLZTewHKFZZuTZDwdHpf","billing_city": "mWRUstvZAmHtOuvtsJzocfFFw","billing_state": "gInXWdcekISCnmaDBcsDRWcyB"}' | http PUT "http://127.0.0.1:8080/invoices/1" X-Api-User:user123 +// echo '{"billing_state": "USFzllBypZZJDjtbuDWBnWcrU","billing_country": "KgrvVzwkLcFjnchqPwVoHWkRT","billing_postal_code": "ScJIRKwUTRltasmLeQNMLxJYS","total": 0.03391519763659607,"customer_id": 66,"invoice_date": "2123-11-18T21:39:12.15674111-08:00","billing_address": "nPoieNSaqVDVMBQvfwSXcYdtM","billing_city": "ObTQBQeAscRjOcAkHOOKmkHXU","invoice_id": 7}' | http PUT "http://127.0.0.1:8080/invoices/1" X-Api-User:user123 func UpdateInvoices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { ctx := initializeContext(r) diff --git a/custom/custom.md.tmpl b/custom/custom.md.tmpl new file mode 100644 index 0000000..2d6ca32 --- /dev/null +++ b/custom/custom.md.tmpl @@ -0,0 +1,11 @@ +custom.md + +{{ range $key, $value := . }} + {{ $desc := spew $value }} + {{ printf "%#-20v" $key }} {{ printf "[%T] %#v" $value $value }}{{ end }} + + + + + + diff --git a/custom/sample.gen b/custom/sample.gen index 9974ca3..fbd7633 100644 --- a/custom/sample.gen +++ b/custom/sample.gen @@ -20,8 +20,7 @@ "tables" [[]string] []string{"albums", "artists", "customers", "employees", "genres", "invoices", "invoice_items", "media_types", "playlists", "playlist_track", "tracks"} -GenerateTableFile(tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool) -GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool) string +GenerateTableFile(tableInfos map[string]*ModelInfo, tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool) "FmtFieldName": dbmeta.FmtFieldName, "singular": inflection.Singular, @@ -37,7 +36,13 @@ GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOut {{ range $key, $value := . }} - {{ printf "%#-20v" $key }} {{ printf "[%T] %#v" $value $value }}{{ end }} + {{ printf "%#-20v [%T] %#v" $key $value $value }}{{ end }} + + + +func (c *Config) GenerateFile(templateFilename, outDir, outputDirectory, outputFileName string, formatOutput bool, overwrite bool) string { + + */}} @@ -50,9 +55,21 @@ GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOut {{$snakeCase := toSnakeCase $table -}} {{ printf "[%-2d] %-20s %-20s %-20s %-20s %-20s %-20s %-20s" $i $table $singular $plural $title $lower $lowerCamel $snakeCase}}{{- end }} - -{{ range $i, $table := .tables }} - {{$name := toUpper $table -}} +{{ $i := 0 }} +{{ range $tableName , $table := .tableInfos }} + {{$i := inc }} + {{$name := toUpper $table.TableName -}} {{$filename := printf "My%s.go" $name -}} - {{ printf "[%-2d] %-20s %-20s" $i $table $filename}} - {{ GenerateTableFile $table "custom.go.tmpl" "test" $filename true}}{{- end }} + {{ printf "[%-2d] %-20s %-20s" $i $name $filename}} + {{ GenerateTableFile $.tableInfos $table.TableName "custom.go.tmpl" "test" $filename true}}{{- end }} + +{{ GenerateFile "custom.md.tmpl" "test" "custom.md" false false }} + + +{{ mkdir "test/alex/test/mkdir" }} +{{ touch "test/alex/test/mkdir/alex.txt" }} +{{ pwd }} +{{ copy "../_test" "test" }} + + + diff --git a/dbmeta/codegen.go b/dbmeta/codegen.go index 159a767..8a0d5f5 100644 --- a/dbmeta/codegen.go +++ b/dbmeta/codegen.go @@ -111,6 +111,10 @@ func (c *Config) GetTemplate(genTemplate *GenTemplate) (*template.Template, erro "replace": replace, "hasField": hasField, "FmtFieldName": FmtFieldName, + "copy": FileSystemCopy, + "mkdir": Mkdir, + "touch": Touch, + "pwd": Pwd, } baseName := filepath.Base(genTemplate.Name) @@ -555,10 +559,10 @@ func Exists(name string) bool { } // GenerateFile generate file from template, non table used within templates -func (c *Config) GenerateFile(templateFilename, outDir, outputDirectory, outputFileName string, formatOutput bool, overwrite bool) string { +func (c *Config) GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool, overwrite bool) string { buf := bytes.Buffer{} buf.WriteString(fmt.Sprintf("GenerateFile( %s, %s, %s)\n", templateFilename, outputDirectory, outputFileName)) - fileOutDir := filepath.Join(outDir, outputDirectory) + fileOutDir := outputDirectory err := os.MkdirAll(fileOutDir, 0777) if err != nil && !overwrite { buf.WriteString(fmt.Sprintf("unable to create fileOutDir: %s error: %v\n", fileOutDir, err)) diff --git a/dbmeta/util.go b/dbmeta/util.go index 047859e..7438234 100644 --- a/dbmeta/util.go +++ b/dbmeta/util.go @@ -3,10 +3,14 @@ package dbmeta import ( "errors" "fmt" + "os" "reflect" "strconv" "strings" + "time" "unicode" + + filecopy "github.com/otiai10/copy" ) // commonInitialisms is a set of common initialisms. @@ -243,3 +247,46 @@ func Copy(dst interface{}, src interface{}) error { func isZeroOfUnderlyingType(x interface{}) bool { return x == nil || reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface()) } + +func FileSystemCopy(src, dst string) string { + err := filecopy.Copy(src, dst) + if err != nil { + return fmt.Sprintf("copy returned an error %v", err) + + } + return fmt.Sprintf("copy %s %s", src, dst) +} +func Mkdir(dst string) string { + err := os.MkdirAll(dst, os.ModePerm) + if err != nil { + return fmt.Sprintf("mkdir returned an error %v", err) + + } + return fmt.Sprintf("mkdir %s", dst) +} + +func Touch(dst string) string { + _, err := os.Stat(dst) + if os.IsNotExist(err) { + file, err := os.Create(dst) + if err != nil { + return fmt.Sprintf("mkdir returned an error %v", err) + } + defer file.Close() + } else { + currentTime := time.Now().Local() + err = os.Chtimes(dst, currentTime, currentTime) + if err != nil { + return fmt.Sprintf("mkdir returned an error %v", err) + } + } + return fmt.Sprintf("touch %s", dst) +} + +func Pwd() string { + currentWorkingDirectory, err := os.Getwd() + if err != nil { + return fmt.Sprintf("pwd returned an error %v", err) + } + return currentWorkingDirectory +} diff --git a/go.mod b/go.mod index 8f7fe1e..2288dc7 100644 --- a/go.mod +++ b/go.mod @@ -8,39 +8,35 @@ require ( github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 github.com/droundy/goopt v0.0.0-20170604162106-0b8effe182da github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 // indirect - github.com/gin-gonic/gin v1.6.2 // indirect github.com/gobuffalo/packd v1.0.0 github.com/gobuffalo/packr/v2 v2.8.0 github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/protobuf v1.4.2 // indirect github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf // indirect - github.com/grpc-ecosystem/grpc-gateway v1.14.6 // indirect - github.com/guregu/null v3.4.0+incompatible // indirect github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 github.com/jimsmart/schema v0.0.4 github.com/jinzhu/gorm v1.9.11 github.com/jinzhu/inflection v1.0.0 - github.com/julienschmidt/httprouter v1.2.0 github.com/karrick/godirwalk v1.15.6 // indirect github.com/kr/pretty v0.2.0 // indirect github.com/lib/pq v1.3.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mattn/go-sqlite3 v2.0.3+incompatible - github.com/mwitkow/go-proto-validators v0.3.0 // indirect github.com/ompluscator/dynamic-struct v1.2.0 + github.com/otiai10/copy v1.2.0 github.com/rogpeppe/go-internal v1.6.0 // indirect github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 github.com/sirupsen/logrus v1.6.0 // indirect github.com/spf13/cobra v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 // indirect + golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - golang.org/x/mod v0.3.0 // indirect + golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect - golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect - golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d // indirect + golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 // indirect + golang.org/x/tools v0.0.0-20200730200120-fe6bb45d2132 // indirect google.golang.org/appengine v1.6.5 // indirect - google.golang.org/genproto v0.0.0-20200608115520-7c474a2e3482 // indirect + google.golang.org/protobuf v1.24.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect honnef.co/go/tools v0.0.1-2020.1.4 // indirect ) diff --git a/readme/main-packr.go b/main-packr.go similarity index 100% rename from readme/main-packr.go rename to main-packr.go diff --git a/main.go b/main.go index 4ea193b..a1e14d8 100644 --- a/main.go +++ b/main.go @@ -101,7 +101,7 @@ func init() { goopt.Description = func() string { return "ORM and RESTful API generator for SQl databases" } - goopt.Version = "v0.9.25 (07/26/2020)" + goopt.Version = "v0.9.26 (07/31/2020)" goopt.Summary = `gen [-v] --sqltype=mysql --connstr "user:password@/dbname" --database --module=example.com/example [--json] [--gorm] [--guregu] [--generate-dao] [--generate-proj] git fetch up sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ] @@ -453,6 +453,13 @@ func execTemplate(conf *dbmeta.Config, genTemplate *dbmeta.GenTemplate, data map data["outDir"] = *outDir data["Config"] = conf + tables := make([]string, 0, len(tableInfos)) + for k := range tableInfos { + tables = append(tables, k) + } + + data["tables"] = tables + rt, err := conf.GetTemplate(genTemplate) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error in loading %s template, error: %v\n", genTemplate.Name, err))) diff --git a/packrd/packed-packr.go b/packrd/packed-packr.go index 49fef16..66f60a8 100644 --- a/packrd/packed-packr.go +++ b/packrd/packed-packr.go @@ -33,7 +33,7 @@ var _ = func() error { "93bd183343e73a133e7c243e834a15f8": "1f8b08000000000000ffac554d73db36103d13bf62c31399b854cf4d7d882d67c69d54f2d4b1a7d31b082c243820402f405baaa3ffde014849946ccff4201f2c72f1f6bdfd20765b2e7ef005c2cb4b25b9bbe9df66bcc1cd8631ddb48e02142ccb85b301572167598e448e7c7c524d32102a8322e48c65f94287655757c23593076dff5d769385a32667256393095c74dac86bab1c680f9d4709c18144a52d425822f0b6355af0a09d853a62415be5ce80dbf8f48022c013371d7ad0363878d23cb9f5d0969c40ef2b16d62d8ea47ca04e0478610c00601bc594070419fff940da2ec029785ee256f7997b689194a30625286d0cc610a05ec32f7f83704dab0d82327cc1b23d1fc0c0c6583699c0371ed0874bd7343a9c48eb8072ac958298754d8d74cab406c65752616eaf6f4e28d4f3c19b42737f5aa1b93f16faabb34137787fb2da8d08774a1bd67f99dfdce2f6d180eaac284458c170b5aacbfef70cfca3197c4ac69e3841b1fd72af88662e7c759d9590ae611f1fa17024c1ba002a9eb16c0c8473504da8ae225e15f9009eb900e93c2ffb1a5c11dd595e1bfceefee4e497dcfc713b9f8d651ebcb3d0f2b5715c8270445d1b5264efb81e09bfe51ec5f7b9ddb5b1fa5fb98e951d0977c90e8a6be3536e07c02319596ff1896194deb5f548e135bd4ef611fd01f035fd803fa69fa2c1b7a297c93ea23f00bea61ff0c7f4175cde70e28d1f73d75c42db5b5bee7dfa2093c61e7d2430f2d8290c1d985e00a142422b300d661e78cd3db26c7a01fbbf8f71a257d38b3eb22f6dbb1fb507eefb01ceb203d4c7dde33026dd6281942e449afc61c9033c6b63a046d0f6c9fd4009352a4708b842d1857839fda361d9e0da5fa9b864d29ab974ed1a387812dbe19fd60507893e68dbef97fe8445d5e450489f70488a0b7cd99c25ff91a11c0affc232e9c33dfc760ec3e6abaeadd48422145bc37ddc52731539cb92659ec4ffc37b12656cb956f0218a5497dc7e91928a32ca6684a123dbc7e1ab193e17b988b9c65d1819d25ab55c4a42efe385cc4b966d7abe1843f57ddd6251c2877348ecc3eb3bd452abd4ce00716e7910dcc68e08d76a945b66e50874ccedd7cfa0e1f79e77d6355f351a59949f417ffa94045404a520fa235db22ce5a9fd3f486eaeeeac44326b6d17292c555d6f6b5f947d8ca9ee3bf7ea1643a122cba60f6548c16a13676ddfd977c857878dad9d3351616058c1f979a4819f3f772d9b22b6578f1d37c5ea6c678cdcbb1e46e2b92a5665791039dbb0ff020000ffff099f892465090000", "9ec72c1db17b72aeed9a9dfae4794622": "1f8b08000000000000ff84924d6bdb401086effb2b5e924b0c967a2f6d21d8b49796b46e028510a2b176b45ebada51b52bbb69c87f2f2bc91f710cbe8e9e79e69dd1de9752d7ece3c37b7cf884abdb950db00104c39e5b8aac5159c7681c5360b0b61141bab664588ffc5de4ba7114394cd491eada39d4a26d654b8a563c36d6392c194e429ce2493aac68cd58327b6ca8f5acdf38264a5d5e62b6b89b637e7d83cf9d2f932aa8c2b02f06e336283409aa2d015b21ae1845966d814c931469b98642608d28e835396e578c5234a3249f12965d8852db7facb1b171b51569928fbda37264527be0d87ff35433641898523454fe26c3b952b303ebfea2d6236e2469d6d2b5618a9f3fbefec2f2099a2bea5c04798d2f378b6ffbf1fdc8b48cb475a194da451ead56fcf4e81efb5b54d2aa0cf70b8eade5b5f5062d97d2ea30f81b32d69b87abcb11e0ec3b19d6d962802607bd0c4268b84c7f75941cf60d95be61d6720a417b6aa81c32778d3e6286ca213367c7af99a1b263d20bd9a5eb83630cae8aa230a29e9fb78f14179ae431fc717f1f0d47722e3792c7ba7117c85f5e12feda368c38a339ed18d73f6f20ad4f1bc6e39c37743d785a325eefbc44f7e05bc9ff000000ffff6236e43020040000", "9ecd3f03cbe3e3ef5337a260a193f876": "1f8b08000000000000ff8c945f6fd33014c59f934f71895448509ac1847898d4876d5d61b07f6a2721046873939b60e1d8ad7dc356ac7c77e4c4ddbaf247ed4b1dfbdedf39b64f626d8125970851c1d48d598afb9b0a890991552aa37a21a2b60df7f6e01dd2a110d66633d24d4e17acc6b6056e8041d9c89cb892400a2a24606004cf1154091a73a58bd824506a5583b5d9359b0bf4dde4c6c025d077746b63466ccecc7ab9f08f4e7ec134ab0d0c61c12a04f7f3438dcb060d61017181256b0419e7e355f24797e1bf1086209b7a8efad19c710698e73e61ec6f41942e50f7d2c51c8cd2e4a772259a5aba5ad45a6918c289d6178a26aa9145ea8a275c16fd62e84eebefa719e7740fb99284f7941df7ff69672c7ddc0097f4f64dea850d692eab04628da673fde5db4b6bb35a1528ae58fe8355fe2cb32da9144811135375e7b64fa9f3d6fb4bc08681590a381841646d6650604ee78d203e5b8ab68dc230e0a5d77f368228720d6eaa37633ae78c4b73285771579642f4e26b1475e420e83b5d6318046d18b49bc0d103f0a1ccdaec4af39ae9d5475c990f8a4b2c9c8d75e3f8281b6bfe13b5db599c7488da98a5e8396e2b23286bca660bcd25957134305e6dbe828181cbc96476720d8302a6979f663039b93e7e0f93d3e9ec71eef2e2ec739482590a7ff2dbd79284410b280cc23f1c2d94a14aa3d9c9d4edc0dc6ed83a3b3d3f75831d1dec2ab0c6a6bba0438f1c1f65539c7359c4662992fe06ce5455f561905c74f2fd8ccb73c74dfacb7219eb08b32e533ee17dd5739fe0753d2fbb486e303552a3a57b4c61f8ba4b6c8fcd25411fe083917bb1a6eaee5835d283a3a7df9ce87fec070fc3fd0dfef662eedf97b00d436b51166d1bfe0e0000ffff9fb98df146050000", - "a2d5586edb9ff79f53fe610c4e91de3d": "1f8b08000000000000ffbc7bfd531c3992e8cf537f451e9e8d03477735e099d91966bd1718b0c7ef307801cfbb0dc6e15657a9ab35a8a5b2a4027a80f7b7bfc84ca93ef8f079e22e7623764c49a9cc54662abfa43e2fec72294df8b8037ffb3bac9f2d9407e54140258d7422c812e64a4ba8b5145e822c55006f1b57485006f24990cb5a8b20fd46760fd5aed6b0b4a59aab4204650d5c29ad6126415b1f46b0b20d2cc4a584999406ae8433b27c8063237bf60c39c9b2f37f3b3f5485345e7e5c5f8450fb9dc9442dabdc2f94d4a5cf959dcc4459c949841aefd6a258c8bf6c6fbec837c733ddc8dc5f561bdd625b4bc31bc9adab269a97f909af1bbfc83737e0fcdfcedfd87d5b74ab2a5bda8216542a2c9a595ed8e5c42f85d646fa30a9a4f90f1f44687c5e9b44edab966d00520b4e5c2adf91e3ef71a168ed60016ee73f664e9862f172297c906ee3abd6c55dc189acad0bb0275cd9df9ea3e142b8927864993ec9f41756f2e7d34bb3ec6c2151b710acd5503b5b368544cbdb3bf9b00feb7b4e8a2047e0a42847d0d4a508128429a1945a06b9012707a767206a854b7f974580648a3077768916ac2ea581520431135ee630a087c69815d6185a69212c249433882368ae3e38652a1046e8d51f9201222ee2231d109a296c2901a74ab08646e65a541e79bb54a52cf32c7bb5a2bd20ce5206a1b46746878867b60911a36e96c4455384c6c911f19e88d209b550d8652d829a69190121ac6a995da9b020244e7e6e949365c266c452fa119123483fa2ad08636ca043eaf32c7b1bc037356acfc37965ddb2a7e54e9bbf2bf3c7a241bd2f3720e05e11935ad65ae201f6e0ed5242e30532b79461614b9fc39bd6ab743c8032856e4a99a8c2dc3a308dd6b49439f770ee3febfca8d1fabf7855dff4b43015597a7d514d922c27feb39e3cc315afacd51b601d9c578d935543c8f38778bacd31dc04e13632dc170a535ed7b240d667c2ab02668dd2019d606519539e65d34a9a29fa4f36850928e36b52c06c4538aeacbb003b8753191670ba90da3421fcbb87f372b6cd1a7c949f04b93d69e1365875fbb3ed53d67ca23a798ae6de422da5137bb694eedf3d54f6776f0dd4a2b81095444de3f7a3e4fb2b270cb791675986de994e6b54abb226eb228817680c10a21a4d10cac812d038159f917c22af0968c2b0793983d3cf5a05f9a2776e3f783c332c5a8a21839337b75adb2b8460b9e4d9743af59f75b67772b07b760067bbaf0e0f604de859b3f46bd97a060070be8b9f6fcb8f006f8fce0ede1c9cc0fb93b7ef764ffe09ff79f04fd8fd7076fcf668efe4e0ddc1d1191c1d9fc1d187c3c3112f3d5341cb8ff8e7d1afbb277bbfec9eac6ffdb0b9711f6cd705e5039248148600af8f4f0edebe39227aeb1d34fab5d7072707477b07a7b02668d8af0d20b26fbe393e82fd83c383b303383a86ddbdb3b7c747707c041fdeefeff6c7b20d1446f6ecd9b36770e684f173eb9678de82c589ca6668b840c2480ee426fb66323987cd8f1065045ff93f6582aca41b0ee209da81b9d05e02d44e2d855bed40708d0400d104db7e1456ef3cc0a1a5d981f1167f94722e1a1d76e0fc63f60d33b78f2b608a4e68672da2ff7421573fa3063fb52afc999dc84edcd1cfb8eb9da8969fd700ed79872de4932ad7a09cedac45d035f4e0c1ce9af9ce9a32e1c5f6687364eb304247fab25d3165996d7d04328eaf951880b914ae5808c716f44599c56f9659fc2099251c03996dfdb0f95066cc5c8c6b5168513234c5721958f5cf5efd2177b67ed86cc51410926574c67f761262d4a3ad4e440c1de5b3fd119215ffefdbd493f2f91336c5cc0d8c2a594ee4fb09d3a1d9ce7622f043e3d9ee194fbb669addd13145f79a7c1b262018060585784c78d8d13ae9439bf0280fb5f59ec23f85fcfddd63285c53c2bc3194c4f811a03b878530a596ce8f60292e2426f4a3e49ebd7497d2817012c4a550147473d85bc8e2023017a1306ee7e46bcf8b1e8bb28c28fcc7f5676d5c1f9ff2581b1f5e2923dc0ade1a1f84d61c23a6d3e9abddd35f705af1789798adfb856d748985429c93252668ff6f52d9c94c19de02f8c6c9760c25a10c96140e6a111679f62d46e54a061837f0540a4aec95f6ca682bca561c147edae8937d0b5788e691a038c03571e26ac269f8c39896c52a8683d6c354f11e4d584f0a4e15d397832794ca6de08ea581f1d87fd668a32f7d8ca3bf61b4f9663cc6ccd607076b79c7d85a3bdbd25e0a65a01da60c217de081e83e38934a9f6497e903cda6e32e8d2e6dd968cca2983ad7083e3c06d7fe1d8db39d8ac63be06f3c5f8697dea051b4cc45598f4b611f0ea2743b5e2fa5bb722a485212e67465629135b5ce63b5b385f49e55928c167327b70d6a0ec686ce5c511945d98920fb966d367d12253e165d4d5cd0891228b99932edd267cfe06a210d68d19862818ebb33ffb0105c2b9cfee3100d8844d36552cab768a3c978b1246b01e1e99b99406e6bc8f349de330d003c44dd1688e9e35a1a103073f6ca4b87a7f2e6e6db9c95745a2ce452dedded4c26dde02fd687bbbb9b1b94908434fa1e93fc1f37efee763a481c434869cabbbb89bf125525dd4499525ee78bb0d444ff8397741c2745e3f404cfa492c8c55c866201978abce512d362ad8cccd8f571058e92d00bebc3ce8f9b3f6e4e381dcc10cf53109c7a65e49a23755149fc2cacf156cbece6267f23cd2f52d777ecc1c9dfa1b9285351799b0e723cc09815b7368cee5d0abf62a3e352b3a40da05b10fec2e7d9739892ba1752d7531883563e746e1a8270950cbe05234c0837c890a3a94d737454d316386a16c15dc3f6811e24197af44de92c9c7ed66463e5ac6f54ad7d78a8b528981e7755fc603e3b0e0bb4186618e4356e042550cacbb45994de4104ef0b2fd6d0bd9222da7bebb4a2794f5b7253b4725904eb5639bc4e557642387d1775308542183c7e8de72033905bdf0d505591cf845f6483a34c5a8f8d85181407ce3b2afe5ea5428ea531254ae449beb3e790f8cc009e238ff346b76324bde8721027064fdfd952e242862287b7de3792d53ec56d96cad75aacc8ac30c8d74d407279a582aa8c754cb05201f89368553621cd9e4365f3a52d19cc4274ed5e86a61e412dbc876972f853ea8410062f4320567bba884b631e06d37e784842c99ec3c9c1eefebb837cc924dfc7c54e8a7229b3e720ea7ac27e6482012caf2cc1712e026f9481539a1d719e14dd0b28a3427beaa4a193246a35f91bd7ac98a7fd3de1a276132557a8536735e653d97328857d141e73b1360d8b368c9ba7344e1474c6501529d1788ea290fa515c5ce17b70b276d24b435214e0ec55f422c5a23b0cb49e3cd6b367bd7e4bcccbb2e770fee6f8e41df147bcbc4e4c7e5ccf2768999f4a613f61b0cf97e506c29ffee3f0bffe3b78ff595f27f85fd0ef12ec2f31ef6c41d1d726b077b8df7686769fa6dec7bc19f6e51c95a4ac6901534e4db07cf8da5660cc179aa0b4fa43729043edcf9d58ca2beb2e46409b2f67048eaa3fbd8ad6d9a9289947698b06cd829356149c7abc3f522933aeac5105fec5224314f65160c26ee91f027df3351d3604e46ce37106502073a1ed84803662ee7dca0d3559c27eb40e9fed35ce4913f4aa9b1d656378279c12fbaff0afd5e93f0eb331bcb73e544ef2c73b5538ebed3ca0ef8d87291b47479c65efb530989e4694d9188e9d2862aa33e402ce56b5f459f6cefad06f026201e25b8e4823ef56feb31e2546fc28f97d54dbbb53fcca635ba8f192636b5da302296d25274929d23d272fcab285e4c61dbc5a250f34229bc19ad119a15b384246099528314d336d4628cb1cdece138d944e066900cbb7b224e3153aedb22cb931d7f8e409d153329597f23a389123f7d3412603b6462c395b7beb2be3b2b8a06391c3f3a05468af6930c0e494484d63bd202ee5cb1e0005081c8c5d6813a89b1b2bc00e4fb018b7ba6579f64a6a7bc5ad3fd42296b071555f8a23f00b8e828319eb601a1b19d39c922edc1575ca6e32aed5d7fc67fd09c5b8b6036b11766d94262bdbce711ba29b4a1ea30598ad82f4bda554cbb4b3d41d3ebd87a3b29f524bbaa3133bd2119640efba7c71b7bc14a69025298ded345d75703e93c283d02aac50a05aacb076a23ad65e993648cead5b8a406a739e6fcdb6a34d5092f89e3192ea754f47ed3dc0743c4ea3fbcabd9cc6c53086b050fe5e5795b393d63eef21cde1acc58f462f15657633ea8ff331a76b0dda70863939db115b73cfe8209f2c573dfb81b39695d6fce47226e9c4747b424b6196249e2d53d1c50e15ff70459d035fcb42cd575fdc38d554a9d742c134ca3acf509ec273cf793c96d7b278d91546a4c5981a0f4e28f14d8d0422db07f7855335e71988ad091254e0eba838b510be9712d039a3244019d63c728ccb2d891a4729c9627746828ed21398617a4caaa5b6351a4bb050343ed8a58a7758bd06939d736e0bffb40d14243b6d6d0d61e16c53b1e550c581c718198a773ce6d25ec86c9a728b339c7a4de9b47500ed380de55946d88501a1bd85dad68dee6ecd4c90d7813df36c75df72c91d728c98ad68c73d9f496b8bf0f26fadb3ff7bd22ecbb60b02a9c84e01e0691ec82c6ae198506bf2184ea95f9e86b2c9040e07a2eac4533b6502b5ceba4c8e262e8553b6210b5e760eb585c17232ba0ccfc9dadd5d86841ec8799d161d89a51cb54ce2b8a111cee7f7531d9106100097c4feef283a95639a8499b57aa34f8ce9fc2f228f531cc2ba52888c192db72d6a63dc6a7cdb76c453d36e34cfb29b1b70c25412bebd90ab117c7b29742361e725e47077875ef8e686b53087b5bf3c1b6f7f7fb946a07077776f72fc62f30cfef20ce71949fce7eeeee606a429115fd696f6f1a2cd8865aaefc54c91efa67a960e9b97014477154df9c7c0fea894ea3b1edf62959488f4ed9c4a272ea5a4fec4845fdedcacdddce477776b777753e222624864105379cfb872f835595f5b93f4d3a29828747c5b7ebbc15e944cd837c50284efce336a086785ab284d464611f253903ebc6cbc7453c45b58f37ba4390848fd3d91e7988ec7785c71484e47c8c2a592577095ba5c0c1a9dfc0c0de1b62d4600e016e88000d921dc021b5f763b1e8fdbff67b728da46d36dc92d8753849ddedcb4136b34bc0677775380dbecb6d68d13584e3c58d2cddc5f13e235d0109e471fc0da437b251f3294c61fc27fa8eb47e179fc09fc7b6229f51e16884f10ea009ea0f8250cf7001e623835e2423eb1b89bbbb78ecb987b072ff5887cd6e97b079cac940f1cd83e71070aed836a4c04c185afc92921d95e59cca81149d63393291db2b5bbbb9cfebb460cc12ddcdcac3d42698d1c0b2e63744f2c6e27bf16057446f924b21ecc57a37da8f1a7d13f84fd1364ee9bc597c8dc87fd13647a06f4250a3db07f91a8c8017ffa1709ec4f13fbf362fb1f90580727b9479cc36f11d16f6bf0dbda6f6b1b78de2386c709778bbfc0429a5b83354209ec429e3d83231ba4e7d6c6673d82777ed057e03610dfc1516d25ba66da52064109285d3150bf3605f4cf8d74abf86e8cdfe2c4476a14139b604199c2d163a851baa606cc4210b47dde44f90fe604949b5bb7ec3dc6cab331c734cf642373c69ab10fc294c29503c4eb47c747ed7577bc6174c0d5e9462f2148890237774beebcefbf82773208eed11c5a7aa896dde23085d6554d41f528f10db7f03e92fe4fb9c2cf5ddcf2dbb4651cd9e3476787d2c06ddbb5f81513ac5b28e8791f94a51e46e8c7fe9fddc61b5280db15b1b3baff9f5b58ddc22abbad934eff1b5093dd2e57feb3fe0a944b0f5f01c8b7c9bf4ae7311bf945794c8fb3315c6ee63fe5dbdfc3fae65f27db3f4cb637b73737a8513086dd72211dd729e3f932747d72a135fc9fd3e32370d2d7d67809de726952e06907aa80afa4fbc49fcac7925751e38cdec372caa60cbc9161576baa3d84e122747fff90cd8eec80aab2452ab8e98690d672061f597dadae77f8666b509376e5792c83b4321754a176d714cad3e568618d573ea065b449203135e3ee3fdd6e2bc3f7cfb8306fa58404c6634ff78bb481f15813aab60532287e0bbbac95c68292efbd826db3dc995c2853829397d279ba83b85e253a6f4ddbad1bf59e920eb3f50f278770253cccd535ca5ae53267c5bc3f3e39c3adfeb8490dc46ec02e55687b22b8dccb0acf47dedac677641b5b2f06b6f11a0980704eac60661b537a507489938a53b6de72461eaac5f582716ddeb33394607b32a811b903335579e994d02368ff5d0aadd3476f7eaead08df514dd0eb38b644b789e8e68f03a2efe8e5b72ca1b295cda90307053d2a09962a8737c7ef77cf7e21db58085716965ec9f2e20fe448cbf82804a473d6618d15804a7ae7431a33856d4c904e9683cd16565b176b53be2241f07b1665ec38c2d5ed7b1b630d2f567fa0f7e412226d748b37fad7c1464f642d8a8b41bfa07dae7c19dd816996333e16787a3ad56f3e869055ef245d0ff2369501679b205d5ed93c2c6b1d419bba72d48f6e9f40b36cf9760eb6f2adef12adad9f1ea3954471a54c8967875c50b0402f22e818c656ccdec9e16b4850d4f0920623841f7153ea4a7131b8e287445a4363d4750c4287af074b5a967e84f5cd1f262f361fd97e4cf5b12c6d7b42e24aacf81ca1c150ebaae44bb3f488b595ecd65f1f437d22e7a20818f2ba03ae39d28dd84d84201df0cbf54ef46dd3a72fb36833c1d2e57965d1815b934caebdc56d19fa8118dafee9c15ea96b30d42e6fb210fa529962214d75fdf8492e678327d3ca4876f0412da50f6259873f5afadf33fda197a117678596c23475ecf8f24e62c15f55d2c726332f48addf68e0beef1f25abe42bf125bebe7b8c2fde27ab365ecdf2c9973ab65bd5bcd7312271b5ef850738e4756d890fcaa3de62d4bb54822eff0aa175c7c70be6637bc007b9792834a3a07669fa7980273ca59ccbe456664d450be8e183ffacafef09a625b54da4b6be1b90a23bd662214c254b78d7f870404d2f0bf42fdd51a3f754a69a38191a6790a3be574b3e9385e4643a4173289b5a2bba131a8191245827416a596146d0427aab1bce3a8702e4b7f9c142903ee47ed1ee638bf7f198ea525f2c70a39a991a458de2662835888407994613b781c4627bdc0f3c034916631fe7df54152cac2ea5eb1ccb2673b635e0ec15eb87fd9993c1297989e4d3f3592d4d15166c4c145d073bea1f348ebd8df1aac2aca594855a0a0d630c8f749c2960266e7e7a8c19de0b8774628871d2e96d9b76254cad2ba59b0e17c5bb49df4bef7a4d7dfa410295112cabd6c4d9db3e9a1bf4d4e5651828a7b0e6529a363e9206e917247684a7888ba7cad505f4fb770fda945fa0e607cd9d1ebd2ff63dfb12c19347426c93ce9e3c9c5c5ad2f3e7c6f25134ed6f7b94896fa19ca4dbaa28290e1e9b3f3d2229da697c65d0233286fffbf63dac6b6b2fd26d26f942b5ac9dbde41fc43c29f2f68851a9f7a4d5b5f6d65928964fad7e39c2dc4bc3925760f7624bdce7bda9c195aeafb50a909ef9f69ac5ca90ba6abe22a4cce05ec6d57b383282c1bb909882f77e51482fb212ddb4010c51df4fb6ee1749e53df661d6bbac677e4b61fbb73529dac746f87d3adf319def0774f6fb6f3d52784b8fa43d3da34aeb5ff0faa1ff6e23631b06465c197018e43b383c317476e8d4dc4b5d621c906150d3c7b7a57850e2d520e7bffc2e3d2dba5075a734351f60883ffd8a7518dd4db97419c3cb2f8556651ba748efc9cb8d60c9cd92badf2c896f8b0787b0ef3454bc2fc48c2f26907429905796e30d1bdba740431d1a7e1198c2550c650caed24fe1a06c281aa5b835b0136af4c4e3c73f1b6b3d2a0f7b151afea11b6a0753d5a1b43dbb24650a49e2ea55a6e5ec31df8d2a4e894a7aa4cd7cc7964d095748a7bb94a1125bf0458c4fb75dfcfa296b2b0336b1eda7b335caab4603bbdc1a2ee2d1c710a5181bab47be618d893037baf8478adcb0a11bb174d8f9fdca600a95418faa82a8863e21bdbb8058ff750e730046a15ed475ff6562cb57da7bdcf6fefee15368fabe32c6f46dfaa9a3c47c783b96b13f8ee0c3e9c1c9a7fd83d76f8f0ef6594adfc79fb259139c9a35c1522e712eb4bcfe7d7bebf12757697203c60002cb65d4b8b920cb4e93c4cc42797e3388a8d1f4b2ec544acec28a1ec91d38ef7f3efe786cf0a383ca897ae127fd551bd9ff0f0000ffff11fc8c36d23d0000", + "a2d5586edb9ff79f53fe610c4e91de3d": "1f8b08000000000000ffc45b7b6f1cb991ff7bfb5354465e443266ba6d2f2e08b4510259921f881e8e242fb2d01a1e4e774d0f576cb245b225cf8e749ffd5045f663f470bcb85c6e01db43b25855ac2a56fd48f65ee4a6aa50fb4fdbf097bfc2e6f9423a900e0494a8d10a8f05cca542a8150a878085f4e04c637304a921cd3c56b5121edd56728fd5ae52509942ce652ebc341a6ea452304350c6f9312c4d030b718d3043d47023acc6e2018fad6463833449928b3f5c1cca1cb5c34f9b0bef6bb79d65b22a53b790a80a974a93cd44516216a926bbb5c817f8fdab173fa42f2633d560eaaecbad7eb2a9518785a4c696990ad35c16e64d7e485f6cc1c51f2ede9a7d93f7b34a53989c2794d22f9a599a9b2a7395504aa3f35989fa6fce0bdfb8b4d6adb46f9ab60524cd5b712d5d2f2eb427b9e4b96b1368397f9b59a1f3c54e259c47bbf54df3e2aae0146b633dec095b0c9767b93b17b6601d834d9f54fa2b3343f3e9a94972be40f22d786314d4d6144d8e14797ba71ff76173cfa2f038068ba218435317c223085d40810a3d6ec1e9c1d939885ad2d45f31f7d08622ccada92882e5356a28841733e130853579148c496eb4e69906fc02a19841eca17075de4a5d82d0422d7fc3401079b11eed06e191dc1408345480d1dc3357a274a4dbb52cb04893e4f592d7423c0bf4422a17145d673c338d8f1c5553b1164dee1b8b63d6bd15ca3bd4406eaa5a7839531809c12f6b4c6ea45f30138b578db458b4dcb4a8d08d591c53ba312f45686d3c6f529726c97b0faea9c97b0e2e4a63ab81977b6ffe2af56f8b86fc5e6d81a7b5122759d50a69033b70a642689c20e52af40b53b814de7659a5d701a4ce5553602b15e6c6826e94e2a941730717ee4aa5c78d52ff0cb386a1a7842e39d2ebcb326b6d99b92b956dd08cd7c6a82d30162ecac662d930f3f4219f7e71812e23baad84d645c6c42f35e6a4fa4c3899c3ac91ca53122c4de09426c9b4443da5fc19422103a95dcd0e982d99c78db19760e670867e01670b54baf1fe8f0e2e8ad9abe0c147f569295f651ddd5670ddfeecd559f07c2b357b4ae6de425668c59e29d0fed141697e7546432df24b5122799ada8f8a1fcecc02dd569a24096567deadd1add2e8a4af204e5030808f6ed45e488d055070cab047d20cbf30511668d3620667574a7afc61b06f3f3ada33c1b45c43d676dedc28656e8822d8254da6d3a9bb52c9dee9c1eef9019cefbe3e3c809150b3a672a364330100b8d8a5e6fbe213c0fbe3f383b707a7f0e1f4fdd1eee9cff0f7839f61f7e3f9c9fbe3bdd383a383e373383e3987e38f8787e330f55c7a859fe8e7f14fbba77bef764f375ffee9c5d67db25deba5f324a295b04ef0e6e4f4e0fddb6396b7d953535e7b73707a70bc77700623c1dd6eb446917cf7ddc931ec1f1c1e9c1fc0f109ecee9dbf3f39869363f8f8617f77d8976c9131928d8d8d0d38b742bbb9b115ed376f68a03409052eb031da04b24abecbb20b78f109a28de01bff93da638976bd9376d036cc857208505b5909bbdc066f1b0400d178d33572a3b61ff050a8b761f232340a9c8b46f96db8f8947c1794dba71930a524b43d8aec3f5fe2f247f2e0e7ce853f8624b21d57f423ad7a3bbae5c711503c6f8708f92c8b1114b3ed51241d5106f766d6ccb74752fb1f5e8d5f8c4dedc7944877ba19d360b3979f8083e35b2d06a0af85cd17c28608faaacd623bd82c36d8662d8f359bbdfcd38b87360bcac5ba168d162dc343c12e6b51fda393bfe1f6cb3fbde8cce48932d8e83cfcec2d14588f5ff6260ad4d13eaf3e411bc5fffe987ad23ebf23a682726b41d5464ed4fb89d0e1d13e7622f1c3e07935089e6ece34b9e36d4ae9b5cd6d0440a80c0a2ef1047842a2b5e87c0778a483da38c7e59f4bfefeee09e4b62960de6806316e0c94ce612174a1d0ba3154e21209d08fdbf4ecd05ea3056111c4b5905c7453d85b607e098445b88c9b39e7da8b7ca022169185fbb4b9d1d5f5c959e8ebeac36ba9855dc27bedbc502ad488e974fa7af7ec1d0dcbd0df03b34db7308d2ae8a010c7b02080f6df5969b299d46109e01a8b5d1f59426a3a5258a8855fa4c933aaca257a9834f0140465f50a73a3951145670e2e3f5df5499ec10db179a428aef1caacb8c9020c7f58d392788a0945eb2154bc2713365b07b727a6af174f28a4dda215a386c9c45d298ad11d17ebe82f546dbe9b4c08d93a6f6194f68a8dbad14e7625a486ae9b1142dba00dd13702926a9b1c976d83c2a6d7aeedad4cd1284251417a382338ff185df73b0667371483774dbfc9bcf23b4e535074ca455b4f0a611e7692757b5dafd1de58e9919d4498ae68550c9eda0c7db535393a175cd2062d6127fb0ae41cb4f17db89233f2a23741f22cc46cdb6449615bf467e29c779420cbcda4eea66e6cc0cd023528d1e87c4189bb0f7fbf10e1ac70f68f430a20364d8fa4a4ebd8c69071a2e26801e1b81d94206d6b48d32c1d8406006da27e09acf4498d1a04ccacb971686957ae56cfd2e0a4b37c8115dedd6d6759dff9ce387f77b75a918510dade0f04f2fffce2ee6ebba7a43ea2445ddcdd65ee469425da4cea02bfa40b5f2996ffd1216fc72c6facca684f4a242de6e8f3055c4bce9615c16225352621f585133859422d8cf3db7f7ef1e71759808309f1798a2240af845373942e4aa4666eb4330a93d52a7d8bfa1daafa2e6470ce77142e52977cbc6d3772dcc0848abb18a6f48ec22d43d085a366c10ba0b420dca54b93e73065772f50d553988092cef7691abcb0257ad7913127a25b43c831d4a62925aa69471c3d4be4b609f14119a40df4989bdabd7076a538c68ad930a8baf870502b910779e156c5ad8d27277e4111131406fc420b210b1478dd2e96ac7710c987c68b67e8c19122c67b97b462784f3b71538a72ccbdb1cb14deb4a7ec96e1f428fa600ab9d0b4fd1a178acc9add8669804f15e94cb845b2b695d9ebf1622116c5b5e41d1d7fefa4c289a5d10559e449bd93e7d0ea99003c271de78deafad87a31e5104f2a9eae8fa5560bf4790aef9d6b30b87d4acb2ca4ab955872585191af1b4fe2d2527a596a6383c0527a084d96559a9669f21c4a9356a6086406626a77e89b7a0cb5700ea66dc29ff24d087370e83dab3af0459c1a71184c87e5a1354af21c4e0f76f78f0ed22a88fc10275b144585c97310759d853c9251014b4bc374018bc05ba9e18c47c70127c5f402524bdfed3ad4bc93442db3bf84332be1b4bfb6bcf8ba89c115f9d41a45782a790e85308fd21316eb60588c615a3cc33891f31e2357b440e3399902d5a3bcc209df81c5daa243cd561460cd4dcc22f9a2df0c3c9f33d6c6c6e0be25e2b2e4395cbc3d393d62fd589737ad929f36d38c22f37321cc672af669556c11fdd93f0efff9afe8dd95fad2d2bfa3bccbb4ef22eeec4829d7b66447b4de6e8457df0e7d88b819f6714e4e924677842da666dab0f9baabc088171a2f95fc0d439123efcfada8f0c6d8cb31f0e28b199393ebcf6e6274f62e6ac3a33079436111402b194e3e7e3f524a3d298d9639fd0a262316e65162e66ef81f267dfb2d376c4418d0c6e30a9041e642998c89b622f63e0b176a58c07e8c0e97ec35d6a2f66ad98f8e93091c092bc5fe6bfab53cfbc76132810fc6f9d262681cc9dc1a67e69e726fdc4cc92426e224f9a08426781a59261338b1228f50675d0b385fd6e892e4c8383fbc04a40388eb34628f1c2ddd951ab78ab8719bf7c96d4767d44ae3b550e330d4d6ba2607326ce524c910e95e921745d151868b3b78bd6c33d0986386ce8c560bd5d131330654a22098a63b4488450aefe7ad8c164e7ad440c7b7a2e0e015aa5d6551848bb9c6b59990326590b2835fbc1529693f5d4332606ae2928668ef72659c1627f42a86f2bc7654e89e69a8c0a40ca4a6f1bc20ae716740c005823ae32db4f67c9b1b4f803d1f6fa86ef5d3d2e4352a7313aefec88b74848db386561c835b842ab836622c4ce345c63465d045abe29bb25512ceea2377a53e931947db308ab4a3713b589a6e2c5c43f4436dc6e808664b8f6e3095cf32dd28df0e9fdde3519acfed95742f27de48475a26bdebf1e26e712d748e053b2dc469fbd411f04c5b1e84927e490655624967273ec79a1bdd15c9b9b195f0ec36ebc2abd9ab18130c123f048eec7a35f051f70e309d4cdade7d6977a671324cc02fa4bb77ab1ad049179ff798a670def1a7a047c9c86ec6f7e3619bf3b3062f38214c1ee22844f320e820cdaae5207ee0bc53a50b3fac66c83ba65f13454a5009696fe9921f76f8f00f377c73e06acce57cf9d585f399aabd6be1621a6d9d26644fe1c29df364825f30dfe90f46ecc5088dd77628ebcd17092c7648ee722beb8033885be311a40fcf51716821dc0012f03e63102075f03c694cd30d9b9a7a19648574c6868ed61384301d816a54a6a660f106f2c67953c9f88635b86032f3806de167d340ceb653c6d4e017d63465881c3e71d0362685e21b8fbe3697984c5b6c714e436f184e1b0bd0f57357642e3408e50cd468694940ca850727704dbea003697559483b86dcd4cb3178d3e48b31d437451aa04c7b32181e6e289db8f6b5af7bfda32584d8b54da809bdfe69721817d8ac0f8ce1129794bb7ae31302e3f55e0bd5706665c0f25ecf4d0afc3a190287f36ebf5dc2e49e3449a6dc450d07db3b24f72264a84fcf3bb269f2883d3707131f9d350ed28e4585e3ce003453734f40f6fbed89a2ed20029a126f82c731bd9cf020cc8c515bd370ec5dade80f58a14b84679d2c18c7062d271d287977473970b57a266944eabcef616b6eef80371feb1a6d64909e773c271de93c2e0088beb652fb398c8e96dfbbb43423089c883a90c303b3c1b3a14e0f04c1286c88b434a9af6a35829147e747d00bf6b6c1bbbbd56a02a80b5ac3b43fe03f1989f703314dd676c2e6bfd13fe30e6760ebaf81bb8652bbc556c5fa62fbfe51bc2d0f7f3fbd583ef78ae101752093376fe09d09855f32fec5bda3aff0e46d1ef7d01abf30f0283f6ea7fe8bff1ae3f6844b989f5fdc1fd5babef99a772913b5fbdbd89e457074fc0886f064bcd6784208b319a569f6395a3efc33949bace7485337aaffb0407bfce203789d2def1777468c0146cf965c1406b032ceddf94b8787ffda16c0507e7a9cdcde43b618f9691db872d6c206411d2a08c6e3901b3c06f323634b9464191caed597bea6f036e7f78607c9d74ad370d9af7a14dad124ab551a71960b275c4a0c59f6302bfcff25d3a13affe799201c11faab26060b840cba4bc3782e685cf7ac43a86490b60629ff12976378160a20e5fa2e9f7779f9fb8dc9abffba1e3129dcdddd1b9cfcf0e21cbedfa0f1c024fe43c9b5cdad49bff7e2970c5a54ed05aa984906c77c61c868c6a11f567b3ee0ad452fdf550d919debb8229ff486bb84efa6c25d15aacf41f0ce6a355aadd2bbbbd1dddd94b5881c5a31c4a9b8178829fcd4466a77e9333c77c69358afb7091fc70598cae11e81509f0dc845342a6cc9f710a428517222d9691cda29f1cd8dfe35ca5c43fcc33531349b4e26b4d9a90ba784b1e05ae20ddcb4cf088134a2e81945c26d77db0300b7c09b094215bd851086c9ed6432e9fe24b764da46f173f46d38af10ed74b5ea0646dc4d19700a709bdcd6aab142113ebd3fa51fb93fc7c777f675fad0fb80d61c9a1b7ca850dbff903e409487f4a1ff09fe7ba242b527dc236add277842e2d738dc2378c8e14c8b4b7c62723f766f5e923cb2f1da12e892dedfdb60b194ce87bcfe39143f8a0f4e9f444213df707a22b1837bc7c09a9824833099f2261bdddda5fcf78815825b58ad468f481a7166a16981dd1393bbc16f65017d503ec96c40f3cd6c1f7afc69f60f697f8798fb61f13531f7697f879841007d4dc280ec3f642a4ec09fff4306fbddc27ebfd9fe172236c16278844be197c8e89711fc32fa65b445fb3d72785c703ff92b2ab4632318314b08296463038e8d4717ee8eafd4188edcdac56db8670f1f39f0e595e85f2b2af482e12bbfe1f283585bd0af1ab4cbf8616ef8d8317e05cc35b1f1864e9896bf361db7df01f1319e48bbef4719001126e0cb0f63abc1d7ae69320935cd05b151396df4c479a10b618b35c69bc727c7ddf744f1130e0be1fa6f6b00085aa0105ecf8af0b4b9ff1a8ed08b70097e68f84be0e496bab9b42e6b2eaac7adde700b1fa2e8bfe3929abbb4e4f7ed92a9672f7cd57b881a6ebb6be19f0861dd42cedf4f4351a8f50afdd89fe4367e820270bb647596f7ffba85e52d2c93dbbaf5e9bf20d5c96db57457ea1b58560ebe81307caef3135a4768e49d74049409fe9f22ffaf09b18701e5c606ec19edad9c35de580acc0b3a34fefaeae5e3cf27ede0164c0004cc248143a12f1970b6831c440be9c2fb1fb126909724678850198b7dbfb16e1b2e86cdc71f82d63e202aada8172e1bceda4afe270000ffff7bc45e969e310000", "a42f6cfa87833f9ac66f4293e399925f": "1f8b08000000000000ffbc55df8fdb44107e8eff8a21aa68827cce51fa80824ee27aeda985b68426074808557bf6d8d9d6d95d66d7cd1ddbfddfd1ae9d1fb6e290a2c2bd5c323b33df3733df4caccd30e70261c8147f5ba98c194c0a9998952a87ce459309dc04a3b5c9dc50959ad76c85ce355660a0b9284a04c254520639c915589b2cd86d898dabf19f810b304bf46f4f9961b74c6f9eb3e6abc7fa7e5ead568ceeb7e9c526b10f0f79dac943d053d4297165b814ff15af052b34747a101e2ed314950178a7a5088619c9ac4ab1b1584b4c14080f728e6506d30ba829bc10b94cae6486d7deae9db31678deb82533e2be0b3fe2fd2515355848cd88ad00acedf503e74031b3dcf399fffcf215538a8b2299af5951202dee55703454210c779e57b2ac56e2151a9634b986d6a2c83cb9f02f6a9168ebe15666f7deb89219963396be6745d3c8a4eb5ae33683ea3ed6f31a36624853d41a1e9d9f8395b7ef3035ee348c107ecd785911c2e34e3853bc1dfc7cb1983d2392d4097bfc29616f64659060d262021fc1c897728de49bf8b9a460c1dab37e0d9c3907ae3d39f85d55e60fcf13d3a58487d62ee40ff39f5eef537821b46122453877ee217c84a5310a66370b2f91078946fa80344f97e8294c27939df1b9d4c623f11c04c2c63a9364e0db73e7a63b4f6fdb72fa5f3ab515f7357b8f7ec1c1755a3304f8edec52f1b31b8d34ad34d2d78fbe89f24aa4872fdf681d3a93bc41ada4d0f82b71831403c1578dfdcf0ab58941e9e048411749581b3d061b0d5273e76be2821bce4afe175e4961f0ce8c687cfabd888ed70ece45d1c0dafedec480441ee08053009931d27e1a23a5e3a081de54c37134e079c8f7c505085efa2a0784a622111664949abb18d63150401d6f5fa3812fa475633ceb1e5d78b65f9eb4ffd66d194d2f8090655eed238aa137f9f8bb130bb036c9986cc33f237ac2b266c6adea2200d831e9454f9e602e09e7ec038eba4c20fcfd6b360050d3e8079f112a46381a9f4cf7175672bf1ca39e7b5cafcee7af641bde94b537e50da56601ebd414a4bbffb33f8c4fa7fc29323ea2db18ded6db7671b0c8c367c663f99c27de03f8c77b7068d177d7a0b385fdc51cdaf523530d7d02803d9fba5b6b7f37c3566ebc8f406eaf4414fd1d0000ffff948e6509b20a0000", "abfb948144ce2666cf818384c00cc754": "1f8b08000000000000ff84924d6bdb401086effb2b5ee24b0c967a2f6d21d8b49796b46e028510a2b176b45ebada11da954d1af2dfcb4af2471c83afa3679e7967b40fa5d435fbf8f8119fbee0fa6e6d036c00c1b0e796226b54d6311ac71418ac6d4490ae2d19d623ff10b96e1c450e5375a2ba710eb5685bd992a2158fad750e2b8693106778960e6bda3056cc1e5b6a3deb778ea9529309e6cbfb051637b7f8daf932a9822a0cfb6230ee824293a0da11b015e29a5164d90ec8344991966b2804d688825e93e36ecd2845334af22961d98528b5fdc71a5b1bd73b9126f9dc3b2a4726b5078efd374f35438681294543e55f329c2b353fb21e2e6a3de2569266235d1b66f8fdebfb1fac9ea1b9a2ce4590d7f876bbfc7118df8f4ccb485b174aa97de4d16ac5cf4eee71b84525adcaf0b0e4d85ade586fd07229ad0e83bf2163bd79bc9e8c00673fc9b0ce9603343dea651042c365faaba3e4b86fa8f40df39653083a5043e598b96ff40933548e99053b7ecb0c953d935ec83e5d1f1c637055148511f5f2b27ba4b8d2244fe9844f862339971bc963ddb82be4afaf097f6b1b465cd09c778ceb5f3690d6e70de3712e1bba1e3c2f19af7759a27bf0bde47f000000ffffa7724c4920040000", "ac60daf3c3958d476b98234112b3896c": "1f8b08000000000000ffcc5a6d6fdb3812fe2cfd8a59a1b7900a454eda5c5078d7c0a54dd3e6b0dbcde565ef8020c83212e57023933a8a4a9afafcdf0f33a464d95652a72fc1e64b2c726638cf70663443aa64e9351b73984e13568a43fbf4814df86ce6fb62522a6d20f4bd2055d2f08f26f0bd80cb5465428e077f564ae2403ea171a10642d54614f820b9195c1953e2efcae854c91bfc69c484e3ff5a562cc75f17108c85b9aa2f93544d0615334a8bc15825752db2c0f7bd603a4d32a6f6ff75f861360becf34465bc68467caf2b602ce4c6584991e2af6071eecfba105c56e9d544645639ad6ac375e047be7fc334843e00c0058c00d54c8e79aa64068301383b744773a5a196d752dd4aa82bb4a090c04ae17ba9aeb3b7322b9590a682092bcf2aa3851c9f3f7fa3eb6cf7f000971b0cc03d41c6ab548b4b5e81e6ffad79652a60374c14ecb2e0b48cc15f15ca37571c3266d825abb86fee4ade0aa98cae530353dfc3bd83ce9f5dbc33309d42f246c95c8c937f1efff6e1848d21906cc20398cd7cef8de6ccf0d3a35f1ec39e12d345ad0b2be4881b2df80dff4d5a496b09d18ee942c91e51bf327987b21e276ac2e4dd5cd669993d1e5b4d4c73217bbce08f169211d35cc83e37e9d5dede2f8d98b584e4c8749165c55cce09bac681cc95637bde44c742202773b23eb9e45f1742e68a84ce7c72cf4336e6d911afeac2a067daffe88e251bf30ade71b35b14cd4462bd718167ee9238dc2213d2ec6c3f8012c55b70c8762c3ef1b5d92e2af1c9f1ee31c3ba4b729db3944f67fdbbc30c2336243e518615473c553ac398330f6e8941e20b6da91beb0d06f0fee4e4f0add64a03ffc82665e1a2753e3c37ce1b9571a724fefb0393ea30585a6c5ff022a3c80e529511c6a0113d0cb63737833f7cef575e512272cef45949134b1f2cc8aa0c337505972c6bd251f047e31256cc11254d48e9a1d61c309326ef99cc0aaec1a6543faf65ba401f468b7453dfb3a4301cc13c17271ff86d18f9de74aa991c737846ce89fac6f00ca193170f479098c6a7abd9ccea329db614c931d9d7babf53c02e10f9762fb9ccd04fdcaaefde9e84c120cb8ac190e9f1c15e10a383ef6545b44ad2ceb5491e89b8a9b56cd02fd8eb9d902b261b0bd963a996d2e98a64c9010d55115aec4bacf24ec8350de3ad6195374ade70fdbeddaf13f54ec8d0d9aadf580fb1cc4dd8dab063bc5536b8d5acecba8b7527308a6ce59c6bbf96696bd79eb5f35509d1323f39a8dd549414a6f01c29ded8328876c3c39aa1649a4daaaec0431af13defa2e01237a5e0324cdd70e47b9ec881a64623d824399e133202290adff366c08b8a2f4d85e1f3b32df8f967d8da3c5f5e2d0a6d3d951c2aca75e18fcd7a679be751149d0d71c17314edfb9e978769f26f2d0cd731a4c9910df3d841897ccfbe03ac05851446b0427ce20e7aa8e13945b2e38b204ccd4770f5e18281440eeef1a095a2e1078249e89071d44313a23776ac60e974233c241d9bed49cd47d297d4fd9d1502dfd74eb93ed562584210db0acbe5cd18586a8492309d3eeb7b97eed26c049cf2b845e904b9c5d50246a7e532096a1683766b378b12b016197ac3ccb7af8e657ef251eb99df17a22d8d5710f6e9e3545dddcfb9b2eb398f5db3c777fa45fbadb76aceb203b9e2a3ceb75bf837b69c8820a4ffb1454a3e5b62c4ea645fe9c9efaca879489c116d7389211b04dd6dbd896dcc7676cd753b1880154765ca18b63663d8d98e703749cf5b8c3e7c1df7efde2d38edab52c98a37b17ad3ad64485bac5c62b8409df16d9ffcca7475c58af026f2bddbe43d6719be7993636ec280a44bb3717257f220868095652152865b4d9ddc4f905ea1ca66549b7ce355d02782a5577c03056985a93d906a23c5314b4c7a86a8d312d223755bede6394f0dcfc27bc0e90e4db33d0f20ec92ff65c0a2fbd1aeaef8dfd2d6b5c9e3b2cec9fb109f6d9e9323ceb2dda20875f25a6577d6f3906235a770ad173c8f0c742a27ce4424fb269a27474b4605e8a3fc6e050daa330f1957320e479bbe57dd0a935e11c1d4f75256d1c942c6d4628679abf50765f6552db3a1ef35126c21981cd3d36b96b9f51e96732a31b39d28e71868feaf16492ddf3e1305ff6af50e64c5b5f936b26cebf96d64bd6699ad10d61194f19cd585598774e67bb6a66f7b1d74586c7286b6d14586d8f79a6e6588be92589f8c62ebcec75cda30baa53724c7177fc6f147d454871ff8ed627f450ede8c927777ebb5d82d8c51b8ecbe5fa92e1627d459858eb855d3455d8979e65448f32a2c7b0ac518aef99d7b334510d648d87d2189ecd890866595bcbec30d0caf39a585accd1c2baf1c62b2af9d570f65105a2d14591437c9e49e99d91296ad9d75c16ced7c4b345b3b9f83b3b5732f9eeed432a0972fd605f4f2c5b704f4f2c5e700bd7c712fa0eed432a09ded75012d16405f0b08cb9c8701ed6cdf0ba83bd5054465d4e7d134d1fd245036b6562008e2efd77fade87fd2e0ef07704fd01fac1bf34f1cf2fd20ee8bf4837503fd89e3bc1fc57de17db06e747ff3e0fefba323225b55ff98d45b47ffa65f5b0340bb1ee96a3be74e423c3dd87b8205eddf60e00e084154c0a8efa56edb281873e33a71217345c7e9ac1da0eb9de934d973373cb6589b5ff80c06f08fe37a3261fa0e17f86241707907749e47124fd8b882f68280860ef63af37b743d551282ef066b374d7969803a181a38d42aab53de1da10ede2a0625335774626e74cd211059e0ac93a6bcaae0c5e6264cd5e59f3c35b3be9bcda4b9894326aca76bcd61fb734c6d89b8c4b6bd261b049db62786ec12ecc501d94b642095811ce760c3b56a1534e4549fd25a732a72520bdc1d30d359ed944c3483b33137e7388b4e0fc174fa2ca9b8bee1fa38bde2a8d97030980fbe579599cda6539183e4d08c1e2a6de0d5e66c369c53e21852d25931adf8f1ee5300ffd9d82dc5c669c5f5b0aeb8de7af1d2c69ff5997bbbfe95839a9e20a540c4921e9be4d5f3c8c8f73deb160b611ad8336b9c75196b38ea3b1fa41e23b067d5fdd767cd6d5df4537fe26b7b8e189a8e4547ed6cd3a2e356c7a0ae518d853be23352f49cd4fc415d3f24389f18db7ce4615053e38be1970b99d9701bc2dfaa20b64112c1b20e0ba74e562469e5da95366fcd6faf7b239d41212a032a872c2b80b7c40fdd5aaf9fd6aaef21bf27c9ade6b5a787bd46dafba294d649074f97063e97025af37eef5cf01788f6d5485b88f8f9892142082da685cf46463061d73cecf97a0411de300d665242fb4589bb9feeb91d6c2f0497ae07e9961c658ce04727052122d790bca415329b05b1efcd3f0c19423068e697ae14e17f60d42fea966bc7b4f821c89770baef3e1ec3da7ee6f118a6f6b38ec730753ee340367afbae580efdc14ccaf94718317d6274cfddcb3b6e5ac270791fa22537395b263887113a863fbfcbc5ccfeff000000ffffce582ff2e4250000", @@ -60,7 +60,7 @@ var _ = func() error { g.DefaultResolver = hgr func() { - b := packr.New("gen", "../template") + b := packr.New("gen", "./template") b.SetResolver("GEN_README.md.tmpl", packr.Pointer{ForwardBox: gk, ForwardPath: "a2d5586edb9ff79f53fe610c4e91de3d"}) b.SetResolver("Makefile.tmpl", packr.Pointer{ForwardBox: gk, ForwardPath: "3ceb52e1f1f3624e5a1a43ca87b692d5"}) b.SetResolver("README.md.tmpl", packr.Pointer{ForwardBox: gk, ForwardPath: "b90aee21f6dfc810704f9344330644a8"}) diff --git a/readme/main.go b/readme/main.go index bb06c65..8dcf733 100644 --- a/readme/main.go +++ b/readme/main.go @@ -32,7 +32,7 @@ func init() { goopt.Description = func() string { return "ORM and RESTful meta data viewer for SQl databases" } - goopt.Version = "v0.9.25 (07/26/2020)" + goopt.Version = "v0.9.26 (07/31/2020)" goopt.Summary = `dbmeta [-v] --sqltype=mysql --connstr "user:password@/dbname" --database sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ] @@ -134,9 +134,25 @@ func genreadme(conf *dbmeta.Config, templateName, outputFile string, ctx map[str {{- end }} ` ctx["AdvancesSample"] = sample + + releaseHistory := loadFile("release.history") + ctx["ReleaseHistory"] = releaseHistory conf.WriteTemplate(template, ctx, outputFile, false) } +func loadFile(src string) string { + // Read entire file content, giving us little control but + // making it very simple. No need to close the file. + content, err := ioutil.ReadFile(src) + if err != nil { + return fmt.Sprintf("error loading %s error: %v", src, err) + } + + // Convert []byte to string and print to screen + text := string(content) + return text +} + func initialize(conf *dbmeta.Config) { outDir := "." module := "github.com/alexj212/test" diff --git a/release.history b/release.history new file mode 100644 index 0000000..3ab586f --- /dev/null +++ b/release.history @@ -0,0 +1,93 @@ +- v0.9.26 (07/31/2020) + - Release scripting + - Added custom script functions to copy, mkdir, touch, pwd + - Fixed custom script exec example +- v0.9.25 (07/26/2020) + - Adhere json-fmt flag for all JSON response so when camel or lower_camel is specified, fields name in GetAll variant and DDL info will also have the same name format + - Fix: Build information embedded through linker in Makefile is not consistent with the variable defined in main file. + - Added --scheme and --listen options. This allows compiled binary to be used behind reverse proxy. + - In addition, template for generating URL was fixed, i.e. when PORT is 80, then PORT is omitted from URL segment. +- v0.9.24 (07/13/2020) + - Fixed array bounds issue parsing mysql db meta +- v0.9.23 (07/10/2020) + - Added postgres types: bigserial, serial, smallserial, bigserial, float4 to mapping.json +- v0.9.22 (07/08/2020) + - Modified gogo.proto check to use GOPATH not hardcoded. + - Updated gen to error exit on first error encountered + - Added color output for error + - Added --no-color option for non colorized output +- v0.9.21 (07/07/2020) + - Repacking templates, update version number in info. +- v0.9.20 (07/07/2020) + - Fixed render error in router.go.tmpl + - upgraded project to use go.mod 1.14 +- v0.9.19 (07/07/2020) + - Added --windows flag to write files with CRLF windows line endings, otherwise they are all unix based LF line endings +- v0.9.18 (06/30/2020) + - Fixed naming in templates away from hard coded model package. +- v0.9.17 (06/30/2020) + - Refactored template loading, to better report error in template + - Added option to run gofmt on output directory +- v0.9.16 (06/29/2020) + - Fixes to router.go.tmpl from calvinchengx + - Added postgres db support for inet and timestamptz +- v0.9.15 (06/23/2020) + - Code cleanup using gofmt name suggestions. + - Template updates for generated code cleanup using gofmt name suggestions. +- v0.9.14 (06/23/2020) + - Added model comment on field line if available from database. + - Added exposing TableInfo via api call. +- v0.9.13 (06/22/2020) + - fixed closing of connections via defer + - bug fixes in sqlx generated code +- v0.9.12 (06/14/2020) + - SQLX changed MustExec to Exec and checking/returning error + - Updated field renaming if duplicated, need more elegant renaming solution. + - Added exclude to test.sh +- v0.9.11 (06/13/2020) + - Added ability to pass field, model and file naming format + - updated test scripts + - Fixed sqlx sql query placeholders +- v0.9.10 (06/11/2020) + - Bug fix with retrieving varchar length from mysql + - Added support for mysql unsigned decimal - maps to float +- v0.9.9 (06/11/2020) + - Fixed issue with mysql and table named `order` + - Fixed internals in GetAll generation in gorm and sqlx. +- v0.9.8 (06/10/2020) + - Added ability to set file naming convention for models, dao, apis and grpc `--file_naming={{.}}` + - Added ability to set struct naming convention `--model_naming={{.}}` + - Fixed bug with Makefile generation removing quoted conn string in `make regen` +- v0.9.7 (06/09/2020) + - Added grpc server generation - WIP (looking for code improvements) + - Added ability to exclude tables + - Added support for unsigned from mysql ddl. +- v0.9.6 (06/08/2020) + - Updated SQLX codegen + - Updated templates to split code gen functions into seperate files + - Added code_dao_gorm, code_dao_sqlx to be generated from templates +- v0.9.5 (05/16/2020) + - Added SQLX codegen by default, split dao templates. + - Renamed templates +- v0.9.4 (05/15/2020) + - Documentation updates, samples etc. +- v0.9.3 (05/14/2020) + - Template bug fixes, when using custom api, dao and model package. + - Set primary key if not set to the first column + - Skip code gen if primary key column is not int or string + - validated codegen for mysql, mssql, postgres and sqlite3 + - Fixed file naming if table ends with _test.go renames to _tst.go + - Fix for duplicate field names in struct due to renaming + - Added Notes for columns and tables for situations where a primary key is set since not defined in db + - Fixed issue when model contained field that had were named the same as funcs within model. +- v0.9.2 (05/12/2020) + - Code cleanup gofmt, etc. +- v0.9.1 (05/12/2020) +- v0.9 (05/12/2020) + - updated db meta data loading fetching default values + - added default value to GORM tags + - Added protobuf .proto generation + - Added test app to display meta data + - Cleanup DDL generation + - Added support for varchar2, datetime2, float8, USER_DEFINED +- v0.5 diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..72d7b72 --- /dev/null +++ b/release.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +VERSION=$(head -n 1 release.history) +VERSION=${VERSION#"- "} + +VERSION_CODE="goopt.Version = \"${VERSION}\"" +echo "VERSION : ${VERSION}" +echo "VERSION_CODE: ${VERSION_CODE}" + + +sed -i "s~goopt\.Version = \".*\"~goopt.Version = \"${VERSION}\"~g" readme/main.go +sed -i "s~goopt\.Version = \".*\"~goopt.Version = \"${VERSION}\"~g" main.go +sed -i "s~goopt\.Version = \".*\"~goopt.Version = \"${VERSION}\"~g" _test/dbmeta/main.go + +ack "goopt.Version = \".*\"" diff --git a/template/GEN_README.md.tmpl b/template/GEN_README.md.tmpl index 29459dd..a69478a 100644 --- a/template/GEN_README.md.tmpl +++ b/template/GEN_README.md.tmpl @@ -178,18 +178,73 @@ The `gen` tool provides functionality to layout your own project format. Users h via the command `gen --save ./mytemplates`. This will save the embedded templates for local editing. Then you would specify the `--templateDir=` option when generating a project. * Passing `--exec=../sample.gen` on the command line will load the `sample.gen` script and execute it. The script has access to the table information and other info passed to `gen`. This allows developers to customize the generation of code. You could loop through the list of tables and invoke -`GenerateTableFile` or `GenerateFile`. +`GenerateTableFile` or `GenerateFile`. You can also perform operations such as mkdir, copy, touch, pwd. -You can also populate the context used by templates with extra data by passing the `--contect=` option. The json file will be used to populate the context used when parsing templates. +### Example - generate files from a template looping thru a map of tables. +Loop thru map of tables, key is the table name and value is ModelInfo. Creating a file using the table ModelInfo. +`tableInfos := map[string]*ModelInfo` +`GenerateTableFile(tableInfos map[string]*ModelInfo, tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool)` + +``` +{{` +{{ range $tableName , $table := .tableInfos }} + {{$i := inc }} + {{$name := toUpper $table.TableName -}} + {{$filename := printf "My%s.go" $name -}} + + {{ GenerateTableFile $.tableInfos $table.TableName "custom.go.tmpl" "test" $filename true}}{{- end }} +`}} +``` + +### Example - generate file from a template. +`GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool, overwrite bool)` +``` +{{` +{{ GenerateFile "custom.md.tmpl" "test" "custom.md" false false }} +`}} +``` + +### Example - make a directory. +``` +{{` +{{ mkdir "test/alex/test/mkdir" }} +`}} +``` + +### Example - touch a file. +``` +{{` +{{ touch "test/alex/test/mkdir/alex.txt" }} +`}} +``` + +### Example - display working directory. +``` +{{` +{{ pwd }} +`}} +``` + +### Example - copy a file or directory from source to a target directory. +``` +{{` +{{ copy "../_test" "test" }} +`}} +``` + + +You can also populate the context used by templates with extra data by passing the `--context=` option. The json file will be used to populate the context used when parsing templates. + +### File Generation ```gotemplate // Loop through tables and print out table name and various forms of the table name {{.AdvancesSample}} -// GenerateTableFile(tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool) -// GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool) string +// GenerateTableFile(tableInfos map[string]*ModelInfo, tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool) +// GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool, overwrite bool) The following info is available within use of the exec template. @@ -199,7 +254,7 @@ The following info is available within use of the exec template. ``` -## Struct naming +### Struct naming The ability exists to set a template that will be used for generating a struct name. By passing the flag `--model_naming={{"{{.}}"}}` The struct will be named the table name. Various functions can be used in the template to modify the name such as @@ -249,96 +304,7 @@ Table Name: registration_source |ms sql |y | y | y | y | y | y| n ## Version History -- v0.9.25 (07/26/2020) - - Adhere json-fmt flag for all JSON response so when camel or lower_camel is specified, fields name in GetAll variant and DDL info will also have the same name format - - Fix: Build information embedded through linker in Makefile is not consistent with the variable defined in main file. - - Added --scheme and --listen options. This allows compiled binary to be used behind reverse proxy. - - In addition, template for generating URL was fixed, i.e. when PORT is 80, then PORT is omitted from URL segment. -- v0.9.24 (07/13/2020) - - Fixed array bounds issue parsing mysql db meta -- v0.9.23 (07/10/2020) - - Added postgres types: bigserial, serial, smallserial, bigserial, float4 to mapping.json -- v0.9.22 (07/08/2020) - - Modified gogo.proto check to use GOPATH not hardcoded. - - Updated gen to error exit on first error encountered - - Added color output for error - - Added --no-color option for non colorized output -- v0.9.21 (07/07/2020) - - Repacking templates, update version number in info. -- v0.9.20 (07/07/2020) - - Fixed render error in router.go.tmpl - - upgraded project to use go.mod 1.14 -- v0.9.19 (07/07/2020) - - Added --windows flag to write files with CRLF windows line endings, otherwise they are all unix based LF line endings -- v0.9.18 (06/30/2020) - - Fixed naming in templates away from hard coded model package. -- v0.9.17 (06/30/2020) - - Refactored template loading, to better report error in template - - Added option to run gofmt on output directory -- v0.9.16 (06/29/2020) - - Fixes to router.go.tmpl from calvinchengx - - Added postgres db support for inet and timestamptz -- v0.9.15 (06/23/2020) - - Code cleanup using gofmt name suggestions. - - Template updates for generated code cleanup using gofmt name suggestions. -- v0.9.14 (06/23/2020) - - Added model comment on field line if available from database. - - Added exposing TableInfo via api call. -- v0.9.13 (06/22/2020) - - fixed closing of connections via defer - - bug fixes in sqlx generated code -- v0.9.12 (06/14/2020) - - SQLX changed MustExec to Exec and checking/returning error - - Updated field renaming if duplicated, need more elegant renaming solution. - - Added exclude to test.sh -- v0.9.11 (06/13/2020) - - Added ability to pass field, model and file naming format - - updated test scripts - - Fixed sqlx sql query placeholders -- v0.9.10 (06/11/2020) - - Bug fix with retrieving varchar length from mysql - - Added support for mysql unsigned decimal - maps to float -- v0.9.9 (06/11/2020) - - Fixed issue with mysql and table named `order` - - Fixed internals in GetAll generation in gorm and sqlx. -- v0.9.8 (06/10/2020) - - Added ability to set file naming convention for models, dao, apis and grpc `--file_naming={{"{{.}}"}}` - - Added ability to set struct naming convention `--model_naming={{"{{.}}"}}` - - Fixed bug with Makefile generation removing quoted conn string in `make regen` -- v0.9.7 (06/09/2020) - - Added grpc server generation - WIP (looking for code improvements) - - Added ability to exclude tables - - Added support for unsigned from mysql ddl. -- v0.9.6 (06/08/2020) - - Updated SQLX codegen - - Updated templates to split code gen functions into seperate files - - Added code_dao_gorm, code_dao_sqlx to be generated from templates -- v0.9.5 (05/16/2020) - - Added SQLX codegen by default, split dao templates. - - Renamed templates -- v0.9.4 (05/15/2020) - - Documentation updates, samples etc. -- v0.9.3 (05/14/2020) - - Template bug fixes, when using custom api, dao and model package. - - Set primary key if not set to the first column - - Skip code gen if primary key column is not int or string - - validated codegen for mysql, mssql, postgres and sqlite3 - - Fixed file naming if table ends with _test.go renames to _tst.go - - Fix for duplicate field names in struct due to renaming - - Added Notes for columns and tables for situations where a primary key is set since not defined in db - - Fixed issue when model contained field that had were named the same as funcs within model. - -- v0.9.2 (05/12/2020) - - Code cleanup gofmt, etc. -- v0.9.1 (05/12/2020) -- v0.9 (05/12/2020) - - updated db meta data loading fetching default values - - added default value to GORM tags - - Added protobuf .proto generation - - Added test app to display meta data - - Cleanup DDL generation - - Added support for varchar2, datetime2, float8, USER_DEFINED -- v0.5 +{{.ReleaseHistory}} ## Contributors