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