@slugbugblue/trax-cli 0.12.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,22 @@
1
- /* Copyright 2022-2023 Chad Transtrum
2
- *
3
- * Licensed under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License.
5
- * You may obtain a copy of the License at
6
- *
7
- * http://www.apache.org/licenses/LICENSE-2.0
8
- *
9
- * Unless required by applicable law or agreed to in writing, software
10
- * distributed under the License is distributed on an "AS IS" BASIS,
11
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- * See the License for the specific language governing permissions and
13
- * limitations under the License.
1
+ /** CLI analyze command
2
+ * @copyright 2022-2026
3
+ * @author Chad Transtrum <chad@transtrum.net>
4
+ * @license Apache-2.0
14
5
  */
15
6
 
16
- // CLI analyze command
7
+ /** @typedef {import('../cli.js').CLIContext} CLIContext */
17
8
 
18
9
  import { analyze } from '@slugbugblue/trax-analyst'
19
10
  import { Trax } from '@slugbugblue/trax'
20
11
 
21
- const notationRx = /^[@a-z]+\d+[bps/\\+]$/i
12
+ const notationRx = /^[@a-z]+\d+[bps\/\\+]$/iv
13
+
14
+ /** @type {Record<string, string>} */
15
+ const threatNames = {
16
+ 0: 'corners',
17
+ 1: 'attacks',
18
+ 2: 'Ls',
19
+ }
22
20
 
23
21
  export const analyzeCmd = {
24
22
  name: 'analyze',
@@ -29,14 +27,64 @@ export const analyzeCmd = {
29
27
  'Analyze the current position of the currently selected game, or pass in a',
30
28
  'move to analyze the result of that move.',
31
29
  ],
32
- }
30
+ /** @param {CLIContext} CLI @param {string} [move] */
31
+ fn(CLI, move) {
32
+ if (!String(CLI.GAME?.id)) {
33
+ return CLI.error('No active game. Type "new" to start a game.')
34
+ }
33
35
 
34
- const threatNames = {
35
- 0: 'corners',
36
- 1: 'attacks',
37
- 2: 'Ls',
36
+ let trax = CLI.TRAX
37
+
38
+ const detailed =
39
+ typeof move === 'string' &&
40
+ ('detail'.startsWith(move) || 'debug'.startsWith(move))
41
+
42
+ if (move && notationRx.test(move)) {
43
+ move = CLI.fixNotation(move)
44
+ CLI.do('try', move)
45
+ trax = new Trax(trax.rules, trax.moves, 'cli')
46
+ const play = trax.dropTile(move)
47
+ if (!play.valid || trax.over) return
48
+ }
49
+
50
+ if (trax.over) {
51
+ return CLI.error('Game has ended.')
52
+ }
53
+
54
+ const { edge, threats, scores, faulty } = analyze(trax)
55
+
56
+ if (detailed) CLI.out(CLI.color.white(edge.w))
57
+ CLI.out(
58
+ CLI.bubble('w', scores.w) +
59
+ CLI.color.white(' ') +
60
+ listThreats(threats.w, detailed),
61
+ )
62
+
63
+ if (detailed) {
64
+ if (faulty.w.length > 0) {
65
+ CLI.out(CLI.color.white('') + listFaulty(faulty.w))
66
+ }
67
+
68
+ CLI.out(CLI.color.black(edge.b))
69
+ }
70
+
71
+ CLI.out(
72
+ CLI.bubble('b', scores.b) +
73
+ CLI.color.black(' ') +
74
+ listThreats(threats.b, detailed),
75
+ )
76
+
77
+ if (detailed && faulty.b.length > 0) {
78
+ CLI.out(CLI.color.black('') + listFaulty(faulty.b))
79
+ }
80
+ },
38
81
  }
39
82
 
