module assets // this module provides an AssetManager for combining // and caching javascript & css. import ( os time crypto.md5 ) const ( UnknownAssetTypeError = 'vweb.assets: unknown asset type' ) struct AssetManager { mut: css []Asset js []Asset pub mut: // when true assets will be minified minify bool // the directory to store the cached/combined files cache_dir string } struct Asset { file_path string last_modified time.Time } // new_manager returns a new AssetManager pub fn new_manager() &AssetManager { return &AssetManager{} } // add_css adds a css asset pub fn (am mut AssetManager) add_css(file string) bool { return am.add('css', file) } // add_js adds a js asset pub fn (am mut AssetManager) add_js(file string) bool { return am.add('js', file) } // combine_css returns the combined css as a string when to_file is false // when to_file is true it combines the css to disk and returns the path of the file pub fn (am AssetManager) combine_css(to_file bool) string { return am.combine('css', to_file) } // combine_js returns the combined js as a string when to_file is false // when to_file is true it combines the css to disk and returns the path of the file pub fn (am AssetManager) combine_js(to_file bool) string { return am.combine('js', to_file) } // include_css returns the html <link> tag(s) for including the css files in a page. // when combine is true the files are combined. pub fn (am AssetManager) include_css(combine bool) string { return am.include('css', combine) } // include_js returns the html <script> tag(s) for including the js files in a page. // when combine is true the files are combined. pub fn (am AssetManager) include_js(combine bool) string { return am.include('js', combine) } fn (am AssetManager) combine(asset_type string, to_file bool) string { if am.cache_dir == '' { panic('vweb.assets: you must set a cache dir.') } cache_key := am.get_cache_key(asset_type) out_file := '$am.cache_dir/${cache_key}.$asset_type' mut out := '' // use cache if os.exists(out_file) { if to_file { return out_file } cached := os.read_file(out_file) or { return '' } return cached } // rebuild for asset in am.get_assets(asset_type) { data := os.read_file(asset.file_path) or { return '' } out += data } if am.minify { if asset_type == 'css' { out = minify_css(out) } else { out = minify_js(out) } } if !to_file { return out } if !os.is_dir(am.cache_dir) { os.mkdir(am.cache_dir) or { panic(err) } } mut file := os.create(out_file) or { panic(err) } file.write(out) file.close() return out_file } fn (am AssetManager) get_cache_key(asset_type string) string { mut files_salt := '' mut latest_modified := 0 for asset in am.get_assets(asset_type) { files_salt += asset.file_path if asset.last_modified.unix > latest_modified { latest_modified = asset.last_modified.unix } } hash := md5.sum(files_salt.bytes()).hex() return '$hash-$latest_modified' } fn (am AssetManager) include(asset_type string, combine bool) string { assets := am.get_assets(asset_type) mut out := '' if asset_type == 'css' { if combine { file := am.combine(asset_type, true) return '<link rel="stylesheet" href="$file">\n' } for asset in assets { out += '<link rel="stylesheet" href="$asset.file_path">\n' } } if asset_type == 'js' { if combine { file := am.combine(asset_type, true) return '<script type="text/javascript" src="$file"></script>\n' } for asset in assets { out += '<script type="text/javascript" src="$asset.file_path"></script>\n' } } return out } // dont return option until size limit is removed // fn (am mut AssetManager) add(asset_type, file string) ?bool { fn (am mut AssetManager) add(asset_type, file string) bool { if !os.exists(file) { // return error('vweb.assets: cannot add asset $file, it does not exist') return false } asset := Asset{ file_path: file last_modified: time.Time{unix: os.file_last_mod_unix(file)} } if asset_type == 'css' { am.css << asset } else if asset_type == 'js' { am.js << asset } else { panic('$UnknownAssetTypeError ($asset_type).') } return true } fn (am AssetManager) exists(asset_type, file string) bool { assets := am.get_assets(asset_type) for asset in assets { if asset.file_path == file { return true } } return false } fn (am AssetManager) get_assets(asset_type string) []Asset { if asset_type != 'css' && asset_type != 'js' { panic('$UnknownAssetTypeError ($asset_type).') } assets := if asset_type == 'css' { am.css } else { am.js } return assets } // todo: implement proper minification pub fn minify_css(css string) string { mut lines := css.split('\n') for i, _ in lines { lines[i] = lines[i].trim_space() } return lines.join(' ') } // todo: implement proper minification pub fn minify_js(js string) string { mut lines := js.split('\n') for i, _ in lines { lines[i] = lines[i].trim_space() } return lines.join(' ') }