<commit_before>package cmd

import (
	"fmt"
	"log"
	"os"
	"os/signal"

	"github.com/spf13/cobra"

	"github.com/Shopify/themekit/src/cmdutil"
	"github.com/Shopify/themekit/src/colors"
	"github.com/Shopify/themekit/src/file"
	"github.com/Shopify/themekit/src/shopify"
)

var watchCmd = &cobra.Command{
	Use:   "watch",
	Short: "Watch directory for changes and update remote theme",
	Long: `Watch is for running in the background while you are making changes to your project.

 run 'theme watch' while you are editing and it will detect create, update and delete events.

 For more documentation please see http://shopify.github.io/themekit/commands/#watch
 `,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmdutil.ForEachClient(flags, args, func(ctx cmdutil.Ctx) error {
			watcher, err := file.NewWatcher(ctx.Env, ctx.Flags.ConfigPath)
			if err != nil {
				return err
			}
			watchEvents, err := watcher.Watch()
			if err != nil {
				return err
			}
			defer watcher.Stop()

			signalChan := make(chan os.Signal)
			signal.Notify(signalChan, os.Interrupt)
			return watch(ctx, watchEvents, signalChan)
		})
	},
}

func watch(ctx cmdutil.Ctx, events chan file.Event, sig chan os.Signal) error {
	ctx.Flags.Verbose = true
	ctx.Log.SetFlags(log.Ltime)

	if ctx.Env.ReadOnly {
		return fmt.Errorf("[%s] environment is reaonly", colors.Green(ctx.Env.Name))
	}

	ctx.Log.Printf(
		"[%s] %s: Watching for file changes to theme %v",
		colors.Green(ctx.Env.Name),
		colors.Yellow(ctx.Shop.Name),
		colors.Yellow(ctx.Env.ThemeID),
	)
	for {
		select {
		case event := <-events:
			if event.Path == ctx.Flags.ConfigPath {
				ctx.Log.Print("Reloading config changes")
				return cmdutil.ErrReload
			}
			ctx.Log.Printf("[%s] processing %s", colors.Green(ctx.Env.Name), colors.Blue(event.Path))
			perform(ctx, event.Path, event.Op)
		case <-sig:
			return nil
		}
	}
}

func perform(ctx cmdutil.Ctx, path string, op file.Op) {
	defer ctx.DoneTask()

	if op == file.Remove {
		if err := ctx.Client.DeleteAsset(shopify.Asset{Key: path}); err != nil {
			ctx.ErrLog.Printf("[%s] (%s) %s", colors.Green(ctx.Env.Name), colors.Blue(path), err)
		} else if ctx.Flags.Verbose {
			ctx.Log.Printf("[%s] Deleted %s", colors.Green(ctx.Env.Name), colors.Blue(path))
		}
	} else {
		asset, err := shopify.ReadAsset(ctx.Env, path)
		if err != nil {<commit_msg>Adding a semaphore to prevent memory errors<commit_after>package cmd

import (
	"fmt"
	"log"
	"os"
	"os/signal"

	"github.com/spf13/cobra"

	"github.com/Shopify/themekit/src/cmdutil"
	"github.com/Shopify/themekit/src/colors"
	"github.com/Shopify/themekit/src/file"
	"github.com/Shopify/themekit/src/shopify"
)

// This is the limit of assets that can be uploaded at the same time. This may
// need to be tweaked in the future.
const assetLimit = 100

// This sets a hard limit on how many assets are loaded at a single time before
// being uploaded. This is to protect from memory errors when very large themes
// are uploaded.
var assetLimitSemaphore = make(chan struct{}, assetLimit)

var watchCmd = &cobra.Command{
	Use:   "watch",
	Short: "Watch directory for changes and update remote theme",
	Long: `Watch is for running in the background while you are making changes to your project.

 run 'theme watch' while you are editing and it will detect create, update and delete events.

 For more documentation please see http://shopify.github.io/themekit/commands/#watch
 `,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmdutil.ForEachClient(flags, args, func(ctx cmdutil.Ctx) error {
			watcher, err := file.NewWatcher(ctx.Env, ctx.Flags.ConfigPath)
			if err != nil {
				return err
			}
			watchEvents, err := watcher.Watch()
			if err != nil {
				return err
			}
			defer watcher.Stop()

			signalChan := make(chan os.Signal)
			signal.Notify(signalChan, os.Interrupt)
			return watch(ctx, watchEvents, signalChan)
		})
	},
}

func watch(ctx cmdutil.Ctx, events chan file.Event, sig chan os.Signal) error {
	ctx.Flags.Verbose = true
	ctx.Log.SetFlags(log.Ltime)

	if ctx.Env.ReadOnly {
		return fmt.Errorf("[%s] environment is reaonly", colors.Green(ctx.Env.Name))
	}

	ctx.Log.Printf(
		"[%s] %s: Watching for file changes to theme %v",
		colors.Green(ctx.Env.Name),
		colors.Yellow(ctx.Shop.Name),
		colors.Yellow(ctx.Env.ThemeID),
	)
	for {
		select {
		case event := <-events:
			if event.Path == ctx.Flags.ConfigPath {
				ctx.Log.Print("Reloading config changes")
				return cmdutil.ErrReload
			}
			ctx.Log.Printf("[%s] processing %s", colors.Green(ctx.Env.Name), colors.Blue(event.Path))
			perform(ctx, event.Path, event.Op)
		case <-sig:
			return nil
		}
	}
}

func perform(ctx cmdutil.Ctx, path string, op file.Op) {
	defer ctx.DoneTask()

	if op == file.Remove {
		if err := ctx.Client.DeleteAsset(shopify.Asset{Key: path}); err != nil {
			ctx.ErrLog.Printf("[%s] (%s) %s", colors.Green(ctx.Env.Name), colors.Blue(path), err)
		} else if ctx.Flags.Verbose {
			ctx.Log.Printf("[%s] Deleted %s", colors.Green(ctx.Env.Name), colors.Blue(path))
		}
	} else {
		assetLimitSemaphore <- struct{}{}
		defer func() { <-assetLimitSemaphore }()

		asset, err := shopify.ReadAsset(ctx.Env, path)
		if err != nil {
{'fraction_non_alphanumeric': 0.14101577529819162, 'fraction_numerical': 0.0013466717968449403, 'mean_word_length': 3.696476964769648, 'pattern_counts': {'":': 0, '<': 9, '<?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': '1397355', 'n_tokens_mistral': 2001, 'n_tokens_neox': 1834, 'n_words': 479}