From be1ee220327073d7304621b4386df9d6252bb837 Mon Sep 17 00:00:00 2001 From: spf13 Date: Fri, 16 May 2014 17:49:27 -0400 Subject: [PATCH] Proper integration of live reload with automatic injection --- commands/hugo.go | 33 +++++--- commands/server.go | 127 +++++------------------------- docs/layouts/chrome/includes.html | 1 - hugolib/site.go | 4 + livereload/connection.go | 56 +++++++++++++ livereload/hub.go | 56 +++++++++++++ livereload/livereload.go | 49 ++++++++++++ transform/livereloadinject.go | 19 +++++ 8 files changed, 224 insertions(+), 121 deletions(-) create mode 100644 livereload/connection.go create mode 100644 livereload/hub.go create mode 100644 livereload/livereload.go create mode 100644 transform/livereloadinject.go diff --git a/commands/hugo.go b/commands/hugo.go index 0a85a300f..e18da8722 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -15,6 +15,7 @@ package commands import ( "fmt" + "net/http" "os" "path/filepath" "runtime" @@ -26,6 +27,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugolib" + "github.com/spf13/hugo/livereload" "github.com/spf13/hugo/utils" "github.com/spf13/hugo/watcher" jww "github.com/spf13/jwalterweatherman" @@ -46,6 +48,7 @@ Complete documentation is available at http://hugo.spf13.com`, build() }, } + var hugoCmdV *cobra.Command var BuildWatch, Draft, UglyUrls, Verbose, Logging, VerboseLog, DisableRSS, DisableSitemap bool @@ -66,19 +69,19 @@ func AddCommands() { } func init() { - HugoCmd.PersistentFlags().BoolVarP(&Draft, "build-drafts", "D", false, "include content marked as draft") + HugoCmd.PersistentFlags().BoolVarP(&Draft, "buildDrafts", "D", false, "include content marked as draft") HugoCmd.PersistentFlags().BoolVar(&DisableRSS, "disableRSS", false, "Do not build RSS files") HugoCmd.PersistentFlags().BoolVar(&DisableSitemap, "disableSitemap", false, "Do not build Sitemap file") HugoCmd.PersistentFlags().StringVarP(&Source, "source", "s", "", "filesystem path to read files relative from") HugoCmd.PersistentFlags().StringVarP(&Destination, "destination", "d", "", "filesystem path to write files to") HugoCmd.PersistentFlags().StringVarP(&Theme, "theme", "t", "", "theme to use (located in /themes/THEMENAME/)") HugoCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") - HugoCmd.PersistentFlags().BoolVar(&UglyUrls, "uglyurls", false, "if true, use /filename.html instead of /filename/") - HugoCmd.PersistentFlags().StringVarP(&BaseUrl, "base-url", "b", "", "hostname (and path) to the root eg. http://spf13.com/") + HugoCmd.PersistentFlags().BoolVar(&UglyUrls, "uglyUrls", false, "if true, use /filename.html instead of /filename/") + HugoCmd.PersistentFlags().StringVarP(&BaseUrl, "baseUrl", "b", "", "hostname (and path) to the root eg. http://spf13.com/") HugoCmd.PersistentFlags().StringVar(&CfgFile, "config", "", "config file (default is path/config.yaml|json|toml)") HugoCmd.PersistentFlags().BoolVar(&Logging, "log", false, "Enable Logging") - HugoCmd.PersistentFlags().StringVar(&LogFile, "logfile", "", "Log File path (if set, logging enabled automatically)") - HugoCmd.PersistentFlags().BoolVar(&VerboseLog, "verboselog", false, "verbose logging") + HugoCmd.PersistentFlags().StringVar(&LogFile, "logFile", "", "Log File path (if set, logging enabled automatically)") + HugoCmd.PersistentFlags().BoolVar(&VerboseLog, "verboseLog", false, "verbose logging") HugoCmd.PersistentFlags().BoolVar(&nitro.AnalysisOn, "stepAnalysis", false, "display memory and timing of different steps of the program") HugoCmd.Flags().BoolVarP(&BuildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed") hugoCmdV = HugoCmd @@ -94,6 +97,7 @@ func InitializeConfig() { viper.RegisterAlias("taxonomies", "indexes") + viper.SetDefault("Watch", false) viper.SetDefault("MetadataFormat", "toml") viper.SetDefault("DisableRSS", false) viper.SetDefault("DisableSitemap", false) @@ -112,12 +116,13 @@ func InitializeConfig() { viper.SetDefault("Sitemap", hugolib.Sitemap{Priority: -1}) viper.SetDefault("PygmentsStyle", "monokai") viper.SetDefault("PygmentsUseClasses", false) + viper.SetDefault("DisableLiveReload", false) - if hugoCmdV.PersistentFlags().Lookup("build-drafts").Changed { + if hugoCmdV.PersistentFlags().Lookup("buildDrafts").Changed { viper.Set("BuildDrafts", Draft) } - if hugoCmdV.PersistentFlags().Lookup("uglyurls").Changed { + if hugoCmdV.PersistentFlags().Lookup("uglyUrls").Changed { viper.Set("UglyUrls", UglyUrls) } @@ -133,7 +138,7 @@ func InitializeConfig() { viper.Set("Verbose", Verbose) } - if hugoCmdV.PersistentFlags().Lookup("logfile").Changed { + if hugoCmdV.PersistentFlags().Lookup("logFile").Changed { viper.Set("LogFile", LogFile) } if BaseUrl != "" { @@ -323,16 +328,14 @@ func NewWatcher(port int) error { fmt.Print("Static file changed, syncing\n\n") utils.StopOnErr(copyStatic(), fmt.Sprintf("Error copying static files to %s", helpers.AbsPathify(viper.GetString("PublishDir")))) - // Tell livereload a js file changed to force a hard refresh - wsHub.broadcast <- []byte(`{"command":"reload","path":"/x.js","originalPath":"","liveCSS":true}`) + livereload.ForceRefresh() } if dynamic_changed { fmt.Print("Change detected, rebuilding site\n\n") utils.StopOnErr(buildSite(true)) - // Tell livereload a js file changed to force a hard refresh - wsHub.broadcast <- []byte(`{"command":"reload","path":"/x.js","originalPath":"","liveCSS":true}`) + livereload.ForceRefresh() } case err := <-watcher.Error: if err != nil { @@ -343,6 +346,12 @@ func NewWatcher(port int) error { }() if port > 0 { + if !viper.GetBool("DisableLiveReload") { + livereload.Initialize() + http.HandleFunc("/livereload.js", livereload.ServeJS) + http.HandleFunc("/livereload", livereload.Handler) + } + go serve(port) } diff --git a/commands/server.go b/commands/server.go index 4d47bdb9f..7f0b9585e 100644 --- a/commands/server.go +++ b/commands/server.go @@ -14,7 +14,6 @@ package commands import ( - "bytes" "fmt" "net" "net/http" @@ -22,9 +21,6 @@ import ( "strconv" "strings" - "github.com/gorilla/websocket" - //"code.google.com/p/go.net/websocket" - "github.com/spf13/cobra" "github.com/spf13/hugo/helpers" jww "github.com/spf13/jwalterweatherman" @@ -34,12 +30,9 @@ import ( var serverPort int var serverWatch bool var serverAppend bool +var disableLiveReload bool -func init() { - serverCmd.Flags().IntVarP(&serverPort, "port", "p", 1313, "port to run the server on") - serverCmd.Flags().BoolVarP(&serverWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed") - serverCmd.Flags().BoolVarP(&serverAppend, "append-port", "", true, "append port to baseurl") -} +//var serverCmdV *cobra.Command var serverCmd = &cobra.Command{ Use: "server", @@ -47,7 +40,15 @@ var serverCmd = &cobra.Command{ Long: `Hugo is able to run it's own high performance web server. Hugo will render all the files defined in the source directory and Serve them up.`, - Run: server, + //Run: server, +} + +func init() { + serverCmd.Flags().IntVarP(&serverPort, "port", "p", 1313, "port to run the server on") + serverCmd.Flags().BoolVarP(&serverWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed") + serverCmd.Flags().BoolVarP(&serverAppend, "appendPort", "", true, "append port to baseurl") + serverCmd.Flags().BoolVar(&disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild") + serverCmd.Run = server } func server(cmd *cobra.Command, args []string) { @@ -57,6 +58,14 @@ func server(cmd *cobra.Command, args []string) { BaseUrl = "http://localhost" } + if cmd.Flags().Lookup("disableLiveReload").Changed { + viper.Set("DisableLiveReload", disableLiveReload) + } + + if serverWatch { + viper.Set("Watch", true) + } + if !strings.HasPrefix(BaseUrl, "http://") { BaseUrl = "http://" + BaseUrl } @@ -96,111 +105,13 @@ func server(cmd *cobra.Command, args []string) { func serve(port int) { jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("PublishDir"))) - jww.FEEDBACK.Printf("Web Server is available at %s\n", viper.GetString("BaseUrl")) - fmt.Println("Press ctrl+c to stop") http.Handle("/", http.FileServer(http.Dir(helpers.AbsPathify(viper.GetString("PublishDir"))))) - go wsHub.run() - http.HandleFunc("/livereload", wsHandler) - err := http.ListenAndServe(":"+strconv.Itoa(port), nil) if err != nil { jww.ERROR.Printf("Error: %s\n", err.Error()) os.Exit(1) } } - -type hub struct { - // Registered connections. - connections map[*connection]bool - - // Inbound messages from the connections. - broadcast chan []byte - - // Register requests from the connections. - register chan *connection - - // Unregister requests from connections. - unregister chan *connection -} - -var wsHub = hub{ - broadcast: make(chan []byte), - register: make(chan *connection), - unregister: make(chan *connection), - connections: make(map[*connection]bool), -} - -func (h *hub) run() { - for { - select { - case c := <-h.register: - h.connections[c] = true - case c := <-h.unregister: - delete(h.connections, c) - close(c.send) - case m := <-h.broadcast: - for c := range h.connections { - select { - case c.send <- m: - default: - delete(h.connections, c) - close(c.send) - } - } - } - } -} - -type connection struct { - // The websocket connection. - ws *websocket.Conn - - // Buffered channel of outbound messages. - send chan []byte -} - -func (c *connection) reader() { - for { - _, message, err := c.ws.ReadMessage() - if err != nil { - break - } - fmt.Println(string(message)) - switch true { - case bytes.Contains(message, []byte(`"command":"hello"`)): - wsHub.broadcast <- []byte(`{ - "command": "hello", - "protocols": [ "http://livereload.com/protocols/official-7" ], - "serverName": "Hugo" - }`) - } - } - c.ws.Close() -} - -func (c *connection) writer() { - for message := range c.send { - err := c.ws.WriteMessage(websocket.TextMessage, message) - if err != nil { - break - } - } - c.ws.Close() -} - -var upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024} - -func wsHandler(w http.ResponseWriter, r *http.Request) { - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - c := &connection{send: make(chan []byte, 256), ws: ws} - wsHub.register <- c - defer func() { wsHub.unregister <- c }() - go c.writer() - c.reader() -} diff --git a/docs/layouts/chrome/includes.html b/docs/layouts/chrome/includes.html index dbc240d76..51ed6cb97 100644 --- a/docs/layouts/chrome/includes.html +++ b/docs/layouts/chrome/includes.html @@ -3,6 +3,5 @@ - diff --git a/hugolib/site.go b/hugolib/site.go index e77d48563..8948a1b29 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -864,6 +864,10 @@ func (s *Site) render(d interface{}, out string, layouts ...string) (err error) transformLinks = append(transformLinks, absURL...) } + if viper.GetBool("watch") && !viper.GetBool("DisableLiveReload") { + transformLinks = append(transformLinks, transform.LiveReloadInject) + } + transformer := transform.NewChain(transformLinks...) var renderBuffer *bytes.Buffer diff --git a/livereload/connection.go b/livereload/connection.go new file mode 100644 index 000000000..70a7e6d5d --- /dev/null +++ b/livereload/connection.go @@ -0,0 +1,56 @@ +// Copyright © 2014 Steve Francia . +// +// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 livereload + +import ( + "bytes" + + "github.com/gorilla/websocket" +) + +type connection struct { + // The websocket connection. + ws *websocket.Conn + + // Buffered channel of outbound messages. + send chan []byte +} + +func (c *connection) reader() { + for { + _, message, err := c.ws.ReadMessage() + if err != nil { + break + } + switch true { + case bytes.Contains(message, []byte(`"command":"hello"`)): + wsHub.broadcast <- []byte(`{ + "command": "hello", + "protocols": [ "http://livereload.com/protocols/official-7" ], + "serverName": "Hugo" + }`) + } + } + c.ws.Close() +} + +func (c *connection) writer() { + for message := range c.send { + err := c.ws.WriteMessage(websocket.TextMessage, message) + if err != nil { + break + } + } + c.ws.Close() +} diff --git a/livereload/hub.go b/livereload/hub.go new file mode 100644 index 000000000..46afbcbc3 --- /dev/null +++ b/livereload/hub.go @@ -0,0 +1,56 @@ +// Copyright © 2014 Steve Francia . +// +// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 livereload + +type hub struct { + // Registered connections. + connections map[*connection]bool + + // Inbound messages from the connections. + broadcast chan []byte + + // Register requests from the connections. + register chan *connection + + // Unregister requests from connections. + unregister chan *connection +} + +var wsHub = hub{ + broadcast: make(chan []byte), + register: make(chan *connection), + unregister: make(chan *connection), + connections: make(map[*connection]bool), +} + +func (h *hub) run() { + for { + select { + case c := <-h.register: + h.connections[c] = true + case c := <-h.unregister: + delete(h.connections, c) + close(c.send) + case m := <-h.broadcast: + for c := range h.connections { + select { + case c.send <- m: + default: + delete(h.connections, c) + close(c.send) + } + } + } + } +} diff --git a/livereload/livereload.go b/livereload/livereload.go new file mode 100644 index 000000000..9fb24696d --- /dev/null +++ b/livereload/livereload.go @@ -0,0 +1,49 @@ +// Copyright © 2014 Steve Francia . +// +// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 livereload + +import ( + "net/http" + + "github.com/gorilla/websocket" +) + +var upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024} + +func Handler(w http.ResponseWriter, r *http.Request) { + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + c := &connection{send: make(chan []byte, 256), ws: ws} + wsHub.register <- c + defer func() { wsHub.unregister <- c }() + go c.writer() + c.reader() +} + +func Initialize() { + go wsHub.run() +} + +func ForceRefresh() { + // Tell livereload a js file changed to force a hard refresh + wsHub.broadcast <- []byte(`{"command":"reload","path":"/x.js","originalPath":"","liveCSS":true}`) +} + +func ServeJS(w http.ResponseWriter, r *http.Request) { + w.Write(livereloadJS) +} + +var livereloadJS []byte = []byte(`(function(){var e={},t={},n={},r={},i={},s={},o={},u={},a={};var f,l,c,h,p=[].indexOf||function(e){for(var t=0,n=this.length;t=0){this.protocol=7}else if(p.call(r.protocols,f)>=0){this.protocol=6}else{throw new h("no supported protocols found")}}return this.handlers.connected(this.protocol)}else if(this.protocol===6){r=JSON.parse(e);if(!r.length){throw new h("protocol 6 messages must be arrays")}t=r[0],i=r[1];if(t!=="refresh"){throw new h("unknown protocol 6 command")}return this.handlers.message({command:"reload",path:i.path,liveCSS:(s=i.apply_css_live)!=null?s:true})}else{r=this._parseMessage(e,["reload","alert"]);return this.handlers.message(r)}}catch(o){n=o;if(n instanceof h){return this.handlers.error(n)}else{throw n}}};e.prototype._parseMessage=function(e,t){var n,r,i;try{r=JSON.parse(e)}catch(s){n=s;throw new h("unparsable JSON",e)}if(!r.command){throw new h('missing "command" key',e)}if(i=r.command,p.call(t,i)<0){throw new h("invalid command '"+r.command+"', only valid commands are: "+t.join(", ")+")",e)}return r};return e}();var d,f,l,c,v,m;m=e,c=m.Parser,f=m.PROTOCOL_6,l=m.PROTOCOL_7;v="2.0.8";t.Connector=d=function(){function e(e,t,n,r){var i=this;this.options=e;this.WebSocket=t;this.Timer=n;this.handlers=r;this._uri="ws://"+this.options.host+":"+this.options.port+"/livereload";this._nextDelay=this.options.mindelay;this._connectionDesired=false;this.protocol=0;this.protocolParser=new c({connected:function(e){i.protocol=e;i._handshakeTimeout.stop();i._nextDelay=i.options.mindelay;i._disconnectionReason="broken";return i.handlers.connected(e)},error:function(e){i.handlers.error(e);return i._closeOnError()},message:function(e){return i.handlers.message(e)}});this._handshakeTimeout=new n(function(){if(!i._isSocketConnected()){return}i._disconnectionReason="handshake-timeout";return i.socket.close()});this._reconnectTimer=new n(function(){if(!i._connectionDesired){return}return i.connect()});this.connect()}e.prototype._isSocketConnected=function(){return this.socket&&this.socket.readyState===this.WebSocket.OPEN};e.prototype.connect=function(){var e=this;this._connectionDesired=true;if(this._isSocketConnected()){return}this._reconnectTimer.stop();this._disconnectionReason="cannot-connect";this.protocolParser.reset();this.handlers.connecting();this.socket=new this.WebSocket(this._uri);this.socket.onopen=function(t){return e._onopen(t)};this.socket.onclose=function(t){return e._onclose(t)};this.socket.onmessage=function(t){return e._onmessage(t)};return this.socket.onerror=function(t){return e._onerror(t)}};e.prototype.disconnect=function(){this._connectionDesired=false;this._reconnectTimer.stop();if(!this._isSocketConnected()){return}this._disconnectionReason="manual";return this.socket.close()};e.prototype._scheduleReconnection=function(){if(!this._connectionDesired){return}if(!this._reconnectTimer.running){this._reconnectTimer.start(this._nextDelay);return this._nextDelay=Math.min(this.options.maxdelay,this._nextDelay*2)}};e.prototype.sendCommand=function(e){if(this.protocol==null){return}return this._sendCommand(e)};e.prototype._sendCommand=function(e){return this.socket.send(JSON.stringify(e))};e.prototype._closeOnError=function(){this._handshakeTimeout.stop();this._disconnectionReason="error";return this.socket.close()};e.prototype._onopen=function(e){var t;this.handlers.socketConnected();this._disconnectionReason="handshake-failed";t={command:"hello",protocols:[f,l]};t.ver=v;if(this.options.ext){t.ext=this.options.ext}if(this.options.extver){t.extver=this.options.extver}if(this.options.snipver){t.snipver=this.options.snipver}this._sendCommand(t);return this._handshakeTimeout.start(this.options.handshake_timeout)};e.prototype._onclose=function(e){this.protocol=0;this.handlers.disconnected(this._disconnectionReason,this._nextDelay);return this._scheduleReconnection()};e.prototype._onerror=function(e){};e.prototype._onmessage=function(e){return this.protocolParser.process(e.data)};return e}();var g;g={bind:function(e,t,n){if(e.addEventListener){return e.addEventListener(t,n,false)}else if(e.attachEvent){e[t]=1;return e.attachEvent("onpropertychange",function(e){if(e.propertyName===t){return n()}})}else{throw new Error("Attempt to attach custom event "+t+" to something which isn't a DOMElement")}},fire:function(e,t){var n;if(e.addEventListener){n=document.createEvent("HTMLEvents");n.initEvent(t,true,true);return document.dispatchEvent(n)}else if(e.attachEvent){if(e[t]){return e[t]++}}else{throw new Error("Attempt to fire custom event "+t+" on something which isn't a DOMElement")}}};n.bind=g.bind;n.fire=g.fire;var y;r=y=function(){function e(e,t){this.window=e;this.host=t}e.identifier="less";e.version="1.0";e.prototype.reload=function(e,t){if(this.window.less&&this.window.less.refresh){if(e.match(/\.less$/i)){return this.reloadLess(e)}if(t.originalPath.match(/\.less$/i)){return this.reloadLess(t.originalPath)}}return false};e.prototype.reloadLess=function(e){var t,n,r,i;n=function(){var e,n,r,i;r=document.getElementsByTagName("link");i=[];for(e=0,n=r.length;e1){s.set(n[0].replace(/-/g,"_"),n.slice(1).join("="))}}}return s}}return null};var E,S,x,T,N,C,k;k=function(e){var t,n,r;if((n=e.indexOf("#"))>=0){t=e.slice(n);e=e.slice(0,n)}else{t=""}if((n=e.indexOf("?"))>=0){r=e.slice(n);e=e.slice(0,n)}else{r=""}return{url:e,params:r,hash:t}};T=function(e){var t;e=k(e).url;if(e.indexOf("file://")===0){t=e.replace(/^file:\/\/(localhost)?/,"")}else{t=e.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//,"/")}return decodeURIComponent(t)};C=function(e,t,n){var r,i,s,o,u;r={score:0};for(o=0,u=t.length;or.score){r={object:i,score:s}}}if(r.score>0){return r}else{return null}};x=function(e,t){var n,r,i,s;e=e.replace(/^\/+/,"").toLowerCase();t=t.replace(/^\/+/,"").toLowerCase();if(e===t){return 1e4}n=e.split("/").reverse();r=t.split("/").reverse();s=Math.min(n.length,r.length);i=0;while(i0};E=[{selector:"background",styleNames:["backgroundImage"]},{selector:"border",styleNames:["borderImage","webkitBorderImage","MozBorderImage"]}];o.Reloader=S=function(){function e(e,t,n){this.window=e;this.console=t;this.Timer=n;this.document=this.window.document;this.importCacheWaitPeriod=200;this.plugins=[]}e.prototype.addPlugin=function(e){return this.plugins.push(e)};e.prototype.analyze=function(e){return results};e.prototype.reload=function(e,t){var n,r,i,s,o,u;this.options=t;if((o=(r=this.options).stylesheetReloadTimeout)==null){r.stylesheetReloadTimeout=15e3}u=this.plugins;for(i=0,s=u.length;i tag");return}this.reloader=new S(this.window,this.console,b);this.connector=new d(this.options,this.WebSocket,b,{connecting:function(){},socketConnected:function(){},connected:function(e){var n;if(typeof (n=t.listeners).connect==="function"){n.connect()}t.log("LiveReload is connected to "+t.options.host+":"+t.options.port+" (protocol v"+e+").");return t.analyze()},error:function(e){if(e instanceof h){return console.log(""+e.message+".")}else{return console.log("LiveReload internal error: "+e.message)}},disconnected:function(e,n){var r;if(typeof (r=t.listeners).disconnect==="function"){r.disconnect()}switch(e){case"cannot-connect":return t.log("LiveReload cannot connect to "+t.options.host+":"+t.options.port+", will retry in "+n+" sec.");case"broken":return t.log("LiveReload disconnected from "+t.options.host+":"+t.options.port+", reconnecting in "+n+" sec.");case"handshake-timeout":return t.log("LiveReload cannot connect to "+t.options.host+":"+t.options.port+" (handshake timeout), will retry in "+n+" sec.");case"handshake-failed":return t.log("LiveReload cannot connect to "+t.options.host+":"+t.options.port+" (handshake failed), will retry in "+n+" sec.");case"manual":break;case"error":break;default:return t.log("LiveReload disconnected from "+t.options.host+":"+t.options.port+" ("+e+"), reconnecting in "+n+" sec.")}},message:function(e){switch(e.command){case"reload":return t.performReload(e);case"alert":return t.performAlert(e)}}})}e.prototype.on=function(e,t){return this.listeners[e]=t};e.prototype.log=function(e){return this.console.log(""+e)};e.prototype.performReload=function(e){var t,n;this.log("LiveReload received reload request: "+JSON.stringify(e,null,2));return this.reloader.reload(e.path,{liveCSS:(t=e.liveCSS)!=null?t:true,liveImg:(n=e.liveImg)!=null?n:true,originalPath:e.originalPath||"",overrideURL:e.overrideURL||"",serverURL:"http://"+this.options.host+":"+this.options.port})};e.prototype.performAlert=function(e){return alert(e.message)};e.prototype.shutDown=function(){var e;this.connector.disconnect();this.log("LiveReload disconnected.");return typeof (e=this.listeners).shutdown==="function"?e.shutdown():void 0};e.prototype.hasPlugin=function(e){return!!this.pluginIdentifiers[e]};e.prototype.addPlugin=function(e){var t,n=this;if(this.hasPlugin(e.identifier)){return}this.pluginIdentifiers[e.identifier]=true;t=new e(this.window,{_livereload:this,_reloader:this.reloader,_connector:this.connector,console:this.console,Timer:b,generateCacheBustUrl:function(e){return n.reloader.generateCacheBustUrl(e)}});this.plugins.push(t);this.reloader.addPlugin(t)};e.prototype.analyze=function(){var e,t,n,r,i,s;if(!(this.connector.protocol>=7)){return}n={};s=this.plugins;for(r=0,i=s.length;r") + replace := []byte(``) + newcontent := bytes.Replace(content, match, replace, -1) + + if len(newcontent) == len(content) { + match := []byte("") + newcontent = bytes.Replace(content, match, replace, -1) + } + + return newcontent +}