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