author | Alberto Bertogli
<albertito@blitiri.com.ar> 2020-06-04 20:54:33 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2020-06-04 21:08:07 UTC |
.gitignore | +3 | -0 |
LICENSE | +25 | -0 |
README.md | +51 | -0 |
css3fmt.go | +163 | -0 |
go.mod | +5 | -0 |
go.sum | +2 | -0 |
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89f2d6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +css3fmt +.* +!.gitignore diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b68719a --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +css3fmt is under the MIT licence, which is reproduced below (taken from +http://opensource.org/licenses/MIT). + +----- + +Copyright (c) Alberto Bertogli + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f46f77 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ + +# css3fmt + +[css3fmt](https://blitiri.com.ar/git/r/css3fmt) is an auto-formatter for +[CSS](https://en.wikipedia.org/wiki/Cascading_Style_Sheets) files. + +It is not particularly fancy or smart, but it is simple and can automatically +format most CSS files. + + +## Install + +css3fmt is written in Go. + +```sh +go get blitiri.com.ar/go/css3fmt +``` + + +## Editor integration + +### vim + +Put the following into your `.vimrc` file to auto-indent on save: + +```vim +function! CSSFormatBuffer() + let l:curw = winsaveview() + let l:tmpname = tempname() + call writefile(getline(1,'$'), l:tmpname) + let l:out = system("css3fmt " . l:tmpname) + call delete(l:tmpname) + if v:shell_error == 0 + try | silent undojoin | catch | endtry + silent %!css3fmt + else + echoerr l:out + endif + call winrestview(l:curw) + return v:shell_error == 0 +endfunction +autocmd filetype css + \ autocmd bufwritepre <buffer> call CSSFormatBuffer() +``` + + +## Contact + +If you have any questions, comments or patches please send them to +albertito@blitiri.com.ar. + diff --git a/css3fmt.go b/css3fmt.go new file mode 100644 index 0000000..e9ad4c5 --- /dev/null +++ b/css3fmt.go @@ -0,0 +1,163 @@ +// css3fmt is an auto-indenter/formatter for CSS. +// +// See https://blitiri.com.ar/git/r/css3fmt for more details. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/gorilla/css/scanner" +) + +var ( + rewrite = flag.Bool("w", false, + "Do not print reformatted sources to standard output."+ + " If a file's formatting is different from gofmt's,"+ + " overwrite it with the formatted version") +) + +func main() { + flag.Parse() + + if args := flag.Args(); len(args) > 0 { + for _, fname := range args { + f, err := os.Open(fname) + if err != nil { + fatalf("%s: %s\n", fname, err) + } + defer f.Close() + + s := indent(f) + if *rewrite { + err = ioutil.WriteFile(fname, []byte(s), 0660) + if err != nil { + fatalf("%s: %s\n", fname, err) + } + } else { + os.Stdout.WriteString(s) + } + } + } else { + os.Stdout.WriteString(indent(os.Stdin)) + } +} + +func fatalf(s string, a ...interface{}) { + fmt.Fprintf(os.Stderr, s, a...) + os.Exit(1) +} + +func indent(f *os.File) string { + buf, err := ioutil.ReadAll(f) + if err != nil { + fatalf("%s: error reading: %s\n", f.Name(), err) + } + + out := output{} + + s := scanner.New(string(buf)) +scan: + for { + t := s.Next() + switch t.Type { + case scanner.TokenEOF: + break scan + case scanner.TokenError: + fatalf("%s:%d:%d: error tokenizing: %s\n", + f.Name(), t.Line, t.Column, t.Value) + case scanner.TokenChar: + switch t.Value { + case "{": + out.emit("{\n") + out.indent++ + case "}": + out.indent-- + out.emit("}\n") + if out.indent == 0 { + out.emit("\n") + } + case ";": + if out.inFunc { + out.inFunc = false + out.indent-- + } + out.emit(";\n") + default: + out.emit(t.Value) + } + case scanner.TokenS: + if !strings.Contains(t.Value, "\n") { + out.emit(" ") + } else if out.inFunc { + // Respect newline within functions, as users know best how to + // break up arguments. + out.emit("\n") + } + out.afterEmptyLine = strings.Contains(t.Value, "\n\n") + case scanner.TokenComment: + out.emitComment(t.Value) + case scanner.TokenFunction: + out.emit(t.Value) + if !out.inFunc { + out.inFunc = true + out.indent++ + } + default: + out.emit(t.Value) + } + + //fmt.Printf("\n«%s»\n", t) + } + + return strings.Trim(out.buf.String(), "\n") + "\n" +} + +type output struct { + indent int + + afterN bool + afterEmptyLine bool + inFunc bool + + buf strings.Builder +} + +func (o *output) emit(s string) { + // Indent if we just came from a newline, UNLESS we're only printing a + // newline, to avoid trailing spaces. + if o.afterN && s != "\n" { + for i := 0; i < o.indent; i++ { + //o.buf.WriteString("‧‧‧‧") + o.buf.WriteString(" ") + } + } + + o.buf.WriteString(s) + o.afterN = strings.HasSuffix(s, "\n") + o.afterEmptyLine = false +} + +func (o *output) emitComment(s string) { + // We preserve empty newlines before comments, so they can be used to + // break long series of entries, or to group sections. + if o.afterEmptyLine { + o.emit("\n") + } + + // Emit the lines adjusting indentation for "* ". + lines := strings.Split(s, "\n") + for _, l := range lines { + if strings.HasPrefix(trimAllSp(l), "* ") { + l = " " + trimAllSp(l) + } + o.emit(l + "\n") + } +} + +func trimAllSp(s string) string { + return strings.Trim(s, " \t\r\n") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0543d44 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module blitiri.com.ar/go/css3fmt + +go 1.14 + +require github.com/gorilla/css v1.0.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6ed4c52 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=