init
This commit is contained in:
		
							
								
								
									
										8
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | [*] | ||||||
|  | charset = utf-8 | ||||||
|  | end_of_line = lf | ||||||
|  | insert_final_newline = true | ||||||
|  | trim_trailing_whitespace = true | ||||||
|  |  | ||||||
|  | [*.v] | ||||||
|  | indent_style = tab | ||||||
							
								
								
									
										8
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | * text=auto eol=lf | ||||||
|  | *.bat eol=crlf | ||||||
|  |  | ||||||
|  | *.v linguist-language=V | ||||||
|  | *.vv linguist-language=V | ||||||
|  | *.vsh linguist-language=V | ||||||
|  | v.mod linguist-language=V | ||||||
|  | .vdocignore linguist-language=ignore | ||||||
							
								
								
									
										29
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | # Binaries for programs and plugins | ||||||
|  | main | ||||||
|  | embedfs | ||||||
|  | /mkembedfs | ||||||
|  | *.exe | ||||||
|  | *.exe~ | ||||||
|  | *.so | ||||||
|  | *.dylib | ||||||
|  | *.dll | ||||||
|  |  | ||||||
|  | # Ignore binary output folders | ||||||
|  | bin/ | ||||||
|  |  | ||||||
|  | # Ignore common editor/system specific metadata | ||||||
|  | .DS_Store | ||||||
|  | .idea/ | ||||||
|  | .vscode/ | ||||||
|  | *.iml | ||||||
|  |  | ||||||
|  | # ENV | ||||||
|  | .env | ||||||
|  |  | ||||||
|  | # vweb and database | ||||||
|  | *.db | ||||||
|  | *.js | ||||||
|  |  | ||||||
|  | # Others | ||||||
|  | doc | ||||||
|  | *_generated* | ||||||
							
								
								
									
										20
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | SRC_DIR ?= src | ||||||
|  | DOC_DIR ?= doc | ||||||
|  | TESTS_DIR ?= . | ||||||
|  |  | ||||||
|  | all: test | ||||||
|  |  | ||||||
|  | test: | ||||||
|  | 	v test $(TESTS_DIR) | ||||||
|  |  | ||||||
|  | doc: | ||||||
|  | 	v doc -f html -m ./$(SRC_DIR) -o $(DOC_DIR) | ||||||
|  |  | ||||||
|  | serve: clean doc | ||||||
|  | 	v -e "import net.http.file; file.serve(folder: '$(DOC_DIR)')" | ||||||
|  |  | ||||||
|  | clean: | ||||||
|  | 	rm -r $(DOC_DIR) || true | ||||||
|  |  | ||||||
|  | cli: | ||||||
|  | 	v cmd/mkembedfs -o mkembedfs | ||||||
							
								
								
									
										61
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | ## Code generator for embedding directories with files into executables | ||||||
|  |  | ||||||
|  | The `$embed_file` statement in V embeds only a single file. This module makes | ||||||
|  | it easy to embed entire directories into the final executable. | ||||||
|  |  | ||||||
|  | File embedding in V is done at compile time, and unfortunately there is no way | ||||||
|  | to dynamically embed arbitrary files into an application. The `embedfs` module | ||||||
|  | is a simple code generator that creates a separate `.v` file with the code for | ||||||
|  | embedding files. That is, the embedfs call must be made before the code is | ||||||
|  | compiled. So embedfs is a build dependency. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | v install --git https://github.com/gechandesu/embedfs | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Usage | ||||||
|  |  | ||||||
|  | For example you have following file structure: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | v.mod | ||||||
|  | src/ | ||||||
|  |     main.v | ||||||
|  |     assets/ | ||||||
|  |         css/style.css | ||||||
|  |         js/app.js | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Lets embed the `assets` directory. | ||||||
|  |  | ||||||
|  | Create `embed_assets.vsh` next to your v.mod: | ||||||
|  |  | ||||||
|  | ```v | ||||||
|  | #!/usr/bin/env -S v | ||||||
|  |  | ||||||
|  | import embedfs | ||||||
|  |  | ||||||
|  | chdir('src')! | ||||||
|  | assets := embedfs.CodeGenerator{ | ||||||
|  |     path: 'assets' | ||||||
|  | } | ||||||
|  | write_file('assets_generated.v', assets.generate())! | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Run it: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | v run embed_assets.vsh | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Now you have `src/assets_generated.v`. Take a look inside it. So you can use | ||||||
|  | `embedfs` const in `src/main.v` in this way: | ||||||
|  |  | ||||||
|  | ```v | ||||||
|  | module main | ||||||
|  |  | ||||||
|  | fn main() { | ||||||
|  |     style := embedfs.files['assets/css/style.css']! | ||||||
|  |     println(style.data.to_string()) | ||||||
|  | } | ||||||
|  | ``` | ||||||
							
								
								
									
										11
									
								
								cmd/mkembedfs/help.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								cmd/mkembedfs/help.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | mkembedfs - generate V code for embed directories with files into executable. | ||||||
|  | usage: mkembedfs [flags] [<path>] | ||||||
|  | flags: | ||||||
|  |   -help                 print this help message and exit | ||||||
|  |   -chdir <string>       change working directory before codegen | ||||||
|  |   -prefix <string>      path prefix for file keys, none by default | ||||||
|  |   -ignore <string>      path globs to ignore (allowed multiple times) | ||||||
|  |   -module-name <string> generated module name, main by default | ||||||
|  |   -const-name <string>  generated constant name with data, embedfs by default | ||||||
|  |   -no-pub               do not make symbols in generated module public | ||||||
|  |   -force-mimetype       set applicetion/octet-stream mime type for unknown files | ||||||
							
								
								
									
										52
									
								
								cmd/mkembedfs/mkembedfs.v
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								cmd/mkembedfs/mkembedfs.v
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | module main | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import flag | ||||||
|  | import embedfs | ||||||
|  |  | ||||||
|  | fn main() { | ||||||
|  | 	mut path := os.getwd() | ||||||
|  | 	mut flags, no_matches := flag.to_struct[FlagConfig](os.args, skip: 1, style: .v) or { | ||||||
|  | 		eprintln('cmdline parsing error, see -help for info') | ||||||
|  | 		exit(2) | ||||||
|  | 	} | ||||||
|  | 	if no_matches.len > 1 { | ||||||
|  | 		eprintln('unrecognized arguments: ${no_matches[1..]}') | ||||||
|  | 		exit(2) | ||||||
|  | 	} else if no_matches.len == 1 { | ||||||
|  | 		path = no_matches[0] | ||||||
|  | 	} | ||||||
|  | 	if flags.help { | ||||||
|  | 		println($embed_file('help.txt').to_string().trim_space()) | ||||||
|  | 		exit(0) | ||||||
|  | 	} | ||||||
|  | 	if flags.chdir != '' { | ||||||
|  | 		os.chdir(flags.chdir) or { | ||||||
|  | 			eprintln(err) | ||||||
|  | 			exit(1) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	generator := embedfs.CodeGenerator{ | ||||||
|  | 		path:            path | ||||||
|  | 		prefix:          flags.prefix | ||||||
|  | 		ignore_patterns: flags.ignore | ||||||
|  | 		module_name:     flags.module_name | ||||||
|  | 		const_name:      flags.const_name | ||||||
|  | 		make_pub:        if flags.no_pub { false } else { true } | ||||||
|  | 		force_mimetype:  flags.force_mimetype | ||||||
|  | 	} | ||||||
|  | 	println(generator.generate()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @[xdoc: 'generate code for embed directories with files into executable.'] | ||||||
|  | @[name: 'embedfs'] | ||||||
|  | struct FlagConfig { | ||||||
|  | 	help           bool | ||||||
|  | 	chdir          string | ||||||
|  | 	prefix         string | ||||||
|  | 	ignore         []string | ||||||
|  | 	module_name    string = 'main' | ||||||
|  | 	const_name     string = 'embedfs' | ||||||
|  | 	no_pub         bool | ||||||
|  | 	force_mimetype bool | ||||||
|  | } | ||||||
							
								
								
									
										107
									
								
								src/embedfs.v
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/embedfs.v
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | module embedfs | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import strings | ||||||
|  | import net.http.mime | ||||||
|  |  | ||||||
|  | pub struct CodeGenerator { | ||||||
|  | pub: | ||||||
|  | 	// Path to file or directory to embed | ||||||
|  | 	path string | ||||||
|  | 	// Path prefix if you want to add extra prefix for file paths | ||||||
|  | 	prefix string | ||||||
|  | 	// Glob patterns to match files the must be ignored when generating the code | ||||||
|  | 	ignore_patterns []string | ||||||
|  | 	// If true set the default MIME-type for files if no MIME-type detected | ||||||
|  | 	force_mimetype bool | ||||||
|  | 	// Default MIME-type for files. See https://www.iana.org/assignments/media-types/media-types.xhtml | ||||||
|  | 	default_mimetype string = 'application/octet-stream' | ||||||
|  | 	// Name of generated module | ||||||
|  | 	module_name string = 'main' | ||||||
|  | 	// Name of constant which will contain embedded files | ||||||
|  | 	const_name string = 'embedfs' | ||||||
|  | 	// If true make symbols in generated module public | ||||||
|  | 	make_pub bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct EmbedFileSpec { | ||||||
|  | 	key      string | ||||||
|  | 	path     string | ||||||
|  | 	name     string | ||||||
|  | 	ext      string | ||||||
|  | 	mimetype string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn (g CodeGenerator) generate() string { | ||||||
|  | 	visible := if g.make_pub == true { 'pub ' } else { '' } | ||||||
|  | 	mut b := strings.new_builder(1024 * 4) | ||||||
|  | 	b.writeln('// !WARNING! This file is generated by embedfs module, do not edit it.') | ||||||
|  | 	b.writeln('') | ||||||
|  | 	b.writeln('module ${g.module_name}') | ||||||
|  | 	b.writeln('') | ||||||
|  | 	b.writeln('import v.embed_file { EmbedFileData }') | ||||||
|  | 	b.writeln('') | ||||||
|  | 	b.writeln('${visible}struct EmbedFileMetadata {') | ||||||
|  | 	b.writeln('\tkey      string') | ||||||
|  | 	b.writeln('\tname     string') | ||||||
|  | 	b.writeln('\text      string') | ||||||
|  | 	b.writeln('\tmimetype string') | ||||||
|  | 	b.writeln('}') | ||||||
|  | 	b.writeln('') | ||||||
|  | 	b.writeln('${visible}struct EmbedFile {') | ||||||
|  | 	b.writeln('\tdata EmbedFileData') | ||||||
|  | 	b.writeln('\tmeta EmbedFileMetadata') | ||||||
|  | 	b.writeln('}') | ||||||
|  | 	b.writeln('') | ||||||
|  | 	b.writeln('${visible}struct EmbedFileSystem {') | ||||||
|  | 	b.writeln('\tfiles map[string]EmbedFile') | ||||||
|  | 	b.writeln('}') | ||||||
|  | 	b.writeln('') | ||||||
|  | 	b.writeln('${visible}const ${g.const_name} = EmbedFileSystem{') | ||||||
|  | 	b.writeln('\tfiles: {') | ||||||
|  | 	for filespec in g.get_files() { | ||||||
|  | 		b.writeln("\t\t'${filespec.key}': EmbedFile{") | ||||||
|  | 		b.writeln("\t\t\tdata: \$embed_file('${filespec.path}')") | ||||||
|  | 		b.writeln('\t\t\tmeta: EmbedFileMetadata{') | ||||||
|  | 		b.writeln("\t\t\t\tkey: '${filespec.key}'") | ||||||
|  | 		b.writeln("\t\t\t\tname: '${filespec.name}'") | ||||||
|  | 		b.writeln("\t\t\t\text: '${filespec.ext}'") | ||||||
|  | 		b.writeln("\t\t\t\tmimetype: '${filespec.mimetype}'") | ||||||
|  | 		b.writeln('\t\t\t}') | ||||||
|  | 		b.writeln('\t\t},') | ||||||
|  | 	} | ||||||
|  | 	b.writeln('\t}') | ||||||
|  | 	b.writeln('}') | ||||||
|  | 	return b.str() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn (g CodeGenerator) get_files() []EmbedFileSpec { | ||||||
|  | 	mut file_list := &[]string{} | ||||||
|  | 	os.walk(g.path, fn [mut file_list] (file string) { | ||||||
|  | 		file_list << file | ||||||
|  | 	}) | ||||||
|  | 	mut files := []EmbedFileSpec{} | ||||||
|  | 	outer: for file in file_list { | ||||||
|  | 		for glob in g.ignore_patterns { | ||||||
|  | 			if file.match_glob(glob) { | ||||||
|  | 				continue outer | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_key := os.join_path_single(g.prefix, file) | ||||||
|  | 		file_path := file // the actual path used in $embed_file() statement | ||||||
|  | 		file_name := os.file_name(file_path) | ||||||
|  | 		file_ext := os.file_ext(file_name).replace_once('.', '') | ||||||
|  | 		mut mimetype := mime.get_mime_type(file_ext) | ||||||
|  | 		if g.force_mimetype && mimetype == '' { | ||||||
|  | 			mimetype = g.default_mimetype | ||||||
|  | 		} | ||||||
|  | 		files << EmbedFileSpec{ | ||||||
|  | 			key:      file_key | ||||||
|  | 			path:     file_path | ||||||
|  | 			name:     file_name | ||||||
|  | 			ext:      file_ext | ||||||
|  | 			mimetype: mimetype | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return files | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								tests/mymod/assets/example.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/mymod/assets/example.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | {"some": "JSON data"} | ||||||
							
								
								
									
										7
									
								
								tests/mymod/main.v
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/mymod/main.v
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | module main | ||||||
|  |  | ||||||
|  | fn main() { | ||||||
|  | 	println(embedfs) | ||||||
|  | 	json_file := embedfs.files['assets/example.json'] or { EmbedFile{} } | ||||||
|  | 	println(json_file.data.to_string().trim_space()) | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								tests/mymod_test.out
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/mymod_test.out
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | EmbedFileSystem{ | ||||||
|  |     files: {'assets/example.json': EmbedFile{ | ||||||
|  |         data: embed_file.EmbedFileData{ len: 22, path: "assets/example.json", apath: "", uncompressed: 8462c4 } | ||||||
|  |         meta: EmbedFileMetadata{ | ||||||
|  |             key: 'assets/example.json' | ||||||
|  |             name: 'example.json' | ||||||
|  |             ext: 'json' | ||||||
|  |             mimetype: 'application/json' | ||||||
|  |         } | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  | {"some": "JSON data"} | ||||||
							
								
								
									
										18
									
								
								tests/mymod_test.v
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								tests/mymod_test.v
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | module main | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import v.util.diff | ||||||
|  | import embedfs | ||||||
|  |  | ||||||
|  | fn test_mymod() { | ||||||
|  | 	expected_out := os.read_file('tests/mymod_test.out')! | ||||||
|  | 	os.chdir('tests/mymod')! | ||||||
|  | 	gen := embedfs.CodeGenerator{ | ||||||
|  | 		path:     'assets' | ||||||
|  | 		make_pub: false | ||||||
|  | 	} | ||||||
|  | 	os.write_file('assets_generated.v', gen.generate())! | ||||||
|  | 	ret := os.execute('sh -c "v run ."') | ||||||
|  | 	dump(diff.compare_text(ret.output, expected_out)!) | ||||||
|  | 	assert ret.output == expected_out | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user