<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
)

func processPath(fullPath string, options *cliOptions) {
	defer wg.Done()

	contents, err := ioutil.ReadFile(fullPath)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	newContents := strings.Replace(string(contents), options.from, options.to, -1)
	if newContents != string(contents) {

		err = ioutil.WriteFile(fullPath, []byte(newContents), 0)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return
		}

		fmt.Println(fullPath)
	}
}

func handlePath(fullPath string, options *cliOptions) {
	defer wg.Done()

	matched, err := filepath.Match(options.file, path.Base(fullPath))
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	if matched {
		wg.Add(1)
		go processPath(fullPath, options)
	}
}

type cliOptions struct {
	dir  string
	from string
	to   string
	file string
	help bool
}

var wg sync.WaitGroup

func main() {

	var options cliOptions

	flag.StringVar(&options.dir, "dir", ".", "The directory to traverse.")
	flag.StringVar(&options.from, "from", "", "The text to replace.")
	flag.StringVar(&options.to, "to", "", "The replacement text.")
	flag.StringVar(&options.file, "file", "*", "The glob file pattern to match.")
	flag.BoolVar(&options.help, "help", false, "Show help.")
	flag.Parse()

	if (options.from == "" && options.to == "") || options.help {
		flag.Usage()

	} else {
		err := filepath.Walk(options.dir, func(fullPath string, info os.FileInfo, err error) error {

			if err != nil {
				return err
			}

			if info.IsDir() {
				return nil
			}

			wg.Add(1)
			go handlePath(fullPath, &options)

			return nil
		})

		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return
		}

		wg.Wait()
	}
}
<commit_msg>Added a dash of color.<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"github.com/fatih/color"
)

type cliOptions struct {
	dir  string
	from string
	to   string
	file string
	help bool
}

func (this cliOptions) valid() bool {
	return this.from != "" && this.to != ""
}

func (this cliOptions) display() {
	options := color.CyanString("replacing:   ")
	options += color.GreenString("%s\n", this.from)
	options += color.CyanString("with:        ")
	options += color.GreenString("%s\n", this.to)
	options += color.CyanString("in files:    ")
	options += color.GreenString("%s\n", this.file)
	options += color.CyanString("starting in: ")
	options += color.GreenString("%s\n", this.dir)
	fmt.Print(options)
}

var wg sync.WaitGroup
var banner string = ` ____            _
|  _ \ ___ _ __ | | __ _  ___ ___
| |_) / _ \ '_ \| |/ _' |/ __/ _ \
|  _ <  __/ |_) | | (_| | (_|  __/
|_| \_\___| .__/|_|\__'_|\___\___|
          |_|

`

func main() {

	var options cliOptions

	flag.StringVar(&options.dir, "dir", ".", "The directory to traverse.")
	flag.StringVar(&options.from, "from", "", "The text to replace.")
	flag.StringVar(&options.to, "to", "", "The replacement text.")
	flag.StringVar(&options.file, "file", "*", "The glob file pattern to match.")
	flag.BoolVar(&options.help, "help", false, "Show help.")
	flag.Parse()

	if (!options.valid()) || options.help {
		color.Cyan(banner)
		flag.Usage()

	} else {
		options.display()

		err := filepath.Walk(options.dir, func(fullPath string, info os.FileInfo, err error) error {

			if err != nil {
				return err
			}

			if info.IsDir() {
				return nil
			}

			wg.Add(1)
			go handlePath(fullPath, &options)

			return nil
		})

		if err != nil {
			fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
			return
		}

		wg.Wait()
	}
}

func processPath(fullPath string, options *cliOptions) {
	defer wg.Done()

	contents, err := ioutil.ReadFile(fullPath)
	if err != nil {
		fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
		return
	}

	newContents := strings.Replace(string(contents), options.from, options.to, -1)
	if newContents != string(contents) {

		err = ioutil.WriteFile(fullPath, []byte(newContents), 0)
		if err != nil {
			fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
			return
		}

		color.Magenta(fullPath)
	}
}

func handlePath(fullPath string, options *cliOptions) {
	defer wg.Done()

	matched, err := filepath.Match(options.file, path.Base(fullPath))
	if err != nil {
		fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
		return
	}

	if matched {
		wg.Add(1)
		go processPath(fullPath, options)
	}
}

{'fraction_non_alphanumeric': 0.16565246788370522, 'fraction_numerical': 0.001803020058598152, 'mean_word_length': 3.41592039800995, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4830575', 'n_tokens_mistral': 1772, 'n_tokens_neox': 1631, 'n_words': 360}