pax_global_header 0000666 0000000 0000000 00000000064 12567623333 0014524 g ustar 00root root 0000000 0000000 52 comment=c5c95ec357c8235fbd7f34e8c843d36783f3fad9 douceur-0.2.0/ 0000775 0000000 0000000 00000000000 12567623333 0013171 5 ustar 00root root 0000000 0000000 douceur-0.2.0/.gitignore 0000664 0000000 0000000 00000000021 12567623333 0015152 0 ustar 00root root 0000000 0000000 douceur test.html douceur-0.2.0/.travis.yml 0000664 0000000 0000000 00000000116 12567623333 0015300 0 ustar 00root root 0000000 0000000 --- language: go go: - 1.3 - tip script: - go build - go test ./... douceur-0.2.0/CHANGELOG.md 0000664 0000000 0000000 00000000437 12567623333 0015006 0 ustar 00root root 0000000 0000000 # Douceur Changelog ### Douceur 0.2.0 _(August 27, 2015)_ - Applied vet and lint on all source code. - [BREAKING CHANGE] Some const were renamed, and even unexported. ### Douceur 0.1.0 _(April 15, 2015)_ - First release. Fetching of external stylesheets is the main missing feature. douceur-0.2.0/LICENSE 0000664 0000000 0000000 00000002074 12567623333 0014201 0 ustar 00root root 0000000 0000000 The MIT License (MIT) Copyright (c) 2015 Aymerick JEHANNE 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. douceur-0.2.0/README.md 0000664 0000000 0000000 00000006310 12567623333 0014450 0 ustar 00root root 0000000 0000000 # douceur [](http://travis-ci.org/aymerick/douceur) A simple CSS parser and inliner in Golang.  Parser is vaguely inspired by [CSS Syntax Module Level 3](http://www.w3.org/TR/css3-syntax) and [corresponding JS parser](https://github.com/tabatkins/parse-css). Inliner only parses CSS defined in HTML document, it *DOES NOT* fetch external stylesheets (for now). Inliner inserts additional attributes when possible, for example: ```html
Inline me !
` ``` Becomes: ```htmlInline me !
` ``` The `bgcolor` attribute is inserted, in addition to the inlined `background-color` style. ## Tool usage Install tool: $ go install github.com/aymerick/douceur Parse a CSS file and display result: $ douceur parse inputfile.css Inline CSS in an HTML document and display result: $ douceur inline inputfile.html ## Library usage Fetch package: $ go get github.com/aymerick/douceur ### Parse CSS ```go package main import ( "fmt" "github.com/aymerick/douceur/parser" ) func main() { input := `body { /* D4rK s1T3 */ background-color: black; } p { /* Try to read that ! HAHA! */ color: red; /* L O L */ } ` stylesheet, err := parser.Parse(input) if err != nil { panic("Please fill a bug :)") } fmt.Print(stylesheet.String()) } ``` Displays: ```css body { background-color: black; } p { color: red; } ``` ### Inline HTML ```go package main import ( "fmt" "github.com/aymerick/douceur/inliner" ) func main() { input := `Inline me please!
` html, err := inliner.Inline(input) if err != nil { panic("Please fill a bug :)") } fmt.Print(html) } ``` Displays: ```cssInline me please!
``` ## Test go test ./... -v ## Dependencies - Parser uses [Gorilla CSS3 tokenizer](https://github.com/gorilla/css). - Inliner uses [goquery](github.com/PuerkitoBio/goquery) to manipulate HTML. ## Similar projects - [premailer](https://github.com/premailer/premailer) - [roadie](https://github.com/Mange/roadie) douceur-0.2.0/css/ 0000775 0000000 0000000 00000000000 12567623333 0013761 5 ustar 00root root 0000000 0000000 douceur-0.2.0/css/declaration.go 0000664 0000000 0000000 00000002750 12567623333 0016601 0 ustar 00root root 0000000 0000000 package css import "fmt" // Declaration represents a parsed style property type Declaration struct { Property string Value string Important bool } // NewDeclaration instanciates a new Declaration func NewDeclaration() *Declaration { return &Declaration{} } // Returns string representation of the Declaration func (decl *Declaration) String() string { return decl.StringWithImportant(true) } // StringWithImportant returns string representation with optional !important part func (decl *Declaration) StringWithImportant(option bool) string { result := fmt.Sprintf("%s: %s", decl.Property, decl.Value) if option && decl.Important { result += " !important" } result += ";" return result } // Equal returns true if both Declarations are equals func (decl *Declaration) Equal(other *Declaration) bool { return (decl.Property == other.Property) && (decl.Value == other.Value) && (decl.Important == other.Important) } // // DeclarationsByProperty // // DeclarationsByProperty represents sortable style declarations type DeclarationsByProperty []*Declaration // Implements sort.Interface func (declarations DeclarationsByProperty) Len() int { return len(declarations) } // Implements sort.Interface func (declarations DeclarationsByProperty) Swap(i, j int) { declarations[i], declarations[j] = declarations[j], declarations[i] } // Implements sort.Interface func (declarations DeclarationsByProperty) Less(i, j int) bool { return declarations[i].Property < declarations[j].Property } douceur-0.2.0/css/rule.go 0000664 0000000 0000000 00000011527 12567623333 0015265 0 ustar 00root root 0000000 0000000 package css import ( "fmt" "strings" ) const ( indentSpace = 2 ) // RuleKind represents a Rule kind type RuleKind int // Rule kinds const ( QualifiedRule RuleKind = iota AtRule ) // At Rules than have Rules inside their block instead of Declarations var atRulesWithRulesBlock = []string{ "@document", "@font-feature-values", "@keyframes", "@media", "@supports", } // Rule represents a parsed CSS rule type Rule struct { Kind RuleKind // At Rule name (eg: "@media") Name string // Raw prelude Prelude string // Qualified Rule selectors parsed from prelude Selectors []string // Style properties Declarations []*Declaration // At Rule embedded rules Rules []*Rule // Current rule embedding level EmbedLevel int } // NewRule instanciates a new Rule func NewRule(kind RuleKind) *Rule { return &Rule{ Kind: kind, } } // Returns string representation of rule kind func (kind RuleKind) String() string { switch kind { case QualifiedRule: return "Qualified Rule" case AtRule: return "At Rule" default: return "WAT" } } // EmbedsRules returns true if this rule embeds another rules func (rule *Rule) EmbedsRules() bool { if rule.Kind == AtRule { for _, atRuleName := range atRulesWithRulesBlock { if rule.Name == atRuleName { return true } } } return false } // Equal returns true if both rules are equals func (rule *Rule) Equal(other *Rule) bool { if (rule.Kind != other.Kind) || (rule.Prelude != other.Prelude) || (rule.Name != other.Name) { return false } if (len(rule.Selectors) != len(other.Selectors)) || (len(rule.Declarations) != len(other.Declarations)) || (len(rule.Rules) != len(other.Rules)) { return false } for i, sel := range rule.Selectors { if sel != other.Selectors[i] { return false } } for i, decl := range rule.Declarations { if !decl.Equal(other.Declarations[i]) { return false } } for i, rule := range rule.Rules { if !rule.Equal(other.Rules[i]) { return false } } return true } // Diff returns a string representation of rules differences func (rule *Rule) Diff(other *Rule) []string { result := []string{} if rule.Kind != other.Kind { result = append(result, fmt.Sprintf("Kind: %s | %s", rule.Kind.String(), other.Kind.String())) } if rule.Prelude != other.Prelude { result = append(result, fmt.Sprintf("Prelude: \"%s\" | \"%s\"", rule.Prelude, other.Prelude)) } if rule.Name != other.Name { result = append(result, fmt.Sprintf("Name: \"%s\" | \"%s\"", rule.Name, other.Name)) } if len(rule.Selectors) != len(other.Selectors) { result = append(result, fmt.Sprintf("Selectors: %v | %v", strings.Join(rule.Selectors, ", "), strings.Join(other.Selectors, ", "))) } else { for i, sel := range rule.Selectors { if sel != other.Selectors[i] { result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i])) } } } if len(rule.Declarations) != len(other.Declarations) { result = append(result, fmt.Sprintf("Declarations Nb: %d | %d", len(rule.Declarations), len(other.Declarations))) } else { for i, decl := range rule.Declarations { if !decl.Equal(other.Declarations[i]) { result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String())) } } } if len(rule.Rules) != len(other.Rules) { result = append(result, fmt.Sprintf("Rules Nb: %d | %d", len(rule.Rules), len(other.Rules))) } else { for i, rule := range rule.Rules { if !rule.Equal(other.Rules[i]) { result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String())) } } } return result } // Returns the string representation of a rule func (rule *Rule) String() string { result := "" if rule.Kind == QualifiedRule { for i, sel := range rule.Selectors { if i != 0 { result += ", " } result += sel } } else { // AtRule result += fmt.Sprintf("%s", rule.Name) if rule.Prelude != "" { if result != "" { result += " " } result += fmt.Sprintf("%s", rule.Prelude) } } if (len(rule.Declarations) == 0) && (len(rule.Rules) == 0) { result += ";" } else { result += " {\n" if rule.EmbedsRules() { for _, subRule := range rule.Rules { result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String()) } } else { for _, decl := range rule.Declarations { result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String()) } } result += fmt.Sprintf("%s}", rule.indentEndBlock()) } return result } // Returns identation spaces for declarations and rules func (rule *Rule) indent() string { result := "" for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ { result += " " } return result } // Returns identation spaces for end of block character func (rule *Rule) indentEndBlock() string { result := "" for i := 0; i < (rule.EmbedLevel * indentSpace); i++ { result += " " } return result } douceur-0.2.0/css/stylesheet.go 0000664 0000000 0000000 00000000671 12567623333 0016505 0 ustar 00root root 0000000 0000000 package css // Stylesheet represents a parsed stylesheet type Stylesheet struct { Rules []*Rule } // NewStylesheet instanciate a new Stylesheet func NewStylesheet() *Stylesheet { return &Stylesheet{} } // Returns string representation of the Stylesheet func (sheet *Stylesheet) String() string { result := "" for _, rule := range sheet.Rules { if result != "" { result += "\n" } result += rule.String() } return result } douceur-0.2.0/douceur.go 0000664 0000000 0000000 00000002726 12567623333 0015175 0 ustar 00root root 0000000 0000000 package main import ( "flag" "fmt" "io/ioutil" "os" "github.com/aymerick/douceur/inliner" "github.com/aymerick/douceur/parser" ) const ( // Version is package version Version = "0.2.0" ) var ( flagVersion bool ) func init() { flag.BoolVar(&flagVersion, "version", false, "Display version") } func main() { flag.Parse() if flagVersion { fmt.Println(Version) os.Exit(0) } args := flag.Args() if len(args) == 0 { fmt.Println("No command supplied") os.Exit(1) } switch args[0] { case "parse": if len(args) < 2 { fmt.Println("Missing file path") os.Exit(1) } parseCSS(args[1]) case "inline": if len(args) < 2 { fmt.Println("Missing file path") os.Exit(1) } inlineCSS(args[1]) default: fmt.Println("Unexpected command: ", args[0]) os.Exit(1) } } // parse and display CSS file func parseCSS(filePath string) { input := readFile(filePath) stylesheet, err := parser.Parse(string(input)) if err != nil { fmt.Println("Parsing error: ", err) os.Exit(1) } fmt.Println(stylesheet.String()) } // inlines CSS into HTML and display result func inlineCSS(filePath string) { input := readFile(filePath) output, err := inliner.Inline(string(input)) if err != nil { fmt.Println("Inlining error: ", err) os.Exit(1) } fmt.Println(output) } func readFile(filePath string) []byte { file, err := ioutil.ReadFile(filePath) if err != nil { fmt.Println("Failed to open file: ", filePath, err) os.Exit(1) } return file } douceur-0.2.0/douceur.png 0000664 0000000 0000000 00000015167 12567623333 0015357 0 ustar 00root root 0000000 0000000 PNG IHDR P P gAMA a cHRM z&