Skip to content

Commit

Permalink
Merge pull request #6 from cmuench/feature/extension-based-config
Browse files Browse the repository at this point in the history
New more flexible configuration format [BREAKING CHANGE]
  • Loading branch information
cmuench authored Dec 30, 2020
2 parents 40f9680 + 1322817 commit 9f746c0
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 152 deletions.
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# inotify-proxy

This tools helps to detect changed files in Docker containers.
If a file is changed from hostsystem a file watcher inside the container detects the change
and triggers a inotify event.
If a file is changed from host system a file watcher inside the container detects the change
and triggers an inotify event.

## Purpose

Expand Down Expand Up @@ -37,12 +37,17 @@ Example config:

---
watch:
- dir: /tmp/watch1
- dir: /tmp/watch2
profile: magento2

- directory: /tmp/watch1
profile: magento2

- dir: /tmp/watch2
profile: sass

- dir: /tmp/watch3
extensions: [.css, .html]

The profile setting is optional.
The config loading can be skiped by adding the option `-no-config`.
The config loading can be skipped by adding the option `-no-config`.

## Supported Profiles

Expand Down
70 changes: 48 additions & 22 deletions inotify-proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,38 +25,64 @@ func main() {
c := config.Config{}

if !*noConfig {
includedDirectories = loadConfig(c, includedDirectories, profilePtr)
}
if util.FileExists("inotify-proxy.yaml") {

// If no argument is defined, the current directory is used
if len(includedDirectories) == 0 {
includedDirectories = append(includedDirectories, ".")
}
r, err := os.Open("inotify-proxy.yaml")

color.Style{color.FgCyan, color.OpBold}.Println("PROFILE: " + *profilePtr)
color.Style{color.FgCyan, color.OpBold}.Println("DIRECTORIES: " + strings.Join(includedDirectories, ","))
if err != nil {
color.Errorf("cannot read file: %v\n", err)
os.Exit(1)
}

watcher.Watch(includedDirectories, *sleepPtr, *profilePtr)
}
defer r.Close()

func loadConfig(c config.Config, includedDirectories []string, profilePtr *string) []string {
if util.FileExists("inotify-proxy.yaml") {
color.Info.Println("load config")
c, err := config.ReadFile("inotify-proxy.yaml");
c, err = config.Read(r)

if err != nil {
color.Errorf("error: Invalid config provided.\n")
os.Exit(1)
if err != nil {
color.Errorf("cannot read config: %v\n", err)
}

if c.OldGlobalProfile != nil {
color.Errorf("You are using the old configuration format. Please use the new configuration version.\n")
color.Print("\nPlease refer: https://github.com/cmuench/inotify-proxy/blob/master/README.md#config\n")
os.Exit(1)
}
}
}

for _, watch := range c.Watch {
includedDirectories = append(includedDirectories, watch.Dir)
if len(includedDirectories) > 0 {
for _, includedDirectory := range includedDirectories {
c.Entries = append(c.Entries, config.WatchEntry{
Directory: includedDirectory,
Extensions: nil,
Profile: profilePtr,
})
}
}

if c.Profile != "" {
*profilePtr = c.Profile
// If no argument is defined, the current directory is used
if len(c.Entries) == 0 {
c.AddEntry(config.WatchEntry{
Directory: ".",
Extensions: nil,
Profile: profilePtr,
})
}

color.Style{color.FgMagenta, color.OpBold}.Println("Watching ...")
color.Style{color.FgWhite}.Println(strings.Repeat("-", 80))

for _, e := range c.Entries {
color.Style{color.FgCyan, color.OpBold}.Printf("Directory: %s\n", e.Directory)
if *e.Profile != "" {
color.Style{color.FgCyan, color.OpBold}.Printf("Profile: %s\n", *e.Profile)
}
if len(e.Extensions) > 0 {
color.Style{color.FgCyan, color.OpBold}.Printf("Extensions: %s\n", e.Extensions)
}

color.Style{color.FgWhite}.Println(strings.Repeat("-", 80))
}

return includedDirectories
watcher.Watch(c, *sleepPtr)
}
36 changes: 27 additions & 9 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,43 @@ package config

import (
"gopkg.in/yaml.v3"
"io"
"io/ioutil"
)

type Watch struct {
Dir string `yaml:"dir"`
type Config struct {
Entries []WatchEntry `yaml:"watch"`

OldGlobalProfile *string `yaml:"profile"`
}

type Config struct {
Watch []Watch `yaml:"watch"`
Profile string `yaml:"profile"`
type WatchEntry struct {
Directory string `yaml:"directory"`
Extensions []string `yaml:"extensions"`
Profile *string `yaml:"profile"`
}

func (c *Config) AddEntry(e WatchEntry) {
c.Entries = append(c.Entries, e)
}

func (c *Config) GetEntryByDirectory(dir string) WatchEntry {
for _, e := range c.Entries {
if e.Directory == dir {
return e
}
}

return WatchEntry{}
}

func ReadFile(filename string) (Config, error) {
func Read(f io.Reader) (Config, error) {
var (
c Config
err error
c Config
err error
yamlData []byte
)
yamlData, err = ioutil.ReadFile(filename)
yamlData, err = ioutil.ReadAll(f)

if err != nil {
return c, err
Expand Down
23 changes: 20 additions & 3 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,28 @@ func TestParseValidYaml(t *testing.T) {
validYamlData := `
---
watch:
- dir: /tmp/watch1
- dir: /tmp/watch2
profile: magento2
- directory: /tmp/watch1
extensions:
- ".scss"
- ".js"
- ".twig"
- directory: /tmp/watch2
profile: magento2
extensions:
- ".scss"
- ".js"
- ".twig"
`
c, err := Parse([]byte(validYamlData))

assert.NoError(t, err, "Config is valid and should not throw an error")
assert.IsType(t, Config{}, c)

assert.Equal(t, "/tmp/watch1", c.Entries[0].Directory)
assert.Equal(t, "/tmp/watch2", c.Entries[1].Directory)

}

func TestParseInvalidYaml(t *testing.T) {
Expand All @@ -31,3 +44,7 @@ watch

assert.Error(t, err, "Config is invalid and should throw an error")
}

func TestLoad(t *testing.T) {

}
57 changes: 8 additions & 49 deletions internal/profile/types.go
Original file line number Diff line number Diff line change
@@ -1,74 +1,33 @@
package profile

import (
"path/filepath"
"strings"
)

type Profile struct {
fileExtensions []string
}

func (l *Profile) IsAllowedFileExtension(path string) bool {

// if profile contains only one extension definition with "*" then allow every extension.
if len(l.fileExtensions) == 1 && l.fileExtensions[0] == "*" {
return true
}

extension := filepath.Ext(path)

for _, a := range l.fileExtensions {
if a == extension {
return true
}
}

return false
}

func (l *Profile) IsAllowedDirectory(path string) bool {
// Exclude some directories by default
excludedDirectories := [...]string{
"node_modules/",
".idea/",
".git/",
".svn/",
}

for _, excludedDirectory := range excludedDirectories {
if strings.Contains(path, excludedDirectory) {
return false
}
}

return true
Extensions []string
}

var Default = Profile{
fileExtensions: []string{"*"},
Extensions: []string{"*"},
}

var LESS = Profile{
fileExtensions: []string{".less"},
Extensions: []string{".less"},
}

var Magento2Theme = Profile{
fileExtensions: []string{".css", ".js", ".less", ".sass", ".ts"},
Extensions: []string{".css", ".js", ".less", ".sass", ".ts"},
}

var Magento2 = Profile{
fileExtensions: []string{".css", ".html", ".less", ".sass", ".js", ".php", ".phtml", ".ts", ".xml"},
Extensions: []string{".css", ".html", ".less", ".sass", ".js", ".php", ".phtml", ".ts", ".xml"},
}

var SASS = Profile{
fileExtensions: []string{".sass", ".scss"},
Extensions: []string{".sass", ".scss"},
}

var VueStorefront = Profile{
fileExtensions: []string{".css", ".js", ".sass", ".ts"},
Extensions: []string{".css", ".js", ".sass", ".ts"},
}

var Javascript = Profile{
fileExtensions: []string{".js", ".ts"},
Extensions: []string{".js", ".ts"},
}
57 changes: 54 additions & 3 deletions internal/profile/validator/path.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
package validator

import (
"github.com/cmuench/inotify-proxy/internal/config"
"github.com/cmuench/inotify-proxy/internal/profile"
"path/filepath"
"strings"
)

func IsPathValid(path string, profileName string) bool {
func IsPathValid(path string, entryConfig config.WatchEntry) bool {

if !isAllowedDirectory(path) {
return false
}

if len(entryConfig.Extensions) > 0 && !isAllowedFileExtension(path, entryConfig.Extensions) {
return false
}

if entryConfig.Profile == nil {
return true
}

var selectedProfile profile.Profile

switch profileName {
switch *entryConfig.Profile {
case "less":
selectedProfile = profile.LESS
case "magento2":
Expand All @@ -25,5 +40,41 @@ func IsPathValid(path string, profileName string) bool {
selectedProfile = profile.Default
}

return selectedProfile.IsAllowedDirectory(path) && selectedProfile.IsAllowedFileExtension(path)
return isAllowedFileExtension(path, selectedProfile.Extensions)
}

func isAllowedDirectory(path string) bool {
// Exclude some directories by default
excludedDirectories := [...]string{
"node_modules/",
".idea/",
".git/",
".svn/",
}

for _, excludedDirectory := range excludedDirectories {
if strings.Contains(path, excludedDirectory) {
return false
}
}

return true
}

func isAllowedFileExtension(path string, fileExtensions []string) bool {

// if profile contains only one extension definition with "*" then allow every extension.
if len(fileExtensions) == 1 && fileExtensions[0] == "*" {
return true
}

extension := filepath.Ext(path)

for _, a := range fileExtensions {
if a == extension {
return true
}
}

return false
}
Loading

0 comments on commit 9f746c0

Please sign in to comment.