odin-jsonschema

Implementation of JSON schema for Odin
Log | Files | Refs | LICENSE

main.odin (4077B)


      1 // jschema generates Odin type declarations from a JSON Schema document.
      2 //
      3 // Usage:
      4 //   jschema schema.json -o:types.odin      generate from a file
      5 //   cat schema.json | jschema              generate from stdin to stdout
      6 //
      7 // Flags:
      8 //   -o:<path>     output file (default: stdout)
      9 //   -pkg:<name>   package name of the generated file (default: schema)
     10 //   -root:<name>  name of the root declaration (default: Root)
     11 package main
     12 
     13 import "core:fmt"
     14 import "core:mem"
     15 import "core:os"
     16 import "core:path/filepath"
     17 import "core:strings"
     18 
     19 import "../../pkg/jschema"
     20 
     21 main :: proc() {
     22 	when ODIN_DEBUG {
     23 		track: mem.Tracking_Allocator
     24 		mem.tracking_allocator_init(&track, context.allocator)
     25 		context.allocator = mem.tracking_allocator(&track)
     26 		defer {
     27 			for _, entry in track.allocation_map {
     28 				fmt.eprintfln("jschema: leaked %v bytes at %v", entry.size, entry.location)
     29 			}
     30 			for entry in track.bad_free_array {
     31 				fmt.eprintfln("jschema: bad free at %v", entry.location)
     32 			}
     33 			mem.tracking_allocator_destroy(&track)
     34 		}
     35 	}
     36 
     37 	os.exit(run())
     38 }
     39 
     40 run :: proc() -> int {
     41 	input := ""
     42 	output := ""
     43 	opts := jschema.Options{}
     44 
     45 	for arg in os.args[1:] {
     46 		switch {
     47 		case arg == "-h" || arg == "--help":
     48 			fmt.println(USAGE)
     49 			return 0
     50 		case strings.has_prefix(arg, "-o:"):
     51 			output = arg[len("-o:"):]
     52 		case strings.has_prefix(arg, "-pkg:"):
     53 			opts.package_name = arg[len("-pkg:"):]
     54 		case strings.has_prefix(arg, "-openapi"):
     55 			rest := arg[len("-openapi"):]
     56 			switch {
     57 			case rest == "":
     58 				opts.openapi = true
     59 			case rest == ":true":
     60 				opts.openapi = true
     61 			case rest == ":false":
     62 				opts.openapi = false
     63 			case:
     64 				fmt.eprintfln("jschema: invalid value for -openapi %q\n%s", arg, USAGE)
     65 				return 2
     66 			}
     67 		case strings.has_prefix(arg, "-root:"):
     68 			opts.root_name = arg[len("-root:"):]
     69 		case strings.has_prefix(arg, "-"):
     70 			fmt.eprintfln("jschema: unknown flag %q\n%s", arg, USAGE)
     71 			return 2
     72 		case input != "":
     73 			fmt.eprintfln("jschema: multiple schema paths given\n%s", USAGE)
     74 			return 2
     75 		case:
     76 			input = arg
     77 		}
     78 	}
     79 
     80 	if input != "" && output != "" {
     81 		if err := jschema.generate(input, output, opts); err != nil {
     82 			report(err)
     83 			return 1
     84 		}
     85 		return 0
     86 	}
     87 
     88 	data: []u8
     89 	base_dir := ""
     90 	if input == "" {
     91 		stdin_data, rerr := os.read_entire_file_from_file(os.stdin, context.allocator)
     92 		if rerr != nil {
     93 			fmt.eprintfln("jschema: cannot read stdin: %v", rerr)
     94 			return 1
     95 		}
     96 		data = stdin_data
     97 	} else {
     98 		file_data, rerr := os.read_entire_file_from_path(input, context.allocator)
     99 		if rerr != nil {
    100 			fmt.eprintfln("jschema: cannot read %s: %v", input, rerr)
    101 			return 1
    102 		}
    103 		data = file_data
    104 		base_dir = filepath.dir(input) // substring of input; not freed
    105 	}
    106 	defer delete(data)
    107 
    108 	source, err := jschema.generate_source(data, opts, base_dir)
    109 	if err != nil {
    110 		report(err)
    111 		return 1
    112 	}
    113 	defer delete(source)
    114 
    115 	if output == "" {
    116 		os.write(os.stdout, transmute([]u8)source)
    117 		return 0
    118 	}
    119 	if werr := os.write_entire_file(output, source); werr != nil {
    120 		fmt.eprintfln("jschema: cannot write %s: %v", output, werr)
    121 		return 1
    122 	}
    123 	return 0
    124 }
    125 
    126 report :: proc(err: jschema.Error) {
    127 	switch specific in err {
    128 	case jschema.IO_Error:
    129 		fmt.eprintfln("jschema: cannot access %s: %v", specific.path, specific.error)
    130 	case jschema.Parse_Error:
    131 		fmt.eprintfln(
    132 			"jschema: %s:%d:%d: %s",
    133 			specific.path,
    134 			specific.pos.line,
    135 			specific.pos.column,
    136 			specific.message,
    137 		)
    138 	case jschema.Resolve_Error:
    139 		fmt.eprintfln("jschema: %s: cannot resolve $ref %q", specific.path, specific.ref)
    140 	case mem.Allocator_Error:
    141 		fmt.eprintfln("jschema: out of memory: %v", specific)
    142 	}
    143 }
    144 
    145 USAGE :: `usage: jschema [schema.json] [-o:output.odin] [-pkg:name] [-root:name] [-openapi[:true|:false]]
    146 
    147 Generates Odin type declarations from a JSON Schema (draft-07 or 2020-12).
    148 Reads from stdin when no schema path is given; writes to stdout when -o is
    149 not given.
    150 
    151 OpenAPI idioms (if/then/else lowering, Or_Reference($T) generic union) are
    152 auto-detected from a top-level "openapi" property. -openapi forces the
    153 mode on; -openapi:false forces it off.`