<commit_before>// Copyright 2016 <NAME>. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package ui

import (
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"strings"

	"github.com/bogem/nehm/track"
)

// TracksMenu gets tracks from GetTracks function, show these tracks in menu
// and returns selected.
//
// TracksMenu finishes when user pushes 'd' button.
type TracksMenu struct {
	GetTracks func(offset uint) ([]track.Track, error)
	Limit     uint
	Offset    uint

	selected          map[float64]bool
	selectionFinished bool
}

// Show gets tracks from GetTracks function, show these tracks,
// adds selected to TracksMenu.selected and returns them.
func (tm TracksMenu) Show() []track.Track {
	Println("Getting information about tracks")
	tracks, err := tm.GetTracks(tm.Offset)
	if err != nil {
		handleError(err)
		Term("", nil)
	}
	oldOffset := tm.Offset

	if len(tracks) == 0 {
		Term("there are no tracks to show", nil)
	}

	tm.selected = make(map[float64]track.Track)
	for !tm.selectionFinished {
		if oldOffset != tm.Offset {
			oldOffset = tm.Offset
			tracks, err = tm.GetTracks(tm.Offset)
			if err != nil {
				handleError(err)
				if tm.Offset >= tm.Limit {
					Println("Downloading previous page")
					Sleep() // pause the goroutine so user can read the errors
					tm.Offset -= tm.Limit
					continue
				} else {
					Term("", nil)
				}
			}
		}

		trackItems := tm.formTrackItems(tracks)
		clearScreen()
		tm.showMenu(trackItems)
	}

	selected := make([]track.Track, 0, len(tm.selected))
	for _, t := range tm.selected {
		selected = append(selected, t)
	}
	return selected
}

func handleError(err error) {
	switch {
	case strings.Contains(err.Error(), "403"):
		Error("you're not allowed to see these tracks", nil)
	case strings.Contains(err.Error(), "404"):
		Error("there are no tracks", nil)
	default:
		Error("", err)
	}
}

var trackItems []MenuItem

func (tm *TracksMenu) formTrackItems(tracks []track.Track) []MenuItem {
	if trackItems == nil {
		trackItems = make([]MenuItem, 0, tm.Limit)
	}
	trackItems = trackItems[:0]

	for i, t := range tracks {
		desc := t.Fullname() + " (" + t.Duration() + ")"

		var trackItem MenuItem
		if contains := tm.selected[t.ID()]; contains {
			trackItem = MenuItem{
				Index: GreenString("A"),
				Desc:  desc,
			}
		} else {
			t := t // https://golang.org/doc/faq#closures_and_goroutines
			trackItem = MenuItem{
				Index: strconv.Itoa(i + 1),
				Desc:  desc,
				Run:   func() { tm.selected[t.ID()] = true },
			}
		}
		trackItems = append(trackItems, trackItem)
	}
	return trackItems
}

func contains(s []track.Track, t track.Track) bool {
	for _, v := range s {
		if v.ID() == t.ID() {
			return true
		}
	}
	return false
}

// clearPath is used for holding the path to clear binary,
// so exec.Command() don't have to always look the path to command.
var clearPath string

func clearScreen() {
	var err error
	if runtime.GOOS == "windows" {
		clearPath, err = exec.LookPath("cls")
	} else {
		clearPath, err = exec.LookPath("clear")
	}
	if err != nil { // if there is no clear command, just do not clear the screen
		return
	}
	cmd := exec.Command(clearPath)
	cmd.Stdout = os.Stdout
	cmd.Run()
}

var (
	controlItems []MenuItem
	menu         Menu
)

