github.com/ernestokarim/closurer@v0.0.0-20130119214741-f245d086c750/config/conf.go (about)

     1  package config
     2  
     3  import (
     4  	"encoding/xml"
     5  	"log"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/ernestokarim/closurer/app"
    11  )
    12  
    13  var (
    14  	globalConf       *Config
    15  	lastModification time.Time
    16  )
    17  
    18  func Load() error {
    19  	if globalConf != nil && !NoCache {
    20  		info, err := os.Lstat(ConfPath)
    21  		if err != nil {
    22  			return app.Error(err)
    23  		}
    24  
    25  		if info.ModTime() == lastModification {
    26  			return nil
    27  		}
    28  	}
    29  
    30  	f, err := os.Open(ConfPath)
    31  	if err != nil {
    32  		return app.Error(err)
    33  	}
    34  	defer f.Close()
    35  
    36  	conf := new(Config)
    37  	if err := xml.NewDecoder(f).Decode(&conf); err != nil {
    38  		return app.Error(err)
    39  	}
    40  
    41  	// Assign it before validating, because we need it to
    42  	// inherit targets.
    43  	globalConf = conf
    44  
    45  	if err := conf.validate(); err != nil {
    46  		return err
    47  	}
    48  
    49  	info, err := os.Lstat(ConfPath)
    50  	if err != nil {
    51  		return app.Error(err)
    52  	}
    53  	lastModification = info.ModTime()
    54  
    55  	return nil
    56  }
    57  
    58  func Current() *Config {
    59  	return globalConf
    60  }
    61  
    62  func (c *Config) validate() error {
    63  	// Library & compiler paths
    64  	if c.Js != nil {
    65  		if c.Js.Root == "" {
    66  			return app.Errorf("The JS root folder is required")
    67  		}
    68  
    69  		if c.Js.Formatting != "" && c.Js.Formatting != "PRETTY_PRINT" {
    70  			return app.Errorf("formatting mode not allowed: %s", c.Js.Formatting)
    71  		}
    72  
    73  		if c.Js.SideEffects != "" && c.Js.SideEffects != "true" {
    74  			return app.Errorf("boolean value not allowed: %s", c.Js.SideEffects)
    75  		}
    76  
    77  		if c.Js.Language != "" {
    78  			modes := map[string]bool{
    79  				"ECMASCRIPT3":        true,
    80  				"ECMASCRIPT5":        true,
    81  				"ECMASCRIPT5_STRICT": true,
    82  			}
    83  			if _, ok := modes[c.Js.Language]; !ok {
    84  				return app.Errorf("language mode not allowed: %s", c.Js.Language)
    85  			}
    86  		}
    87  
    88  		// JS targets and inheritation
    89  		if len(c.Js.Targets) == 0 {
    90  			return app.Errorf("No target provided for JS code")
    91  		}
    92  		for _, t := range c.Js.Targets {
    93  			if err := t.ApplyInherits(); err != nil {
    94  				return err
    95  			}
    96  		}
    97  
    98  		// Check compilation mode and warnings level
    99  		for _, t := range c.Js.Targets {
   100  			modes := map[string]bool{
   101  				"SIMPLE":     true,
   102  				"ADVANCED":   true,
   103  				"WHITESPACE": true,
   104  				"RAW":        true,
   105  			}
   106  			if _, ok := modes[t.Mode]; !ok {
   107  				return app.Errorf("Illegal compilation mode in target %s: %s", t.Name, t.Mode)
   108  			}
   109  
   110  			levels := map[string]bool{
   111  				"QUIET":   true,
   112  				"DEFAULT": true,
   113  				"VERBOSE": true,
   114  			}
   115  			if _, ok := levels[t.Level]; !ok {
   116  				return app.Errorf("Illegal warning level in target %s: %s", t.Name, t.Level)
   117  			}
   118  		}
   119  
   120  		// Check that the command line target is in the config file
   121  		found := false
   122  		for _, name := range TargetList() {
   123  			for _, t := range c.Js.Targets {
   124  				if t.Name == name {
   125  					found = true
   126  					break
   127  				}
   128  			}
   129  			if !found {
   130  				return app.Errorf("Target %s not found in the config file", name)
   131  			}
   132  		}
   133  
   134  		// Validate the compilation checks
   135  		if c.Js.Checks != nil {
   136  			validChecks(c.Js.Checks.Errors)
   137  			validChecks(c.Js.Checks.Warnings)
   138  			validChecks(c.Js.Checks.Offs)
   139  		}
   140  
   141  		// Check the prepend files
   142  		if c.Js.Prepends != nil {
   143  			for _, prepend := range c.Js.Prepends {
   144  				if prepend.File == "" {
   145  					return app.Errorf("prepend file empty")
   146  				}
   147  			}
   148  		}
   149  	}
   150  
   151  	if c.Build == "" {
   152  		return app.Errorf("The build folder is required")
   153  	}
   154  	if c.Library != nil && c.Library.Root == "" {
   155  		return app.Errorf("The Closure Library path is required")
   156  	}
   157  	if c.Js != nil && c.Js.Compiler == "" {
   158  		return app.Errorf("The Closure Compiler path is required")
   159  	}
   160  
   161  	if c.Gss != nil {
   162  		// GSS compiler
   163  		if c.Gss.Compiler == "" {
   164  			return app.Errorf("The Closure Stylesheets path is required")
   165  		}
   166  
   167  		// GSS targets
   168  		if len(c.Gss.Targets) == 0 {
   169  			return app.Errorf("No target provided for GSS code")
   170  		}
   171  
   172  		// At least one input file should be provided
   173  		if len(c.Gss.Inputs) == 0 {
   174  			return app.Errorf("No inputs provided for GSS code")
   175  		}
   176  
   177  		// Compare JS targets and GSS targets
   178  		if c.Js != nil {
   179  			if len(c.Js.Targets) != len(c.Gss.Targets) {
   180  				return app.Errorf("Different number of targets provided for GSS & JS")
   181  			}
   182  			for i, tjs := range c.Js.Targets {
   183  				tgss := c.Gss.Targets[i]
   184  				if tjs.Name != tgss.Name {
   185  					return app.Errorf("Targets with different name or order: %s != %s",
   186  						tjs.Name, tgss.Name)
   187  				}
   188  
   189  				// Rename property of the GSS target
   190  				if tgss.Rename != "true" && tgss.Rename != "false" && tgss.Rename != "" {
   191  					return app.Errorf("Illegal renaming policy value")
   192  				}
   193  
   194  				// Apply the inherits option
   195  				if err := tgss.ApplyInherits(); err != nil {
   196  					return err
   197  				}
   198  
   199  				// Check that the GSS defines don't have a value
   200  				for _, d := range tgss.Defines {
   201  					if d.Value != "" {
   202  						return app.Errorf("Define values in GSS should be empty")
   203  					}
   204  				}
   205  			}
   206  		}
   207  	}
   208  
   209  	// Soy compiler
   210  	if c.Soy != nil && c.Soy.Root != "" && c.Soy.Compiler == "" {
   211  		return app.Errorf("The Closure Templates path is required")
   212  	}
   213  
   214  	// Current targets in build mode
   215  	if c.Js != nil && c.Gss != nil {
   216  		for _, t := range TargetList() {
   217  			SelectTarget(t)
   218  
   219  			tjs := c.Js.CurTarget()
   220  			tgss := c.Gss.CurTarget()
   221  
   222  			if tjs == nil || tgss == nil {
   223  				return app.Errorf("Target not found in the config: %s", t)
   224  			}
   225  
   226  			if Build && IsTarget(tjs.Name) {
   227  				if tjs.Output == "" {
   228  					return app.Errorf("Target to build JS without an output file: %s",
   229  						tjs.Name)
   230  				}
   231  				if tgss != nil && tgss.Output == "" {
   232  					return app.Errorf("Target to build GSS without an output file: %s",
   233  						tjs.Name)
   234  				}
   235  			}
   236  		}
   237  	}
   238  
   239  	// Fix the compilers paths
   240  	if c.Js != nil {
   241  		c.Js.Compiler = fixPath(c.Js.Compiler)
   242  	}
   243  	if c.Gss != nil {
   244  		c.Gss.Compiler = fixPath(c.Gss.Compiler)
   245  	}
   246  	if c.Soy != nil {
   247  		c.Soy.Compiler = fixPath(c.Soy.Compiler)
   248  	}
   249  	if c.Library != nil {
   250  		c.Library.Root = fixPath(c.Library.Root)
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  // Replace the ~ with the correct folder path
   257  func fixPath(p string) string {
   258  	if !strings.Contains(p, "~") {
   259  		return p
   260  	}
   261  
   262  	user := os.Getenv("USER")
   263  	if user == "" {
   264  		user = os.Getenv("USERNAME")
   265  	}
   266  	if user == "" {
   267  		log.Fatal("Found ~ in a path, but USER nor USERNAME are exported in the env")
   268  	}
   269  
   270  	return strings.Replace(p, "~", "/home/"+user, -1)
   271  }
   272  
   273  func validChecks(lst []*CheckNode) error {
   274  	for _, check := range lst {
   275  		checks := map[string]bool{
   276  			"ambiguousFunctionDecl":  true,
   277  			"checkRegExp":            true,
   278  			"checkTypes":             true,
   279  			"checkVars":              true,
   280  			"constantProperty":       true,
   281  			"deprecated":             true,
   282  			"fileoverviewTags":       true,
   283  			"internetExplorerChecks": true,
   284  			"invalidCasts":           true,
   285  			"missingProperties":      true,
   286  			"nonStandardJsDocs":      true,
   287  			"strictModuleDepCheck":   true,
   288  			"typeInvalidation":       true,
   289  			"undefinedNames":         true,
   290  			"undefinedVars":          true,
   291  			"unknownDefines":         true,
   292  			"uselessCode":            true,
   293  			"globalThis":             true,
   294  			"duplicateMessage":       true,
   295  		}
   296  		if _, ok := checks[check.Name]; !ok {
   297  			return app.Errorf("Illegal check: %s", check.Name)
   298  		}
   299  	}
   300  
   301  	return nil
   302  }