Skip to content

Commit ff6dce9

Browse files
authored
Merge pull request #3237 from scala-steward-org/topic/multiple-non-local-repos-files
Allow multiple and non-local repos files
2 parents 9643355 + 674fc4f commit ff6dce9

File tree

10 files changed

+106
-32
lines changed

10 files changed

+106
-32
lines changed

docs/help.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All command line arguments for the `scala-steward` application.
55
```
66
Usage:
77
scala-steward validate-repo-config
8-
scala-steward --workspace <file> --repos-file <file> [--git-author-name <string>] --git-author-email <string> [--git-author-signing-key <string>] --git-ask-pass <file> [--sign-commits] [--forge-type <forge-type>] [--forge-api-host <uri>] --forge-login <string> [--do-not-fork] [--add-labels] [--ignore-opts-files] [--env-var <name=value>]... [--process-timeout <duration>] [--whitelist <string>]... [--read-only <string>]... [--enable-sandbox | --disable-sandbox] [--max-buffer-size <integer>] [--repo-config <uri>]... [--disable-default-repo-config] [--scalafix-migrations <uri>]... [--disable-default-scalafix-migrations] [--artifact-migrations <uri>]... [--disable-default-artifact-migrations] [--cache-ttl <duration>] [--bitbucket-use-default-reviewers] [--bitbucket-server-use-default-reviewers] [--gitlab-merge-when-pipeline-succeeds] [--gitlab-required-reviewers <integer>] [--gitlab-remove-source-branch] [--azure-repos-organization <string>] [--github-app-id <integer> --github-app-key-file <file>] [--url-checker-test-url <uri>]... [--default-maven-repo <string>] [--refresh-backoff-period <duration>]
8+
scala-steward --workspace <file> --repos-file <uri> [--repos-file <uri>]... [--git-author-name <string>] --git-author-email <string> [--git-author-signing-key <string>] --git-ask-pass <file> [--sign-commits] [--forge-type <forge-type>] [--forge-api-host <uri>] --forge-login <string> [--do-not-fork] [--add-labels] [--ignore-opts-files] [--env-var <name=value>]... [--process-timeout <duration>] [--whitelist <string>]... [--read-only <string>]... [--enable-sandbox | --disable-sandbox] [--max-buffer-size <integer>] [--repo-config <uri>]... [--disable-default-repo-config] [--scalafix-migrations <uri>]... [--disable-default-scalafix-migrations] [--artifact-migrations <uri>]... [--disable-default-artifact-migrations] [--cache-ttl <duration>] [--bitbucket-use-default-reviewers] [--bitbucket-server-use-default-reviewers] [--gitlab-merge-when-pipeline-succeeds] [--gitlab-required-reviewers <integer>] [--gitlab-remove-source-branch] [--azure-repos-organization <string>] [--github-app-id <integer> --github-app-key-file <file>] [--url-checker-test-url <uri>]... [--default-maven-repo <string>] [--refresh-backoff-period <duration>]
99
1010
1111
@@ -14,8 +14,8 @@ Options and flags:
1414
Display this help text.
1515
--workspace <file>
1616
Location for cache and temporary files
17-
--repos-file <file>
18-
A markdown formatted file with a repository list
17+
--repos-file <uri>
18+
A markdown formatted file with a repository list (can be used multiple times)
1919
--git-author-name <string>
2020
Git "user.name"; default: Scala Steward
2121
--git-author-email <string>

modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ object Cli {
7575
private val workspace: Opts[File] =
7676
option[File]("workspace", "Location for cache and temporary files")
7777

78-
private val reposFile: Opts[File] =
79-
option[File]("repos-file", "A markdown formatted file with a repository list")
78+
private val reposFiles: Opts[Nel[Uri]] =
79+
options[Uri]("repos-file", s"A markdown formatted file with a repository list $multiple")
8080

8181
private val gitAuthorName: Opts[String] = {
8282
val default = "Scala Steward"
@@ -348,7 +348,7 @@ object Cli {
348348

349349
private val configOpts: Opts[Config] = (
350350
workspace,
351-
reposFile,
351+
reposFiles,
352352
gitCfg,
353353
forgeCfg,
354354
ignoreOptsFiles,

modules/core/src/main/scala/org/scalasteward/core/application/Config.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import scala.concurrent.duration.FiniteDuration
5353
*/
5454
final case class Config(
5555
workspace: File,
56-
reposFile: File,
56+
reposFiles: Nel[Uri],
5757
gitCfg: GitCfg,
5858
forgeCfg: ForgeCfg,
5959
ignoreOptsFiles: Boolean,

modules/core/src/main/scala/org/scalasteward/core/application/Context.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ final class Context[F[_]](implicit
7575
val refreshErrorAlg: RefreshErrorAlg[F],
7676
val repoCacheAlg: RepoCacheAlg[F],
7777
val repoConfigAlg: RepoConfigAlg[F],
78+
val reposFilesLoader: ReposFilesLoader[F],
7879
val sbtAlg: SbtAlg[F],
7980
val scalaCliAlg: ScalaCliAlg[F],
8081
val scalafixMigrationsFinder: ScalafixMigrationsFinder,
@@ -234,6 +235,7 @@ object Context {
234235
implicit val editAlg: EditAlg[F] = new EditAlg[F]
235236
implicit val nurtureAlg: NurtureAlg[F] = new NurtureAlg[F](config.forgeCfg)
236237
implicit val pruningAlg: PruningAlg[F] = new PruningAlg[F]
238+
implicit val reposFilesLoader: ReposFilesLoader[F] = new ReposFilesLoader[F]()
237239
implicit val gitHubAppApiAlg: GitHubAppApiAlg[F] =
238240
new GitHubAppApiAlg[F](config.forgeCfg.apiHost)
239241
implicit val stewardAlg: StewardAlg[F] = new StewardAlg[F](config)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2018-2023 Scala Steward contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.scalasteward.core.application
18+
19+
import cats.effect.Sync
20+
import cats.syntax.all._
21+
import fs2.Stream
22+
import org.http4s.Uri
23+
import org.scalasteward.core.data.Repo
24+
import org.scalasteward.core.io.FileAlg
25+
import org.scalasteward.core.util.Nel
26+
import org.typelevel.log4cats.Logger
27+
28+
final class ReposFilesLoader[F[_]](implicit
29+
fileAlg: FileAlg[F],
30+
logger: Logger[F],
31+
F: Sync[F]
32+
) {
33+
def loadAll(reposFiles: Nel[Uri]): Stream[F, Repo] =
34+
Stream.emits(reposFiles.toList).evalMap(loadRepos).flatMap(Stream.emits)
35+
36+
private def loadRepos(reposFile: Uri): F[List[Repo]] =
37+
for {
38+
_ <- logger.debug(s"Loading repos from $reposFile")
39+
content <- fileAlg.readUri(reposFile)
40+
repos <- Stream.fromIterator(content.linesIterator, 4096).mapFilter(Repo.parse).compile.toList
41+
_ <-
42+
if (repos.nonEmpty) F.unit
43+
else {
44+
val msg = s"No repos found in ${reposFile.renderString}. " +
45+
s"Check the formatting of that file. " +
46+
s"""The format is "- $$owner/$$repo" or "- $$owner/$$repo:$$branch"."""
47+
logger.warn(msg)
48+
}
49+
} yield repos
50+
}

modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.scalasteward.core.application
1818

19-
import better.files.File
2019
import cats.effect.{ExitCode, Sync}
2120
import cats.syntax.all._
2221
import fs2.Stream
@@ -43,16 +42,11 @@ final class StewardAlg[F[_]](config: Config)(implicit
4342
nurtureAlg: NurtureAlg[F],
4443
pruningAlg: PruningAlg[F],
4544
repoCacheAlg: RepoCacheAlg[F],
45+
reposFilesLoader: ReposFilesLoader[F],
4646
selfCheckAlg: SelfCheckAlg[F],
4747
workspaceAlg: WorkspaceAlg[F],
4848
F: Sync[F]
4949
) {
50-
private def readRepos(reposFile: File): Stream[F, Repo] =
51-
Stream
52-
.eval(fileAlg.readFile(reposFile).map(_.getOrElse("")))
53-
.flatMap(content => Stream.fromIterator(content.linesIterator, 1024))
54-
.mapFilter(Repo.parse)
55-
5650
private def getGitHubAppRepos(githubApp: GitHubApp): Stream[F, Repo] =
5751
Stream.evals[F, List, Repo] {
5852
for {
@@ -95,22 +89,16 @@ final class StewardAlg[F[_]](config: Config)(implicit
9589
_ <- workspaceAlg.removeAnyRunSpecificFiles
9690
exitCode <-
9791
(config.githubApp.map(getGitHubAppRepos).getOrElse(Stream.empty) ++
98-
readRepos(config.reposFile))
92+
reposFilesLoader.loadAll(config.reposFiles))
9993
.evalMap(repo => steward(repo).map(_.bimap(repo -> _, _ => repo)))
10094
.compile
10195
.toList
102-
.flatMap {
103-
case Nil =>
104-
val msg = "No repos specified. " +
105-
s"Check the formatting of ${config.reposFile.pathAsString}. " +
106-
s"""The format is "- $$owner/$$repo" or "- $$owner/$$repo:$$branch"."""
107-
logger.warn(msg).as(ExitCode.Success)
108-
case results =>
109-
val runResults = RunResults(results)
110-
for {
111-
summaryFile <- workspaceAlg.runSummaryFile
112-
_ <- fileAlg.writeFile(summaryFile, runResults.markdownSummary)
113-
} yield runResults.exitCode
96+
.flatMap { results =>
97+
val runResults = RunResults(results)
98+
for {
99+
summaryFile <- workspaceAlg.runSummaryFile
100+
_ <- fileAlg.writeFile(summaryFile, runResults.markdownSummary)
101+
} yield runResults.exitCode
114102
}
115103
} yield exitCode
116104
}

modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.scalasteward.core.application.Cli.ParseResult._
99
import org.scalasteward.core.application.Config.StewardUsage
1010
import org.scalasteward.core.forge.ForgeType
1111
import org.scalasteward.core.forge.github.GitHubApp
12+
import org.scalasteward.core.util.Nel
1213
import scala.concurrent.duration._
1314

1415
class CliTest extends FunSuite {
@@ -38,7 +39,7 @@ class CliTest extends FunSuite {
3839
)
3940

4041
assertEquals(obtained.workspace, File("a"))
41-
assertEquals(obtained.reposFile, File("b"))
42+
assertEquals(obtained.reposFiles, Nel.one(uri"b"))
4243
assertEquals(obtained.gitCfg.gitAuthor.email, "d")
4344
assertEquals(obtained.gitCfg.gitAskPass, File("f"))
4445
assertEquals(obtained.forgeCfg.tpe, ForgeType.GitLab)
@@ -84,7 +85,7 @@ class CliTest extends FunSuite {
8485

8586
assert(!obtained.processCfg.sandboxCfg.enableSandbox)
8687
assertEquals(obtained.workspace, File("a"))
87-
assertEquals(obtained.reposFile, File("b"))
88+
assertEquals(obtained.reposFiles, Nel.one(uri"b"))
8889
assertEquals(obtained.gitCfg.gitAuthor.email, "d")
8990
assertEquals(obtained.gitCfg.gitAskPass, File("f"))
9091
assertEquals(obtained.forgeCfg.login, "e")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.scalasteward.core.application
2+
3+
import munit.CatsEffectSuite
4+
import org.scalasteward.core.data.Repo
5+
import org.scalasteward.core.git.Branch
6+
import org.scalasteward.core.mock.MockContext.context.reposFilesLoader
7+
import org.scalasteward.core.mock.{MockConfig, MockState}
8+
import org.scalasteward.core.util.Nel
9+
10+
class ReposFilesLoaderTest extends CatsEffectSuite {
11+
test("non-empty repos file") {
12+
val initialState = MockState.empty.addUris(MockConfig.reposFile -> "- a/b\n- c/d:e")
13+
val obtained =
14+
reposFilesLoader.loadAll(Nel.one(MockConfig.reposFile)).compile.toList.runA(initialState)
15+
assertIO(obtained, List(Repo("a", "b"), Repo("c", "d", Some(Branch("e")))))
16+
}
17+
18+
test("malformed repos file") {
19+
val initialState = MockState.empty.addUris(MockConfig.reposFile -> " - a/b")
20+
val obtained =
21+
reposFilesLoader.loadAll(Nel.one(MockConfig.reposFile)).compile.toList.runA(initialState)
22+
assertIO(obtained, List.empty)
23+
}
24+
25+
test("non-existing repos file") {
26+
val initialState = MockState.empty
27+
val obtained =
28+
reposFilesLoader.loadAll(Nel.one(MockConfig.reposFile)).compile.toList.runA(initialState)
29+
assertIO(obtained.attempt.map(_.isLeft), true)
30+
}
31+
}

modules/core/src/test/scala/org/scalasteward/core/application/StewardAlgTest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package org.scalasteward.core.application
33
import cats.effect.ExitCode
44
import munit.CatsEffectSuite
55
import org.scalasteward.core.mock.MockContext.context.stewardAlg
6-
import org.scalasteward.core.mock.MockState
6+
import org.scalasteward.core.mock.{MockConfig, MockState}
77

88
class StewardAlgTest extends CatsEffectSuite {
99
test("runF") {
10-
val exitCode = stewardAlg.runF.runA(MockState.empty)
10+
val exitCode = stewardAlg.runF.runA(MockState.empty.addUris(MockConfig.reposFile -> ""))
1111
assertIO(exitCode, ExitCode.Success)
1212
}
1313
}

modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package org.scalasteward.core.mock
22

33
import better.files.File
4+
import org.http4s.Uri
45
import org.scalasteward.core.application.Cli.ParseResult.Success
56
import org.scalasteward.core.application.{Cli, Config}
67

78
object MockConfig {
89
val mockRoot: File = File.temp / "scala-steward"
10+
val reposFile: Uri = Uri.unsafeFromString((mockRoot / "repos.md").pathAsString)
911
mockRoot.delete(true) // Ensure folder is cleared of previous test files
1012
private val args: List[String] = List(
1113
s"--workspace=$mockRoot/workspace",
12-
s"--repos-file=$mockRoot/repos.md",
14+
s"--repos-file=$reposFile",
1315
"--git-author-name=Bot Doe",
1416
1517
s"--git-ask-pass=$mockRoot/askpass.sh",

0 commit comments

Comments
 (0)