func (tm *TracksMenu) showMenu(trackItems []MenuItem) {
	if controlItems == nil {
		controlItems = tm.controlItems()
	}
	menu.Clear()
	menu.AddItems(trackItems...)
	menu.AddNewline()
	menu.AddItems(controlItems...)
	menu.Show()<commit_msg>Add check of clearPath<commit_after>// Copyright 2016 <NAME>. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package ui

import (
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"strings"

	"github.com/bogem/nehm/track"
)

// TracksMenu gets tracks from GetTracks function, show these tracks in menu
// and returns selected.
//
// TracksMenu finishes when user pushes 'd' button.
type TracksMenu struct {
	GetTracks func(offset uint) ([]track.Track, error)
	Limit     uint
	Offset    uint

	selected          map[float64]bool
	selectionFinished bool
}

// Show gets tracks from GetTracks function, show these tracks,
// adds selected to TracksMenu.selected and returns them.
func (tm TracksMenu) Show() []track.Track {
	Println("Getting information about tracks")
	tracks, err := tm.GetTracks(tm.Offset)
	if err != nil {
		handleError(err)
		Term("", nil)
	}
	oldOffset := tm.Offset

	if len(tracks) == 0 {
		Term("there are no tracks to show", nil)
	}

	tm.selected = make(map[float64]track.Track)
	for !tm.selectionFinished {
		if oldOffset != tm.Offset {
			oldOffset = tm.Offset
			tracks, err = tm.GetTracks(tm.Offset)
			if err != nil {
				handleError(err)
				if tm.Offset >= tm.Limit {
					Println("Downloading previous page")
					Sleep() // pause the goroutine so user can read the errors
					tm.Offset -= tm.Limit
					continue
				} else {
					Term("", nil)
				}
			}
		}

		trackItems := tm.formTrackItems(tracks)
		clearScreen()
		tm.showMenu(trackItems)
	}

	selected := make([]track.Track, 0, len(tm.selected))
	for _, t := range tm.selected {
		selected = append(selected, t)
	}
	return selected
}

func handleError(err error) {
	switch {
	case strings.Contains(err.Error(), "403"):
		Error("you're not allowed to see these tracks", nil)
	case strings.Contains(err.Error(), "404"):
		Error("there are no tracks", nil)
	default:
		Error("", err)
	}
}

var trackItems []MenuItem

func (tm *TracksMenu) formTrackItems(tracks []track.Track) []MenuItem {
	if trackItems == nil {
		trackItems = make([]MenuItem, 0, tm.Limit)
	}
	trackItems = trackItems[:0]

	for i, t := range tracks {
		desc := t.Fullname() + " (" + t.Duration() + ")"

		var trackItem MenuItem
		if contains := tm.selected[t.ID()]; contains {
			trackItem = MenuItem{
				Index: GreenString("A"),
				Desc:  desc,
			}
		} else {
			t := t // https://golang.org/doc/faq#closures_and_goroutines
			trackItem = MenuItem{
				Index: strconv.Itoa(i + 1),
				Desc:  desc,
				Run:   func() { tm.selected[t.ID()] = true },
			}
		}
		trackItems = append(trackItems, trackItem)
	}
	return trackItems
}

// clearPath is used for holding the path to clear binary,
// so exec.Command() don't have to always look the path to command.
var clearPath string

func clearScreen() {
	if clearPath == "" {
		var err error
		if runtime.GOOS == "windows" {
			clearPath, err = exec.LookPath("cls")
		} else {
			clearPath, err = exec.LookPath("clear")
		}
		if err != nil { // if there is no clear command, just do not clear the screen
			return
		}
	}
	cmd := exec.Command(clearPath)
	cmd.Stdout = os.Stdout
	cmd.Run()
}

var (
	controlItems []MenuItem
	menu         Menu
)

func (tm *TracksMenu) showMenu(trackItems []MenuItem) {
	if controlItems == nil {
		controlItems = tm.controlItems()
	}
	menu.Clear()
	menu.AddItems(trackItems...)
	menu.AddNewline()
	menu.AddItems(controlItems...)
	menu.Show()
{'fraction_non_alphanumeric': 0.1341743119266055, 'fraction_numerical': 0.005447247706422019, 'mean_word_length': 3.29089790897909, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '871252', 'n_tokens_mistral': 2601, 'n_tokens_neox': 2375, 'n_words': 697}