func (pkgr *Packager) resolveFunctions(dirName, fileName string) { // the existence of `fileName` should be verified by the caller relPath := filepath.Join(dirName, fileName) defs := parser.ParseFile(pkgr.MixerDir, dirName, fileName, true, make([]string, 0)) for _, f := range defs.Functions { // if it's an import stub ... if f.GetName() == "@import" { importPath := f.GetDescription() importExists, _ := fileutil.Exists(filepath.Join(pkgr.MixerDir, importPath)) if !importExists { errURL := "http://help.moovweb.com/entries/22335641-importing-non-existent-files-in-functions-main-ts" msg := fmt.Sprintf("\n********\nin file %s:\nattempting to import nonexistent file %s\nPlease consult %s for more information about this error.\n********\n", relPath, importPath, errURL) panic(msg) } pkgr.resolveFunctions(filepath.Dir(importPath), filepath.Base(importPath)) continue // to forgo appending it to the comprehensive list of functions // otherwise it's a proper function definition -- see if it's native } else if f.GetBuiltIn() { pkgr.resolveNativeDeclaration(f, relPath) // otherwise it's a user-defined function } else { pkgr.resolveUserDefinition(f, relPath) } pkgr.Package.Functions = append(pkgr.Package.Functions, f) } }
func (pkgr *Packager) buildLib() { // tritium libraries are optional -- compile them if they're there, otherwise do nothing libPath := filepath.Join(pkgr.MixerDir, pkgr.LibDir, ENTRY_FILE) there, _ := fileutil.Exists(libPath) if !there { return } pkgr.resolveFunctions(pkgr.LibDir, ENTRY_FILE) }
func (pkgr *Packager) readDependenciesFile() { depPath := filepath.Join(pkgr.MixerDir, DEPS_FILE) depPathExists, _ := fileutil.Exists(depPath) if !depPathExists { return } data, readErr := ioutil.ReadFile(filepath.Join(pkgr.MixerDir, DEPS_FILE)) if readErr != nil { panic(fmt.Sprintf("error reading dependencies file for `%s`", pkgr.Mixer.GetName())) } pkgr.Dependencies = make(map[string]string) yaml.Unmarshal(data, &pkgr.Dependencies) }
func (pkgr *Packager) buildLib() { // tritium libraries are optional -- compile them if they're there, otherwise do nothing libPath := filepath.Join(pkgr.MixerDir, pkgr.LibDir, ENTRY_FILE) there, _ := fileutil.Exists(libPath) if !there { return } pkgr.resolveFunctions(pkgr.LibDir, ENTRY_FILE) // legacyPackage := &legacy.Package{} // legacyPackage.Package = pkgr.Package // for _, f := range pkgr.Package.Functions { // legacyPackage.ResolveFunctionDescendants(f) // } }
func New(relSrcDir, libDir string, mayBuildHttpTransformers bool, logger *golog.Logger, mixerDownloader downloader) *Packager { pkgr := new(Packager) wd, wdErr := os.Getwd() if wdErr != nil { panic("unable to determine current directory for mixer creation") } absSrcDir, absErr := filepath.Abs(relSrcDir) if absErr != nil { panic("unable to absolutize mixer source directory for mixer creation") } absSrcDir = filepath.Clean(absSrcDir) pkgr.MixerDir = absSrcDir pkgr.LibDir = libDir pkgr.IncludePaths = make([]string, 2) // support more in the future as a command-line option pkgr.IncludePaths[0] = wd pkgr.IncludePaths[1] = filepath.Dir(absSrcDir) pkgr.Mixer = tp.NewMixer(absSrcDir) pkgr.PackagerVersion = proto.Int32(PACKAGER_VERSION) pkgr.readDependenciesFile() pkgr.AlreadyLoaded = make(map[string]bool) pkgr.Logger = logger pkgr.downloader = mixerDownloader pkgr.Mixer.Package = new(tp.Package) pkgr.Mixer.Package.Functions = make([]*tp.Function, 0) pkgr.MayBuildHttpTransformers = mayBuildHttpTransformers tSigThere, _ := fileutil.Exists(filepath.Join(pkgr.MixerDir, HTTP_TRANSFORMERS_SIGNATURE)) if tSigThere { pkgr.IsHttpTransformer = true if !pkgr.MayBuildHttpTransformers { panic("you are not authorized to build HTTP transformer mixers") } } return pkgr }
func (pkgr *Packager) resolveTypeDeclarations() { // see whether a type declarations file exists; if so, read it typeFilePath := filepath.Join(pkgr.MixerDir, pkgr.LibDir, TYPES_FILE) there, _ := fileutil.Exists(typeFilePath) if !there { return } data, readErr := ioutil.ReadFile(typeFilePath) if readErr != nil { panic(fmt.Sprintf("error reading type declarations file for `%s`", pkgr.Mixer.GetName())) } typeDecs := make([]string, 0) yaml.Unmarshal(data, &typeDecs) if pkgr.TypeMap == nil { pkgr.TypeMap = make(map[string]int) } if pkgr.SuperclassOf == nil { pkgr.SuperclassOf = make(map[string]string) } // now resolve the type declarations for _, typeDec := range typeDecs { // TODO: this logic is more complicated than it needs to be. Should really // just disallow any duplicate declarations. Anyway.... // if the type doesn't extend anything ... if !strings.Contains(typeDec, "<") { _, isThere := pkgr.TypeMap[typeDec] if !isThere { pkgr.TypeMap[typeDec] = len(pkgr.TypeMap) pkgr.SuperclassOf[typeDec] = "" // if the type has already been declared, check for a semantic conflict } else if extendee := pkgr.SuperclassOf[typeDec]; extendee != "" { panic(fmt.Sprintf("type declaration `%s` conflicts with previous declaration `%s < %s`", typeDec, typeDec, extendee)) } // if we get to this point, the declaration is a duplicate, so do nothing // else it's an extension } else { // parse the subtype and supertype splitted := strings.Split(typeDec, "<") if len(splitted) != 2 { panic(fmt.Sprintf("invalid syntax in type declaration `%s`; only one extension is permitted per declaration", typeDec)) } sub, super := strings.TrimSpace(splitted[0]), strings.TrimSpace(splitted[1]) // make sure that the supertype in a declaration actually exists _, there := pkgr.TypeMap[super] if !there { panic(fmt.Sprintf("cannot extend type `%s` before it has been declared", super)) } // check for conflicts with previous declarations // (and incidentally ensure that subtypes always have a higher id than their supertypes extendee, subHasAlreadyExtended := pkgr.SuperclassOf[sub] if subHasAlreadyExtended /* && extendee != super */ { var previousDec string if extendee == "" { previousDec = fmt.Sprintf("`%s` (extends nothing)", sub) } else if extendee == super { previousDec = fmt.Sprintf("`%s < %s` (duplicates not allowed)", sub, extendee) } else { previousDec = fmt.Sprintf("`%s < %s`", sub, extendee) } panic(fmt.Sprintf("type declaration `%s` conflicts with previous declaration %s", typeDec, previousDec)) } // if we get this far, we can proceed with the type extension pkgr.TypeMap[sub] = len(pkgr.TypeMap) pkgr.SuperclassOf[sub] = super } } }
func (pkgr *Packager) loadDependency(name, specifiedVersion string) { foundMixerSrc := false foundCompiledMixer := false // loop through the include paths and build the first dependency that we // find, then merge that dependency into the current mixer for _, incPath := range pkgr.IncludePaths { depPath := filepath.Join(incPath, name) there, err := fileutil.Exists(depPath) if !there || err != nil { continue } foundMixerSrc = true needed := NewDependencyOf(depPath, "lib", pkgr) if needed.GetVersion() != specifiedVersion { continue } // circular dependency check if pkgr.NowVisiting == nil { pkgr.NowVisiting = make(map[string]bool) pkgr.NowVisiting[pkgr.GetName()] = true } if pkgr.NowVisiting[needed.GetName()] { panic(fmt.Sprintf("circular dependency on `%s`", needed.GetName())) } // pass the dependency stack downwards ... needed.NowVisiting = pkgr.NowVisiting // ... and push the current mixer onto it needed.NowVisiting[needed.GetName()] = true needed.Build() pkgr.mergeWith(needed) // pop the dependency stack needed.NowVisiting[needed.GetName()] = false // don't need to pass it back up because maps are shared, and extending // them doesn't invalidate references to them (unlike slices) pkgr.Logger.Infof(" - built dependency `%s` (%s) from source", name, specifiedVersion) return } // if a mixer src dir isn't found, try to grab a compiled version locally mxr, mxErr := mixer.GetMixer(name, specifiedVersion) if mxErr == nil { foundCompiledMixer = true needed := NewFromCompiledMixer(mxr) pkgr.MergeCompiled(needed) pkgr.Logger.Infof(" - loaded dependency `%s` (%s) from local compiled mixer", name, specifiedVersion) return } // otherwise, try to download a compiled version from apollo mxr, mxErr = pkgr.downloader(name, specifiedVersion) if mxErr == nil { foundCompiledMixer = true needed := NewFromCompiledMixer(mxr) pkgr.MergeCompiled(needed) pkgr.Logger.Infof(" - loaded dependency `%s` (%s) from downloaded mixer", name, specifiedVersion) return } if foundMixerSrc || foundCompiledMixer { panic(fmt.Sprintf("version %s needed for dependency `%s` of `%s`", specifiedVersion, name, pkgr.GetName())) } panic(fmt.Sprintf("unable to find dependency `%s` of `%s`", name, pkgr.GetName())) }
func (p *Parser) statement() (node *tp.Instruction) { switch p.peek().Lexeme { case IMPORT, OPTIONAL: optional := false if p.peek().Lexeme == OPTIONAL { optional = true } if p.inFunc { panic(fmt.Sprintf("|%s:%d -- imports not allowed inside function definitions", p.FileName, p.peek().LineNumber)) } token := p.pop() // pop the "@import" or "@optional" token (includes importee) importPath := token.Value layered := false appliedLayers := p.AppliedLayers if strings.Index(importPath, "@") != -1 { if len(p.Layers) == 0 { if !optional { panic(fmt.Sprintf("%s:%d -- required layer not provided; please make sure you've specified all necessary layers in the start-up options", p.FileName, token.LineNumber)) } else { // make a no-op if the import is optional and no layer has been provided node = tp.MakeText("", token.LineNumber) return } } if len(appliedLayers) > 0 { tmpSlice := make([]string, 2) tmpSlice[0] = appliedLayers tmpSlice[1] = p.Layers[0] appliedLayers = strings.Join(tmpSlice, "/") } else { appliedLayers = p.Layers[0] } importPath = strings.Replace(importPath, "@", p.Layers[0], -1) layered = true } scriptLocationInProject := filepath.Clean(filepath.Join(p.ScriptPath, importPath)) // extract the root script folder from the relative path of the importee // (would be easier if filepath.FromSlash worked as advertised) dir, base := filepath.Split(p.ScriptPath) if len(dir) == 0 { dir = base base = "" } if dir[len(dir)-1] == os.PathSeparator { dir = dir[0 : len(dir)-1] } for len(base) > 0 { dir, base = filepath.Split(dir) if len(dir) == 0 { dir = base base = "" } if dir[len(dir)-1] == os.PathSeparator { dir = dir[0 : len(dir)-1] } } // make sure that the importee is under the right subfolder if !strings.HasPrefix(scriptLocationInProject, dir) { msg := fmt.Sprintf("%s:%d -- imported file must exist under the `%s` folder", p.FileName, token.LineNumber, dir) panic(msg) } // now make sure the file exists and give a helpful message if it's a layer file exists, exErr := fileutil.Exists(filepath.Join(p.ProjectPath, scriptLocationInProject)) if !exists || exErr != nil { if optional { // make a no-op if the import is optional node = tp.MakeText("", token.LineNumber) return } else if layered { panic(fmt.Sprintf("%s:%d -- required layer (%s) has no corresponding Tritium file (want %s)", p.FileName, p.LineNumber, appliedLayers, scriptLocationInProject)) } else { panic(fmt.Sprintf("%s:%d -- file to import not found (%s)", p.FileName, p.LineNumber, scriptLocationInProject)) } } node = tp.MakeImport(scriptLocationInProject, token.LineNumber) if layered { node.Namespace = proto.String(appliedLayers) // re-use this slot to specify which layer the import is targeting } case STRING, REGEXP, POS, READ, ID, TYPE, GVAR, LVAR, LPAREN: node = p.expression() case NAMESPACE: p.error("`@namespace` directive must occur at the top of a file or code block") default: p.error("statement must consist of import or expression") } return node }