// FileAppender takes the giving data of type FileWriter and appends the value out into a endpoint which is the combination of the name and the toPath value provided func FileAppender(fx func(string) string) flux.Reactor { if fx == nil { fx = defaultMux } return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if file, ok := data.(*FileWrite); ok { // endpoint := filepath.Join(toPath, file.Path) endpoint := fx(file.Path) endpointDir := filepath.Dir(endpoint) //make the directory part incase it does not exists os.MkdirAll(endpointDir, 0700) osfile, err := os.Open(endpoint) if err != nil { root.ReplyError(err) return } defer osfile.Close() // io.Copy(osfile, file.Data) osfile.Write(file.Data) root.Reply(&FileWrite{Path: endpoint}) } })) }
// BinaryLauncher returns a new Task generator that builds a binary runner from the given properties, which causing a relaunch of a binary file everytime it recieves a signal, it sends out a signal onces its done running all commands func BinaryLauncher(bin string, args []string) flux.Reactor { var channel chan bool return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if channel == nil { channel = RunBin(bin, args, func() { root.Reply(true) }, func() { go root.Close() }) } select { case <-root.CloseNotify(): close(channel) return case <-time.After(0): //force check of boolean values to ensure we can use correct signal if cmd, ok := data.(bool); ok { channel <- cmd return } //TODO: should we fallback to sending true if we receive a signal normally? or remove this // channel <- true } })) }
// GoRunner calls `go run` with the command it receives from its data pipes func GoRunner() flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if cmd, ok := data.(string); ok { root.Reply(GoRun(cmd)) } })) }
// ModFileWrite provides a task that allows building a fileWrite modder,where you mod out the values for a particular FileWrite struct func ModFileWrite(fx func(*FileWrite)) flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if fw, ok := data.(*FileWrite); ok { fx(fw) root.Reply(fw) } })) }
// GoInstallerWith calls `go install` everysingle time to the provided path once a signal is received func GoInstallerWith(path string) flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, _ interface{}) { if err := GoDeps(path); err != nil { root.ReplyError(err) return } root.Reply(true) })) }
// GoBuilder calls `go run` with the command it receives from its data pipes, using the GoBuild function func GoBuilder() flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if cmd, ok := data.(BuildConfig); ok { if err := Gobuild(cmd.Path, cmd.Name, cmd.Args); err != nil { root.ReplyError(err) } } })) }
// GoArgsBuilderWith calls `go run` everysingle time to the provided path once a signal is received using the GobuildArgs function func GoArgsBuilderWith(cmd []string) flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, _ interface{}) { if err := GobuildArgs(cmd); err != nil { root.ReplyError(err) return } root.Reply(true) })) }
// ByteRenderer provides a baseline worker for building rendering tasks eg markdown. It expects to receive a *RenderFile and then it returns another *RenderFile containing the outputed rendered data with the path from the previous RenderFile,this allows chaining with other ByteRenderers func ByteRenderer(fx RenderMux) flux.Reactor { if fx == nil { panic("RenderMux cant be nil for ByteRender") } return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if databytes, ok := data.(*RenderFile); ok { root.Reply(&RenderFile{Path: databytes.Path, Data: fx(databytes.Data)}) } })) }
// GoBuilderWith calls `go run` everysingle time to the provided path once a signal is received using the GoBuild function func GoBuilderWith(cmd BuildConfig) flux.Reactor { validateBuildConfig(cmd) return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, _ interface{}) { if err := Gobuild(cmd.Path, cmd.Name, cmd.Args); err != nil { root.ReplyError(err) return } root.Reply(true) })) }
// GoInstaller calls `go install` from the path it receives from its data pipes func GoInstaller() flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if path, ok := data.(string); ok { if err := GoDeps(path); err != nil { root.ReplyError(err) return } root.Reply(true) } })) }
// GoArgsBuilder calls `go run` with the command it receives from its data pipes usingthe GobuildArgs function func GoArgsBuilder() flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if cmd, ok := data.([]string); ok { if err := GobuildArgs(cmd); err != nil { root.ReplyError(err) return } root.Reply(true) } })) }
// FileAllRemover takes a *RemoveFile as the data and removes the path using the os.RemoveAll func FileAllRemover() flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if file, ok := data.(*RemoveFile); ok { err := os.RemoveAll(file.Path) if err != nil { root.ReplyError(err) return } } })) }
func addCommander(pm *PluginManager) { pm.Add("commandWatch", func(config *BuildConfig, options Plugins, c chan bool) { /*Expects to receive a plugin config follow this format tag: dirWatch config: path: "./static/less" args: - lessc ./static/less/main.less ./static/css/main.css - lessc ./static/less/svg.less ./static/css/svg.css where the config.path is the path to be watched */ //get the current directory pwd, _ := os.Getwd() //get the dir we should watch dir := options.Config["path"] //get the command we should run on change commands := options.Args if dir == "" { fmt.Printf("---> dirWatch.error: no path set in config map for plug") return } //get the absolute path absDir := filepath.Join(pwd, dir) //create the file watcher watcher := fs.Watch(fs.WatchConfig{ Path: absDir, }) watcher.React(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if ev, ok := data.(fsnotify.Event); ok { fmt.Printf("--> commandWatch:File as changed: %+s\n", ev.String()) } }), true) // create the command runner set to run the args watcher.Bind(builders.CommandLauncher(commands), true) flux.GoDefer("CommandWatch:kill", func() { <-c watcher.Close() }) }) }
// CommandLauncher returns a new Task generator that builds a command executor that executes a series of command every time it receives a signal, it sends out a signal onces its done running all commands func CommandLauncher(cmd []string) flux.Reactor { var channel chan bool return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, _ interface{}) { if channel == nil { channel = RunCMD(cmd, func() { root.Reply(true) }) } select { case <-root.CloseNotify(): close(channel) return case <-time.After(0): channel <- true } })) }
// GoFileLauncher returns a new Task generator that builds a binary runner from the given properties, which causing a relaunch of a binary file everytime it recieves a signal, it sends out a signal onces its done running all commands func GoFileLauncher(goFile string, args []string) flux.Reactor { var channel chan bool return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if channel == nil { channel = RunGo(goFile, args, func() { root.Reply(true) }, func() { go root.Close() }) } select { case <-root.CloseNotify(): close(channel) return case <-time.After(0): channel <- true } })) }
// JSBuildLauncher returns a Task generator that builds a new jsbuild task giving the specific configuration and on every reception of signals rebuilds and sends off a FileWrite for each file i.e the js and js.map file func JSBuildLauncher(config JSBuildConfig) flux.Reactor { if config.Package == "" { panic("JSBuildConfig.Package can not be empty") } if config.FileName == "" { config.FileName = "jsapp.build" } // var session *JSSession return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { // if session == nil { session := NewJSSession(config.Tags, config.Verbose, false) // } // session.Session. //do we have an optional PackageDir that is not empty ? if so we use session.BuildDir //else session.BuildPkg var js, jsmap *bytes.Buffer var err error if config.PackageDir != "" { js, jsmap, err = session.BuildDir(config.PackageDir, config.Package, config.FileName) } else { js, jsmap, err = session.BuildPkg(config.Package, config.FileName) } if err != nil { root.ReplyError(err) return } jsfile := fmt.Sprintf("%s.js", config.FileName) jsmapfile := fmt.Sprintf("%s.js.map", config.FileName) root.Reply(&fs.FileWrite{Data: js.Bytes(), Path: filepath.Join(config.Folder, jsfile)}) root.Reply(&fs.FileWrite{Data: jsmap.Bytes(), Path: filepath.Join(config.Folder, jsmapfile)}) })) }
// BinaryBuildLauncher combines the builder and binary runner to provide a simple and order-based process, // the BinaryLauncher is only created to handling a binary lunching making it abit of a roundabout to time its response to wait until another process finishes, but BinaryBuildLuncher cleans out the necessity and provides a reactor that embedds the necessary call routines while still response the: Build->Run or StopRunning->Build->Run process in development func BinaryBuildLauncher(cmd BinaryBuildConfig) flux.Reactor { validateBinaryBuildConfig(cmd) // first generate the output file name from the config var basename = cmd.Name if runtime.GOOS == "windows" { basename = fmt.Sprintf("%s.exe", basename) } binfile := filepath.Join(cmd.Path, basename) //create the root stack which connects all the sequence of build and run together buildStack := flux.ReactorStack() //package builder builder := GoBuilderWith(BuildConfig{Path: cmd.Path, Name: cmd.Name, Args: cmd.BuildArgs}) //package runner runner := BinaryLauncher(binfile, cmd.RunArgs) //when buildStack receives a signal, we will send a bool(false) signal to runner to kill the current process buildStack.React(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { //tell runner to kill process // log.Printf("sending to runner") runner.Send(false) //forward the signal down the chain root.Reply(data) }), true) //connect the build stack first then the runn stack to force order buildStack.Bind(builder, true) buildStack.Bind(runner, true) return buildStack }
// FileReader returns a new flux.Reactor that takes a path and reads out returning the file path func FileReader() flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if pr, ok := data.(*FileRead); ok { root.Reply(pr) return } if path, ok := data.(string); ok { if _, err := os.Stat(path); err == nil { file, err := os.Open(path) if err != nil { root.ReplyError(err) return } defer file.Close() var buf bytes.Buffer //copy over data _, err = io.Copy(&buf, file) //if we have an error and its not EOF then reply with error if err != nil && err != io.EOF { root.ReplyError(err) return } root.Reply(&FileRead{Data: buf.Bytes(), Path: path}) } else { root.ReplyError(err) } } })) }
func addJsClient(pm *PluginManager) { //these are internally used for js building pm.Add("jsClients", func(config *BuildConfig, options Plugins, c chan bool) { for _, pkg := range options.Args { var pg Plugins pg.Config = make(PluginConfig) pg.Tag = "jsClient" pg.Config["package"] = pkg pg.Args = nil pm.Activate(pg, config, c) } }) pm.Add("jsClient", func(config *BuildConfig, options Plugins, c chan bool) { pkg := options.Config["package"] _, jsName := filepath.Split(pkg) pkgs := append([]string{}, pkg) packages, err := assets.GetAllPackageLists(pkgs) if err != nil { panic(err) } dir, err := assets.GetPackageDir(pkg) if err != nil { panic(err) } jsbuild := builders.JSLauncher(builders.JSBuildConfig{ Package: pkg, Folder: dir, FileName: jsName, Tags: options.Args, Verbose: config.Client.UseVerbose, }) jsbuild.React(func(root flux.Reactor, err error, _ interface{}) { if err != nil { fmt.Printf("--> Js.client.Build complete: Dir: %s \n -----> Error: %s \n", dir, err) } }, true) watcher := fs.WatchSet(fs.WatchSetConfig{ Path: packages, Validator: func(base string, info os.FileInfo) bool { if strings.Contains(base, ".git") { return false } if info != nil && info.IsDir() { return true } if filepath.Ext(base) != ".go" { return false } return true }, }) watcher.React(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if ev, ok := data.(fsnotify.Event); ok { fmt.Printf("--> Client:File as changed: %+s\n", ev.String()) } }), true) watcher.Bind(jsbuild, true) jsbuild.Send(true) flux.GoDefer("jsClient:kill", func() { <-c watcher.Close() jsbuild.Close() }) }) }
func addGoStaticBundle(pm *PluginManager) { pm.Add("goStatic", func(config *BuildConfig, options Plugins, c chan bool) { /*Expects to receive a plugin config follow this format: you can control all aspects of the assets.BindFS using the following tag: gostatic # add commands to run on file changes args: - touch ./templates/smirf.go config: in: ./markdown out: ./templates package: smirf file: smirf gzipped: true nodecompression: true production: true // generally you want to leave this to the cli to set where the config.path is the path to be watched */ //get the current directory pwd, _ := os.Getwd() //get the dir we should watch inDir := options.Config["in"] outDir := options.Config["out"] packageName := options.Config["package"] fileName := options.Config["file"] ignore := options.Config["ignore"] absDir := filepath.Join(pwd, inDir) absFile := filepath.Join(pwd, outDir, fileName+".go") if inDir == "" || outDir == "" || packageName == "" || fileName == "" { fmt.Println("---> goStatic.error: the following keys(in,out,package,file) must not be empty") return } //set up the boolean values var prod bool var gzip bool var nodcom bool var err error if gz, err := strconv.ParseBool(options.Config["gzipped"]); err == nil { gzip = gz } else { if config.Mode > 0 { gzip = true } } if br, err := strconv.ParseBool(options.Config["nodecompression"]); err == nil { nodcom = br } if pr, err := strconv.ParseBool(options.Config["production"]); err == nil { prod = pr } else { if config.Mode <= 0 { prod = false } else { prod = true } } var ignoreReg *regexp.Regexp if ignore != "" { ignoreReg = regexp.MustCompile(ignore) } gostatic, err := builders.BundleAssets(&assets.BindFSConfig{ InDir: inDir, OutDir: outDir, Package: packageName, File: fileName, Gzipped: gzip, NoDecompression: nodcom, Production: prod, }) if err != nil { fmt.Printf("---> goStatic.error: %s", err) return } gostatic.React(func(root flux.Reactor, err error, data interface{}) { fmt.Printf("--> goStatic.Reacted: State %t Error: (%+s)\n", data, err) }, true) //bundle up the assets for the main time gostatic.Send(true) var command []string if prod { if runtime.GOOS != "windows" { command = append(command, fmt.Sprintf("touch %s", absFile)) } else { command = append(command, fmt.Sprintf("copy /b %s+,,", absFile)) // command = append(command, fmt.Sprintf("powershell (ls %s).LastWriteTime = Get-Date", absFile)) } } //add the args from the options command = append(command, options.Args...) // log.Printf("command %s", command) //adds a CommandLauncher to touch the output file to force a file change notification touchCommand := builders.CommandLauncher(command) gostatic.Bind(touchCommand, true) //create the file watcher watcher := fs.Watch(fs.WatchConfig{ Path: absDir, Validator: func(path string, info os.FileInfo) bool { if ignoreReg != nil && ignoreReg.MatchString(path) { return false } return true }, }) // create the command runner set to run the args watcher.Bind(gostatic, true) watcher.React(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if ev, ok := data.(fsnotify.Event); ok { fmt.Printf("--> goStatic:File as changed: %+s\n", ev.String()) } }), true) flux.GoDefer("goStatic:kill", func() { <-c gostatic.Close() }) }) }
func addGoFriday(pm *PluginManager) { pm.Add("goFriday", func(config *BuildConfig, options Plugins, c chan bool) { /*Expects to receive a plugin config follow this format tag: gofriday config: markdown: ./markdown templates: ./templates where the config.path is the path to be watched */ //get the current directory pwd, _ := os.Getwd() //get the dir we should watch markdownDir := options.Config["markdown"] templateDir := options.Config["templates"] //optional args ext := options.Config["ext"] //must be a bool sanitizeString := options.Config["sanitize"] var sanitize bool if svz, err := strconv.ParseBool(sanitizeString); err == nil { sanitize = svz } if markdownDir == "" || templateDir == "" { fmt.Println("---> gofriday.error: expected to find keys (markdown and templates) in config map") return } //get the absolute path absDir := filepath.Join(pwd, markdownDir) tbsDir := filepath.Join(pwd, templateDir) gofriday, err := builders.GoFridayStream(builders.MarkStreamConfig{ InputDir: absDir, SaveDir: tbsDir, Ext: ext, Sanitize: sanitize, }) if err != nil { fmt.Printf("---> gofriday.error: %s", err) return } //create the file watcher watcher := fs.Watch(fs.WatchConfig{ Path: absDir, }) watcher.React(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if ev, ok := data.(fsnotify.Event); ok { fmt.Printf("--> goFriday:File as changed: %+s\n", ev.String()) } }), true) // create the command runner set to run the args watcher.Bind(gofriday, true) flux.GoDefer("goFiday:kill", func() { <-c watcher.Close() }) }) }
func addJSWatchBuild(pm *PluginManager) { //these are internally used for js building pm.Add("jsWatchBuild", func(config *BuildConfig, options Plugins, c chan bool) { pwd, _ := os.Getwd() _, binName := filepath.Split(config.Package) binDir := filepath.Join(pwd, config.Bin) binfile := filepath.Join(binDir, binName) pkgs := append([]string{}, config.ClientPackage) packages, err := assets.GetAllPackageLists(pkgs) if err != nil { panic(err) } // packages = append(packages, pwd) fmt.Printf("--> Retrieved js package directories %s \n", config.Package) var clientdir string outputdir := filepath.Join(pwd, config.Client.StaticDir) if config.Client.Dir != "" { clientdir = filepath.Join(pwd, config.Client.Dir) } jsbuild := builders.JSLauncher(builders.JSBuildConfig{ Package: config.ClientPackage, Folder: outputdir, FileName: config.Client.Name, Tags: config.Client.BuildTags, Verbose: config.Client.UseVerbose, PackageDir: clientdir, }) jsbuild.React(func(root flux.Reactor, err error, _ interface{}) { if err != nil { fmt.Printf("--> Js.client.Build complete: Dir: %s \n -----> Error: %s \n", clientdir, err) } }, true) fmt.Printf("--> Initializing File Watcher using js package dependecies at %d\n", len(packages)) watcher := fs.WatchSet(fs.WatchSetConfig{ Path: packages, Validator: func(base string, info os.FileInfo) bool { if strings.Contains(base, ".git") { return false } if strings.Contains(base, binDir) || base == binDir { return false } if strings.Contains(base, binfile) || base == binfile { return false } if info != nil && info.IsDir() { return true } if filepath.Ext(base) != ".go" { return false } // log.Printf("allowed: %s", base) return true }, }) watcher.React(flux.SimpleMuxer(func(root flux.Reactor, data interface{}) { if ev, ok := data.(fsnotify.Event); ok { fmt.Printf("--> Client:File as changed: %+s\n", ev.String()) } }), true) watcher.Bind(jsbuild, true) jsbuild.Send(true) flux.GoDefer("jsWatchBuild:kill", func() { <-c //close our builders watcher.Close() jsbuild.Close() }) }) }
// GoRunnerWith calls `go run` everysingle time to the provided path once a signal is received func GoRunnerWith(cmd string) flux.Reactor { return flux.Reactive(flux.SimpleMuxer(func(root flux.Reactor, _ interface{}) { root.Reply(GoRun(cmd)) })) }