init
This commit is contained in:
commit
23b0c1bf6b
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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user