1package commands
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/ambientsound/pms/api"
8	"github.com/ambientsound/pms/input/lexer"
9	"github.com/ambientsound/pms/input/parser"
10	"github.com/ambientsound/pms/options"
11)
12
13// Set manipulates a Options table by parsing input tokens from the "set" command.
14type Set struct {
15	newcommand
16	api    api.API
17	tokens []parser.OptionToken
18}
19
20// NewSet returns Set.
21func NewSet(api api.API) Command {
22	return &Set{
23		api:    api,
24		tokens: make([]parser.OptionToken, 0),
25	}
26}
27
28// Parse implements Command.
29func (cmd *Set) Parse() error {
30
31	cmd.setTabCompleteVerbs("")
32
33	for {
34		// Scan the next token, which must be an identifier.
35		tok, lit := cmd.ScanIgnoreWhitespace()
36		switch tok {
37		case lexer.TokenIdentifier:
38			break
39		case lexer.TokenEnd, lexer.TokenComment:
40			return nil
41		default:
42			cmd.setTabCompleteEmpty()
43			return fmt.Errorf("Unexpected '%s', expected whitespace or END", lit)
44		}
45
46		cmd.setTabCompleteVerbs(lit)
47
48		// Parse the option statement.
49		cmd.Unscan()
50		err := cmd.ParseSet()
51		if err != nil {
52			return err
53		}
54	}
55}
56
57// ParseSet parses a single "key=val" statement.
58func (cmd *Set) ParseSet() error {
59	tokens := make([]string, 0)
60	for {
61		tok, lit := cmd.Scan()
62		if tok == lexer.TokenWhitespace || tok == lexer.TokenEnd || tok == lexer.TokenComment {
63			break
64		}
65		tokens = append(tokens, lit)
66	}
67
68	s := strings.Join(tokens, "")
69	cmd.setTabCompleteVerbs(s)
70	optionToken := parser.OptionToken{}
71	err := optionToken.Parse([]rune(s))
72	if err != nil {
73		cmd.setTabCompleteEmpty()
74		return err
75	}
76
77	// Figure out tabcomplete
78	cmd.setTabCompleteOption(optionToken)
79
80	cmd.tokens = append(cmd.tokens, optionToken)
81
82	return nil
83}
84
85// Exec implements Command.
86func (cmd *Set) Exec() error {
87	for _, tok := range cmd.tokens {
88		opt := cmd.api.Options().Get(tok.Key)
89
90		if opt == nil {
91			return fmt.Errorf("No such option: %s", tok.Key)
92		}
93
94		// Queries print options to the statusbar.
95		if tok.Query {
96			cmd.api.Message(opt.String())
97			continue
98		}
99
100		switch opt := opt.(type) {
101
102		case *options.BoolOption:
103			switch {
104			case !tok.Bool:
105				return fmt.Errorf("Attempting to give parameters to a boolean option (try 'set no%s' or 'set inv%s')", tok.Key, tok.Key)
106			case tok.Invert:
107				opt.SetBool(!opt.BoolValue())
108				cmd.api.Message(opt.String())
109			case tok.Negate:
110				opt.SetBool(false)
111			default:
112				opt.SetBool(true)
113			}
114
115		default:
116			if !tok.Bool {
117				if err := opt.Set(tok.Value); err != nil {
118					return err
119				}
120				break
121			}
122
123			// Not a boolean option, and no value. Print the value.
124			cmd.api.Message(opt.String())
125			continue
126		}
127
128		cmd.api.OptionChanged(opt.Key())
129		cmd.api.Message(opt.String())
130	}
131
132	return nil
133}
134
135// setTabCompleteVerbs sets the tab complete list to the list of option keys.
136func (cmd *Set) setTabCompleteVerbs(lit string) {
137	cmd.setTabComplete(lit, cmd.api.Options().Keys())
138}
139
140// setTabCompleteOption sets the tab complete list to an option value and a blank value.
141func (cmd *Set) setTabCompleteOption(tok parser.OptionToken) {
142	// Bool options are already handled by the verb completion.
143	if tok.Bool {
144		return
145	}
146
147	// Get the option object. If it is not found, let the verb completion handle this.
148	opt := cmd.api.Options().Get(tok.Key)
149	if opt == nil {
150		return
151	}
152
153	// Don't tab complete option values unless the value is empty.
154	if len(tok.Value) > 0 {
155		return
156	}
157
158	// Return two items: the existing value, and the typed value.
159	cmd.setTabComplete("", []string{
160		fmt.Sprintf(`="%s"`, opt.StringValue()),
161		"=",
162	})
163}
164