83
+ /**
84
+ * @param {Record<string, any[]>} threats
85
+ * @param {boolean} detailed
86
+ * @returns {string}
87
+ */
40
88
  const listThreats = (threats, detailed) => {
41
89
  const summary = []
42
90
  for (const level of Object.keys(threats || {})) {
@@ -54,6 +102,10 @@ const listThreats = (threats, detailed) => {
54
102
  return summary.join(' ')
55
103
  }
56
104
 
105
+ /**
106
+ * @param {any[]} faulty
107
+ * @returns {string}
108
+ */
57
109
  const listFaulty = (faulty) => {
58
110
  const summary = []
59
111
  for (const threat of faulty) {
@@ -64,49 +116,3 @@ const listFaulty = (faulty) => {
64
116
 
65
117
  return 'Faulty: ' + summary.join(' ')
66
118
  }
67
-
68
- analyzeCmd.fn = (CLI, move) => {
69
- if (!String(CLI.GAME?.id)) {
70
- return CLI.error('No active game. Type "new" to start a game.')
71
- }
72
-
73
- let trax = CLI.TRAX
74
-
75
- const detailed = 'detail'.startsWith(move) || 'debug'.startsWith(move)
76
-
77
- if (notationRx.test(move)) {
78
- move = CLI.fixNotation(move)
79
- CLI.do('try', move)
80
- trax = new Trax(trax.rules, trax.moves, 'cli')
81
- const play = trax.dropTile(move)
82
- if (!play.valid || trax.over) return
83
- }
84
-
85
- if (trax.over) {
86
- return CLI.error('Game has ended.')
87
- }
88
-
89
- const { edge, threats, scores, faulty } = analyze(trax)
90
-
91
- if (detailed) CLI.out(CLI.color.white(edge.w))
92
- CLI.out(
93
- CLI.bubble('w', scores.w) +
94
- CLI.color.white(' ') +
95
- listThreats(threats.w, detailed),
96
- )
97
-
98
- if (detailed) {
99
- if (faulty.w.length > 0) CLI.out(CLI.color.white('') + listFaulty(faulty.w))
100
- CLI.out(CLI.color.black(edge.b))
101
- }
102
-
103
- CLI.out(
104
- CLI.bubble('b', scores.b) +
105
- CLI.color.black(' ') +
106
- listThreats(threats.b, detailed),
107
- )
108
-
109
- if (detailed && faulty.b.length > 0) {
110
- CLI.out(CLI.color.black('') + listFaulty(faulty.b))
111
- }
112
- }
@@ -1,19 +1,10 @@
1
- /* Copyright 2022 Chad Transtrum
2
- *
3
- * Licensed under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License.
5
- * You may obtain a copy of the License at
6
- *
7
- * http://www.apache.org/licenses/LICENSE-2.0
8
- *
9
- * Unless required by applicable law or agreed to in writing, software
10
- * distributed under the License is distributed on an "AS IS" BASIS,
11
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- * See the License for the specific language governing permissions and
13
- * limitations under the License.
1
+ /** CLI delete command
2
+ * @copyright 2022-2026
3
+ * @author Chad Transtrum <chad@transtrum.net>
4
+ * @license Apache-2.0
14
5
  */
15
6
 
16
- // CLI delete command
7
+ /** @typedef {import('../cli.js').CLIContext} CLIContext */
17
8
 
18
9
  export const deleteCmd = {
19
10
  name: 'delete',
@@ -25,11 +16,40 @@ export const deleteCmd = {
25
16
  'Delete the current game. Or specify a game number to delete that game.',
26
17
  'If the game is in progress, you must type "force" to delete it.',
27
18
  ],
19
+ /** @param {CLIContext} CLI @param {string} [id] @param {string} [force] */
20
+ fn(CLI, id, force = 'x') {
21
+ const current = String(CLI.GAME.id)
22
+ id ||= current
23
+ if ('force'.startsWith(id)) {
24
+ id = current
25
+ force = 'force'
26
+ }
27
+
28
+ if (id.startsWith('#')) id = id.slice(1)
29
+
30
+ const game = CLI.GAMES[id]
31
+
32
+ if (!game || !String(game.id)) {
33
+ return CLI.error('Invalid id. Type "list" to see available games.')
34
+ }
35
+
36
+ if (game.over || game.moves === '' || 'force'.startsWith(force)) {
37
+ CLI.delete(id)
38
+ CLI.out(CLI.color('Deleted game #' + id + '.'))
39
+
40
+ if (id === String(current)) {
41
+ selectAnyGame(CLI)
42
+ }
43
+ } else {
44
+ CLI.error('Game #' + id + ' is still active. Use "force" to delete it.')
45
+ }
46
+ },
28
47
  }
29
48
 
49
+ /** @param {CLIContext} CLI */
30
50
  const selectAnyGame = (CLI) => {
31
51
  if (CLI.GAMES) {
32
- const games = Object.values(CLI.GAMES).sort((a, b) => {
52
+ const games = Object.values(CLI.GAMES).toSorted((a, b) => {
33
53
  // Active games first
34
54
  if (a.over && !b.over) return 1
35
55
  if (b.over && !a.over) return -1
@@ -41,31 +61,3 @@ const selectAnyGame = (CLI) => {
41
61
  }
42
62
  }
43
63
  }
44
-
45
- deleteCmd.fn = (CLI, id, force = 'x') => {
46
- const current = String(CLI.GAME.id)
47
- id ||= current
48
- if ('force'.startsWith(id)) {
49
- id = current
50
- force = 'force'
51
- }
52
-
53
- if (id.startsWith('#')) id = id.slice(1)
54
-
55
- const game = CLI.GAMES[id]
56
-
57
- if (!game || !String(game.id)) {
58
- return CLI.error('Invalid id. Type "list" to see available games.')
59
- }
60
-
61
- if (game.over || game.moves === '' || 'force'.startsWith(force)) {
62
- CLI.delete(id)
63
- CLI.out(CLI.color('Deleted game #' + id + '.'))
64
-
65
- if (id === String(current)) {
66
- selectAnyGame(CLI)
67
- }
68
- } else {
69
- CLI.error('Game #' + id + ' is still active. Use "force" to delete it.')
70
- }
71
- }
package/src/cmds/help.js CHANGED
@@ -1,19 +1,10 @@
1
- /* Copyright 2022 Chad Transtrum
2
- *
3
- * Licensed under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License.
5
- * You may obtain a copy of the License at
6
- *
7
- * http://www.apache.org/licenses/LICENSE-2.0
8
- *
9
- * Unless required by applicable law or agreed to in writing, software
10
- * distributed under the License is distributed on an "AS IS" BASIS,
11
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- * See the License for the specific language governing permissions and
13
- * limitations under the License.
1
+ /** CLI help command
2
+ * @copyright 2022-2026
3
+ * @author Chad Transtrum <chad@transtrum.net>
4
+ * @license Apache-2.0
14
5
  */
15
6
 
16
- // CLI help command
7
+ /** @typedef {import('../cli.js').CLIContext} CLIContext */
17
8
 
18
9
  export const helpCmd = {
19
10
  name: 'help',
@@ -26,56 +17,62 @@ export const helpCmd = {
26
17
  'Type "help <command>" or "<command> help" to see additional information',
27
18
  'for a specific command.',
28
19
  ],
29
- }
30
-
31
- // Find the length of the printable characters of the string
32
- const length = (text) => text.replaceAll(/[<">]/g, '').length
33
-
34
- helpCmd.fn = (CLI, word) => {
35
- // If we are looking for help on a particular commands, get that command
36
- word = CLI.resolveCommand(word) || (word ? String(word).toLowerCase() : '')
37
- let cmds = CLI.commands.filter((c) => !word || c.startsWith(word))
38
- if (cmds.length === 0) {
39
- cmds = CLI.commands.filter((c) => c.includes(word))
40
- }
41
-
42
- // If we don't have a few commands, select all commands
43
- if (cmds.length === 0) cmds = CLI.commands
20
+ /** @param {CLIContext} CLI @param {string} [word] */
21
+ fn(CLI, word) {
22
+ // If we are looking for help on a particular commands, get that command
23
+ const resolved = CLI.resolveCommand(word)
24
+ const resolvedWord =
25
+ typeof resolved === 'string'
26
+ ? resolved
27
+ : word
28
+ ? String(word).toLowerCase()
29
+ : ''
30
+ let cmds = CLI.commands.filter(
31
+ (c) => !resolvedWord || c.startsWith(resolvedWord),
32
+ )
33
+ if (cmds.length === 0) {
34
+ cmds = CLI.commands.filter((c) => c.includes(resolvedWord))
35
+ }
44
36
 
45
- // Load all the commands we need to print into a local variable
46
- // and also use the loop to get the length of the longest args
47
- let max = 1
48
- const commands = {}
49
- for (const key of cmds) {
50
- commands[key] = CLI.cmd(key)
51
- max = Math.max(max, key.length + length(commands[key].args))
52
- }
37
+ // If we don't have a few commands, select all commands
38
+ if (cmds.length === 0) cmds = CLI.commands
53
39
 
54
- // Print a summary of all matching commands
55
- for (const key of cmds.sort()) {
56
- const topic = commands[key]
57
- if (topic.hide && !CLI.repl) continue // Hidden commands only in repl
58
- const space = ' '.repeat(5 + max - length(topic.args) - key.length)
59
- CLI.out(
60
- CLI.color.command(` ${key} ${topic.args}`) +
61
- CLI.color.short(space + topic.desc),
62
- )
63
- }
40
+ // Load all the commands we need to print into a local variable
41
+ // and also use the loop to get the length of the longest args
42
+ let max = 1
43
+ /** @type {Record<string, ReturnType<CLIContext['cmd']>>} */
44
+ const commands = {}
45
+ for (const key of cmds) {
46
+ commands[key] = CLI.cmd(key)
47
+ max = Math.max(max, key.length + printLength(commands[key].args))
48
+ }
64
49
 
65
- // And if we have just one command, print details on that command
66
- if (cmds.length === 1) {
67
- const topic = commands[cmds[0]]
68
- for (const alias of topic.alt) {
69
- CLI.out(CLI.color.command(` ${alias}`))
50
+ // Print a summary of all matching commands
51
+ for (const key of cmds.toSorted()) {
52
+ const topic = commands[key]
53
+ if (topic.hide && !CLI.repl) continue // Hidden commands only in repl
54
+ const space = ' '.repeat(5 + max - printLength(topic.args) - key.length)
55
+ CLI.out(
56
+ CLI.color.command(` ${key} ${topic.args}`) +
57
+ CLI.color.short(space + topic.desc),
58
+ )
70
59
  }
71
60
 
72
- CLI.out('')
73
- if (CLI.is(topic.help, 'str')) {
74
- CLI.out(' ' + CLI.color.help(topic.help))
75
- } else {
61
+ // And if we have just one command, print details on that command
62
+ if (cmds.length === 1) {
63
+ const topic = commands[cmds[0]]
64
+ for (const alias of topic.alt) {
65
+ CLI.out(CLI.color.command(` ${alias}`))
66
+ }
67
+
68
+ CLI.out('')
76
69
  for (const line of topic.help) {
77
70
  CLI.out(' ' + CLI.color.help(line))
78
71
  }
79
72
  }
80
- }
73
+ },
81
74
  }
75
+
76
+ // Find the length of the printable characters of the string
77
+ /** @param {string} text @returns {number} */
78
+ const printLength = (text) => text.replaceAll(/[<">]/gv, '').length