Skip to content

Commit 445f2ea

Browse files
committed
Add uninstall-completions command
1 parent c502199 commit 445f2ea

File tree

11 files changed

+251
-38
lines changed

11 files changed

+251
-38
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package scala.cli.commands
2+
3+
import caseapp._
4+
5+
// format: off
6+
final case class SharedUninstallCompletionsOptions(
7+
@HelpMessage("Path to `*rc` file, defaults to `.bashrc` or `.zshrc` depending on shell")
8+
rcFile: Option[String] = None,
9+
@Hidden
10+
@HelpMessage("Custom banner in comment placed in rc file")
11+
banner: String = "{NAME} completions",
12+
@Hidden
13+
@HelpMessage("Custom completions name")
14+
name: Option[String] = None
15+
)
16+
// format: on
17+
18+
object SharedUninstallCompletionsOptions {
19+
implicit lazy val parser: Parser[SharedUninstallCompletionsOptions] = Parser.derive
20+
implicit lazy val help: Help[SharedUninstallCompletionsOptions] = Help.derive
21+
// Parser.Aux for using SharedUninstallCompletionsOptions with @Recurse in other options
22+
implicit lazy val parserAux: Parser.Aux[SharedUninstallCompletionsOptions, parser.D] = parser
23+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package scala.cli.commands
2+
3+
import caseapp._
4+
5+
// format: off
6+
@HelpMessage("Uninstalls completions from your shell")
7+
final case class UninstallCompletionsOptions(
8+
@Recurse
9+
shared: SharedUninstallCompletionsOptions = SharedUninstallCompletionsOptions(),
10+
@Recurse
11+
logging: LoggingOptions = LoggingOptions()
12+
)
13+
// format: on
14+
15+
object UninstallCompletionsOptions {
16+
implicit lazy val parser: Parser[UninstallCompletionsOptions] = Parser.derive
17+
implicit lazy val help: Help[UninstallCompletionsOptions] = Help.derive
18+
}

modules/cli-options/src/main/scala/scala/cli/commands/UninstallOptions.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import caseapp._
55
import java.nio.file.Path
66

77
// format: off
8-
@HelpMessage("Unistall scala-cli - only works when installed by the installation script")
8+
@HelpMessage("Uninstall scala-cli - only works when installed by the installation script")
99
final case class UninstallOptions(
1010
@Recurse
1111
bloopExit: BloopExitOptions = BloopExitOptions(),
12+
@Recurse
13+
sharedUninstallCompletions: SharedUninstallCompletionsOptions = SharedUninstallCompletionsOptions(),
1214
@Group("Uninstall")
1315
@Name("f")
1416
@HelpMessage("Force scala-cli uninstall")

modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class ScalaCliCommands(
5959
Shebang,
6060
Test,
6161
Uninstall,
62+
UninstallCompletions,
6263
Update,
6364
Version
6465
) ++ (if (pgpUseBinaryCommands) Nil else pgpCommands.allScalaCommands.toSeq) ++

modules/cli/src/main/scala/scala/cli/commands/InstallCompletions.scala

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ object InstallCompletions extends ScalaCommand[InstallCompletionsOptions] {
1717
List("install", "completions"),
1818
List("install-completions")
1919
)
20-
private lazy val home = os.Path(sys.props("user.home"), os.pwd)
2120
def run(options: InstallCompletionsOptions, args: RemainingArgs): Unit = {
2221
CurrentParams.verbosity = options.logging.verbosity
2322
val interactive = options.logging.verbosityOptions.interactiveInstance()
@@ -27,44 +26,29 @@ object InstallCompletions extends ScalaCommand[InstallCompletionsOptions] {
2726
.getOrElse(options.directories.directories.completionsDir)
2827

2928
val logger = options.logging.logger
30-
31-
val name = options.name.getOrElse {
32-
val baseName = (new Argv0).get("scala-cli")
33-
val idx = baseName.lastIndexOf(File.separator)
34-
if (idx < 0) baseName
35-
else baseName.drop(idx + 1)
36-
}
37-
38-
val format = options.format.map(_.trim).filter(_.nonEmpty)
39-
.orElse {
40-
Option(System.getenv("SHELL")).map(_.split(File.separator).last).map {
41-
case "bash" => Bash.id
42-
case "zsh" => Zsh.id
43-
case other => other
44-
}
45-
}
46-
.getOrElse {
47-
val msg = "Cannot determine current shell. Which would you like to use?"
48-
interactive.chooseOne(msg, List("zsh", "bash")).getOrElse {
49-
System.err.println(
50-
"Cannot determine current shell, pass the shell you use with --shell, like"
51-
)
52-
System.err.println(s" $name install completions --shell zsh")
53-
System.err.println(s" $name install completions --shell bash")
54-
sys.exit(1)
55-
}
29+
val name = getName(options.name)
30+
val format = getFormat(options.format).getOrElse {
31+
val msg = "Cannot determine current shell. Which would you like to use?"
32+
interactive.chooseOne(msg, List("zsh", "bash")).getOrElse {
33+
System.err.println(
34+
"Cannot determine current shell, pass the shell you use with --shell, like"
35+
)
36+
System.err.println(s"$name install completions --shell zsh")
37+
System.err.println(s"$name install completions --shell bash")
38+
sys.exit(1)
5639
}
40+
}
5741

5842
val (rcScript, defaultRcFile) = format match {
5943
case Bash.id | "bash" =>
6044
val script = Bash.script(name)
61-
val defaultRcFile = home / ".bashrc"
45+
val defaultRcFile = os.home / ".bashrc"
6246
(script, defaultRcFile)
6347
case Zsh.id | "zsh" =>
6448
val completionScript = Zsh.script(name)
6549
val zDotDir = Option(System.getenv("ZDOTDIR"))
6650
.map(os.Path(_, os.pwd))
67-
.getOrElse(home)
51+
.getOrElse(os.home)
6852
val defaultRcFile = zDotDir / ".zshrc"
6953
val dir = completionsDir / "zsh"
7054
val completionScriptDest = dir / s"_$name"
@@ -88,13 +72,8 @@ object InstallCompletions extends ScalaCommand[InstallCompletionsOptions] {
8872
if (options.env)
8973
println(rcScript)
9074
else {
91-
92-
val rcFile = options.rcFile
93-
.map(os.Path(_, os.pwd))
94-
.getOrElse(defaultRcFile)
95-
75+
val rcFile = options.rcFile.map(os.Path(_, os.pwd)).getOrElse(defaultRcFile)
9676
val banner = options.banner.replace("{NAME}", name)
97-
9877
val updated = ProfileFileUpdater.addToProfileFile(
9978
rcFile.toNIO,
10079
banner,
@@ -114,4 +93,22 @@ object InstallCompletions extends ScalaCommand[InstallCompletionsOptions] {
11493
System.err.println(s"$rcFile already up-to-date")
11594
}
11695
}
96+
97+
def getName(name: Option[String]) =
98+
name.getOrElse {
99+
val baseName = (new Argv0).get("scala-cli")
100+
val idx = baseName.lastIndexOf(File.separator)
101+
if (idx < 0) baseName
102+
else baseName.drop(idx + 1)
103+
}
104+
105+
def getFormat(format: Option[String]) =
106+
format.map(_.trim).filter(_.nonEmpty)
107+
.orElse {
108+
Option(System.getenv("SHELL")).map(_.split(File.separator).last).map {
109+
case "bash" => Bash.id
110+
case "zsh" => Zsh.id
111+
case other => other
112+
}
113+
}
117114
}

modules/cli/src/main/scala/scala/cli/commands/Uninstall.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ object Uninstall extends ScalaCommand[UninstallOptions] {
3535
interactive.confirmOperation(msg).getOrElse(fallbackAction())
3636
}
3737
if (os.exists(destBinPath)) {
38+
// uninstall completions
39+
logger.debug("Uninstalling completions...")
40+
UninstallCompletions.run(
41+
UninstallCompletionsOptions(options.sharedUninstallCompletions, options.bloopExit.logging),
42+
args
43+
)
3844
// exit bloop server
3945
logger.debug("Stopping Bloop server...")
4046
BloopExit.run(options.bloopExit, args)
@@ -44,7 +50,7 @@ object Uninstall extends ScalaCommand[UninstallOptions] {
4450
// remove scala-cli caches
4551
logger.debug("Removing scala-cli cache directory...")
4652
if (!options.skipCache) os.remove.all(cacheDir)
47-
logger.message("Uninstalled sucessfully.")
53+
logger.message("Uninstalled successfully.")
4854
}
4955
else {
5056
logger.error(s"Could't find scala-cli binary at $destBinPath.")
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package scala.cli.commands
2+
3+
import caseapp._
4+
5+
import java.nio.charset.Charset
6+
7+
import scala.cli.CurrentParams
8+
import scala.cli.commands.util.CommonOps._
9+
import scala.cli.internal.ProfileFileUpdater
10+
11+
object UninstallCompletions extends ScalaCommand[UninstallCompletionsOptions] {
12+
override def names = List(
13+
List("uninstall", "completions"),
14+
List("uninstall-completions")
15+
)
16+
def run(options: UninstallCompletionsOptions, args: RemainingArgs) = {
17+
CurrentParams.verbosity = options.logging.verbosity
18+
val logger = options.logging.logger
19+
val name = InstallCompletions.getName(options.shared.name)
20+
21+
val zDotDir = Option(System.getenv("ZDOTDIR"))
22+
.map(os.Path(_, os.pwd))
23+
.getOrElse(os.home)
24+
val rcFiles = options.shared.rcFile.map(file => Seq(os.Path(file, os.pwd))).getOrElse(Seq(
25+
zDotDir / ".zshrc",
26+
os.home / ".bashrc"
27+
)).filter(os.exists(_))
28+
29+
rcFiles.foreach { rcFile =>
30+
val banner = options.shared.banner.replace("{NAME}", name)
31+
32+
val updated = ProfileFileUpdater.removeFromProfileFile(
33+
rcFile.toNIO,
34+
banner,
35+
Charset.defaultCharset()
36+
)
37+
38+
if (options.logging.verbosity >= 0)
39+
if (updated) {
40+
logger.message(s"Updated $rcFile")
41+
logger.message("scala-cli completions uninstalled successfully")
42+
}
43+
else
44+
logger.error("Problem occurred while uninstalling scala-cli completions")
45+
}
46+
}
47+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package scala.cli.integration
2+
3+
import com.eed3si9n.expecty.Expecty.expect
4+
import coursier.cache.shaded.dirs.ProjectDirectories
5+
6+
import scala.util.Properties
7+
8+
class InstallAndUninstallCompletionsTests extends munit.FunSuite {
9+
val zshRcFile = ".zshrc"
10+
val bashRcFile = ".bashrc"
11+
val rcContent = s"""
12+
|dummy line
13+
|dummy line""".stripMargin
14+
val testInputs = TestInputs(
15+
Seq(
16+
os.rel / zshRcFile -> rcContent,
17+
os.rel / bashRcFile -> rcContent
18+
)
19+
)
20+
21+
def runInstallAndUninstallCompletions(): Unit = {
22+
testInputs.fromRoot { root =>
23+
val zshRcPath = root / zshRcFile
24+
val bashRcPath = root / bashRcFile
25+
// install completions to the dummy rc files
26+
os.proc(TestUtil.cli, "install-completions", "--rc-file", zshRcPath, "--shell", "zsh").call(
27+
cwd = root
28+
)
29+
os.proc(TestUtil.cli, "install-completions", "--rc-file", bashRcPath, "--shell", "bash").call(
30+
cwd = root
31+
)
32+
expect(os.read(bashRcPath).contains(bashRcScript))
33+
expect(os.read(zshRcPath).contains(zshRcScript))
34+
// uninstall completions from the dummy rc files
35+
os.proc(TestUtil.cli, "uninstall-completions", "--rc-file", zshRcPath).call(cwd = root)
36+
os.proc(TestUtil.cli, "uninstall-completions", "--rc-file", bashRcPath).call(cwd = root)
37+
expect(os.read(zshRcPath) == rcContent)
38+
expect(os.read(bashRcPath) == rcContent)
39+
}
40+
}
41+
42+
if (!Properties.isWin)
43+
test("installing and uninstalling completions") {
44+
runInstallAndUninstallCompletions()
45+
}
46+
47+
lazy val bashRcScript = {
48+
val progName = "scala-cli"
49+
val ifs = "\\n"
50+
val script =
51+
s"""_${progName}_completions() {
52+
| local IFS=$$'$ifs'
53+
| eval "$$($progName complete bash-v1 "$$(( $$COMP_CWORD + 1 ))" "$${COMP_WORDS[@]}")"
54+
|}
55+
|
56+
|complete -F _${progName}_completions $progName
57+
|""".stripMargin
58+
addTags(script)
59+
}
60+
61+
lazy val zshRcScript = {
62+
val projDirs = ProjectDirectories.from(null, null, "ScalaCli")
63+
val dir = os.Path(projDirs.dataLocalDir, TestUtil.pwd) / "completions" / "zsh"
64+
val script = Seq(
65+
s"""fpath=("$dir" $$fpath)""",
66+
"compinit"
67+
).map(_ + System.lineSeparator()).mkString
68+
addTags(script)
69+
}
70+
71+
def addTags(script: String): String = {
72+
val start = "# >>> scala-cli completions >>>\n"
73+
val end = "# <<< scala-cli completions <<<\n"
74+
val withTags = "\n" + start + script.stripSuffix("\n") + "\n" + end
75+
withTags
76+
}
77+
}

modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,9 @@ object TestUtil {
117117
else
118118
os.proc("kill", pid).call()
119119

120+
def pwd =
121+
if (Properties.isWin)
122+
os.Path(os.pwd.toIO.getCanonicalFile)
123+
else
124+
os.pwd
120125
}

website/docs/reference/cli-options.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ Available in commands:
526526
- [`shebang`](./commands.md#shebang)
527527
- [`test`](./commands.md#test)
528528
- [`uninstall`](./commands.md#uninstall)
529+
- [`uninstall completions` / `uninstall-completions`](./commands.md#uninstall-completions)
529530
- [`update`](./commands.md#update)
530531
- [`version`](./commands.md#version)
531532

@@ -749,6 +750,7 @@ Available in commands:
749750
- [`shebang`](./commands.md#shebang)
750751
- [`test`](./commands.md#test)
751752
- [`uninstall`](./commands.md#uninstall)
753+
- [`uninstall completions` / `uninstall-completions`](./commands.md#uninstall-completions)
752754

753755

754756
<!-- Automatically generated, DO NOT EDIT MANUALLY -->
@@ -1648,6 +1650,27 @@ Binary name
16481650

16491651
Binary directory
16501652

1653+
## Uninstall completions options
1654+
1655+
Available in commands:
1656+
- [`uninstall`](./commands.md#uninstall)
1657+
- [`uninstall completions` / `uninstall-completions`](./commands.md#uninstall-completions)
1658+
1659+
1660+
<!-- Automatically generated, DO NOT EDIT MANUALLY -->
1661+
1662+
#### `--rc-file`
1663+
1664+
Path to `*rc` file, defaults to `.bashrc` or `.zshrc` depending on shell
1665+
1666+
#### `--banner`
1667+
1668+
Custom banner in comment placed in rc file
1669+
1670+
#### `--name`
1671+
1672+
Custom completions name
1673+
16511674
## Update options
16521675

16531676
Available in commands:
@@ -1708,6 +1731,7 @@ Available in commands:
17081731
- [`shebang`](./commands.md#shebang)
17091732
- [`test`](./commands.md#test)
17101733
- [`uninstall`](./commands.md#uninstall)
1734+
- [`uninstall completions` / `uninstall-completions`](./commands.md#uninstall-completions)
17111735
- [`update`](./commands.md#update)
17121736
- [`version`](./commands.md#version)
17131737

0 commit comments

Comments
 (0)