2012-02-28 03:50:17 +00:00
/ *
Copyright 2012 Google Inc .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package main
import (
"bytes"
2012-11-28 23:54:56 +00:00
"compress/zlib"
2012-02-28 03:50:17 +00:00
"flag"
"fmt"
"go/parser"
2012-11-05 17:43:20 +00:00
"go/printer"
2012-02-28 03:50:17 +00:00
"go/token"
2012-11-28 23:54:56 +00:00
"io"
2012-02-28 03:50:17 +00:00
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)
2012-11-28 23:54:56 +00:00
const (
maxUncompressed = 50 << 10 // 50KB
// Threshold ratio for compression.
// Files which don't compress at least as well are kept uncompressed.
zRatio = 0.5
)
2013-06-05 00:05:40 +00:00
func usage ( ) {
fmt . Fprintf ( os . Stderr , "usage: genfileembed [flags] [<dir>]\n" )
flag . PrintDefaults ( )
os . Exit ( 2 )
}
2012-02-28 03:50:17 +00:00
func main ( ) {
2013-06-05 00:05:40 +00:00
var processAll bool
flag . BoolVar ( & processAll , "all" , false , "process all files (if false, only process modified files)" )
var fileEmbedPkgPath string
flag . StringVar ( & fileEmbedPkgPath , "fileembed-package" , "camlistore.org/pkg/fileembed" , "the Go package name for fileembed. If you have vendored fileembed (e.g. with goven), you can use this flag to ensure that generated code imports the vendored package." )
flag . Usage = usage
2012-02-28 03:50:17 +00:00
flag . Parse ( )
2013-06-05 00:05:40 +00:00
2012-02-28 03:50:17 +00:00
dir := "."
switch flag . NArg ( ) {
case 0 :
case 1 :
dir = flag . Arg ( 0 )
if err := os . Chdir ( dir ) ; err != nil {
log . Fatalf ( "chdir(%q) = %v" , dir , err )
}
default :
2013-06-05 00:05:40 +00:00
flag . Usage ( )
2012-02-28 03:50:17 +00:00
}
pkgName , filePattern , err := parseFileEmbed ( )
if err != nil {
log . Fatalf ( "Error parsing %s/fileembed.go: %v" , dir , err )
}
2012-02-29 06:49:52 +00:00
2012-02-28 03:50:17 +00:00
for _ , fileName := range matchingFiles ( filePattern ) {
embedName := "zembed_" + fileName + ".go"
fi , err := os . Stat ( fileName )
if err != nil {
log . Fatal ( err )
}
efi , err := os . Stat ( embedName )
2013-06-05 00:05:40 +00:00
if err == nil && ! efi . ModTime ( ) . Before ( fi . ModTime ( ) ) && ! processAll {
2012-02-28 03:50:17 +00:00
continue
}
log . Printf ( "Updating %s (package %s)" , filepath . Join ( dir , embedName ) , pkgName )
2012-11-28 23:54:56 +00:00
2013-05-24 21:19:29 +00:00
bs , err := ioutil . ReadFile ( fileName )
if err != nil {
log . Fatal ( err )
}
// TODO(mpl): rm "newui" test for the final switch
if strings . HasSuffix ( dir , "newui" ) && strings . HasSuffix ( fileName , ".html" ) {
bs = useMinifiedJs ( bs )
}
zb , fileSize := compressFile ( bytes . NewReader ( bs ) )
2012-11-28 23:54:56 +00:00
ratio := float64 ( len ( zb ) ) / float64 ( fileSize )
byteStreamType := ""
if fileSize < maxUncompressed || ratio > zRatio {
byteStreamType = "fileembed.String"
} else {
byteStreamType = "fileembed.ZlibCompressed"
bs = zb
2012-11-05 17:43:20 +00:00
}
2012-11-28 23:54:56 +00:00
qb := quote ( bs ) // quoted bytes
2012-11-05 17:43:20 +00:00
2012-02-28 03:50:17 +00:00
var b bytes . Buffer
fmt . Fprintf ( & b , "// THIS FILE IS AUTO-GENERATED FROM %s\n" , fileName )
2013-03-16 00:16:40 +00:00
fmt . Fprintf ( & b , "// DO NOT EDIT.\n\n" )
2012-11-05 17:43:20 +00:00
fmt . Fprintf ( & b , "package %s\n\n" , pkgName )
fmt . Fprintf ( & b , "import \"time\"\n\n" )
2013-06-05 00:05:40 +00:00
fmt . Fprintf ( & b , "import \"" + fileEmbedPkgPath + "\"\n\n" )
2012-11-28 23:54:56 +00:00
fmt . Fprintf ( & b , "func init() {\n\tFiles.Add(%q, %d, %s(%s), time.Unix(0, %d));\n}\n" ,
fileName , fileSize , byteStreamType , qb , fi . ModTime ( ) . UnixNano ( ) )
2012-11-05 17:43:20 +00:00
// gofmt it
fset := token . NewFileSet ( )
ast , err := parser . ParseFile ( fset , "" , b . Bytes ( ) , parser . ParseComments )
if err != nil {
log . Fatal ( err )
}
var clean bytes . Buffer
config := & printer . Config {
Mode : printer . TabIndent | printer . UseSpaces ,
Tabwidth : 8 ,
}
err = config . Fprint ( & clean , fset , ast )
if err != nil {
log . Fatal ( err )
}
if err := ioutil . WriteFile ( embedName , clean . Bytes ( ) , 0644 ) ; err != nil {
2012-02-28 03:50:17 +00:00
log . Fatal ( err )
}
}
}
2013-05-24 21:19:29 +00:00
var (
jsPattern = regexp . MustCompile (
` <script +(type="text/javascript" )?src="(\./)?[a-zA-Z0-9\-\_/]+\.js"></script> ` )
requirePattern = regexp . MustCompile (
` <script>\s*goog.require\(.*\);\s*</script> ` )
headPattern = regexp . MustCompile ( ` <head> ` )
withMinified = "<head>\n\t\t<script type=\"text/javascript\" src=\"all.js\"></script>"
)
func useMinifiedJs ( b [ ] byte ) [ ] byte {
bs := jsPattern . ReplaceAllLiteral ( b , [ ] byte ( "" ) )
bs = requirePattern . ReplaceAllLiteral ( bs , [ ] byte ( "" ) )
bs = headPattern . ReplaceAllLiteral ( bs , [ ] byte ( withMinified ) )
return bs
}
func compressFile ( r io . Reader ) ( [ ] byte , int64 ) {
2012-11-28 23:54:56 +00:00
var zb bytes . Buffer
w := zlib . NewWriter ( & zb )
2013-05-24 21:19:29 +00:00
n , err := io . Copy ( w , r )
2012-11-28 23:54:56 +00:00
if err != nil {
log . Fatal ( err )
}
w . Close ( )
return zb . Bytes ( ) , n
}
func quote ( bs [ ] byte ) [ ] byte {
var qb bytes . Buffer
qb . WriteByte ( '"' )
run := 0
for _ , b := range bs {
if b == '\n' {
qb . WriteString ( ` \n ` )
}
if b == '\n' || run > 80 {
qb . WriteString ( "\" +\n\t\"" )
run = 0
}
if b == '\n' {
continue
}
run ++
if b == '\\' {
qb . WriteString ( ` \\ ` )
continue
}
if b == '"' {
qb . WriteString ( ` \" ` )
continue
}
if ( b >= 32 && b <= 126 ) || b == '\t' {
qb . WriteByte ( b )
continue
}
fmt . Fprintf ( & qb , "\\x%02x" , b )
}
qb . WriteByte ( '"' )
return qb . Bytes ( )
}
2012-02-28 03:50:17 +00:00
func matchingFiles ( p * regexp . Regexp ) [ ] string {
var f [ ] string
d , err := os . Open ( "." )
if err != nil {
log . Fatal ( err )
}
defer d . Close ( )
names , err := d . Readdirnames ( - 1 )
if err != nil {
log . Fatal ( err )
}
for _ , n := range names {
if strings . HasPrefix ( n , "zembed_" ) {
continue
}
if p . MatchString ( n ) {
f = append ( f , n )
}
}
return f
}
func parseFileEmbed ( ) ( pkgName string , filePattern * regexp . Regexp , err error ) {
fe , err := os . Open ( "fileembed.go" )
if err != nil {
return
}
defer fe . Close ( )
fs := token . NewFileSet ( )
astf , err := parser . ParseFile ( fs , "fileembed.go" , fe , parser . PackageClauseOnly | parser . ParseComments )
if err != nil {
return
}
pkgName = astf . Name . Name
if astf . Doc == nil {
err = fmt . Errorf ( "no package comment before the %q line" , "package " + pkgName )
return
}
pkgComment := astf . Doc . Text ( )
findPattern := regexp . MustCompile ( ` (?m)^#fileembed\s+pattern\s+(\S+)\s*$ ` )
m := findPattern . FindStringSubmatch ( pkgComment )
if m == nil {
err = fmt . Errorf ( "package comment lacks line of form: #fileembed pattern <pattern>" )
return
}
pattern := m [ 1 ]
filePattern , err = regexp . Compile ( pattern )
if err != nil {
err = fmt . Errorf ( "bad regexp %q: %v" , pattern , err )
return
}
return
}