Skip to content

Commit

Permalink
Merge pull request #70 from imclerran/upgrade-feature
Browse files Browse the repository at this point in the history
Add ability to upgrade the dependencies of an existing app or package
  • Loading branch information
imclerran authored Dec 17, 2024
2 parents be170eb + 89f044b commit 3119fc7
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 39 deletions.
37 changes: 24 additions & 13 deletions src/ArgParser.roc
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module [parseOrDisplayMessage, baseUsage, extendedUsage]

import cli.Arg.Cli as Cli
import cli.Arg.Help as Help
import cli.Arg.Opt as Opt
import cli.Arg.Param as Param
import cli.Arg.SubCmd as Subcommand
import weaver.Cli
import weaver.Help
import weaver.Opt
import weaver.Param
import weaver.SubCmd

parseOrDisplayMessage = \args -> Cli.parseOrDisplayMessage cliParser args

Expand All @@ -26,9 +26,9 @@ extendedUsage =
Str.joinWith [usageHelpStr, extendedUsageStr] "\n\n"

cliParser =
{ Cli.combine <-
{ Cli.weave <-
update: Opt.flag { short: "u", long: "update", help: "Update the platform and package repositories." },
subcommand: Subcommand.optional [tuiSubcommand, updateSubcommand, appSubcommand, pkgSubcommand],
subcommand: SubCmd.optional [tuiSubcommand, updateSubcommand, appSubcommand, pkgSubcommand, upgradeSubcommand],
}
|> Cli.finish {
name: "roc-start",
Expand All @@ -40,41 +40,52 @@ cliParser =
|> Cli.assertValid

appSubcommand =
{ Cli.combine <-
{ Cli.weave <-
appName: Param.str { name: "app-name", help: "Name your new roc app." },
platform: Param.str { name: "platform", help: "The platform to use." },
packages: Param.strList { name: "packages", help: "Any packages to use." },
}
|> Subcommand.finish {
|> SubCmd.finish {
name: "app",
description: "Create a new roc app with the specified name, platform, and packages.",
mapper: App,
}

pkgSubcommand =
Param.strList { name: "packages", help: "Any packages to use." }
|> Subcommand.finish {
|> SubCmd.finish {
name: "pkg",
description: "Create a new roc package main file with any other specified packages dependencies.",
mapper: Pkg,
}

tuiSubcommand =
Opt.flag { short: "s", long: "secret" }
|> Subcommand.finish {
|> SubCmd.finish {
name: "tui",
description: "Use the TUI app to browse and search for platforms and packages.",
mapper: Tui,
}

updateSubcommand =
{ Cli.combine <-
{ Cli.weave <-
doPkgs: Opt.flag { short: "k", long: "packages", help: "Update the package repositories." },
doPfs: Opt.flag { short: "f", long: "platforms", help: "Update the platform repositories." },
doStubs: Opt.flag { short: "s", long: "app-stubs", help: "Update the app stubs." },
}
|> Subcommand.finish {
|> SubCmd.finish {
name: "update",
description: "Update the platform and package repositories and app stubs. Update all, or specify which to update.",
mapper: Update,
}

upgradeSubcommand =
{ Cli.weave <-
filename: Param.str { name: "filename", help: "The name of the file who's platforms and/or packages should be upgraded." },
toUpgrade: Param.strList { name: "to-upgrade", help: "List of platform and package names to upgrade. If ommitted, all will be upgraded." },
}
|> SubCmd.finish {
name: "upgrade",
description: "Upgrade the platform and/or packages in an app or package",
mapper: Upgrade,
}
181 changes: 155 additions & 26 deletions src/main.roc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ app [main] {
ansi: "https://github.com/lukewilliamboswell/roc-ansi/releases/download/0.7.0/NmbsrdwKIOb1DtUIV7L_AhCvTx7nhfaW3KkOpT7VUZg.tar.br",
json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.11.0/z45Wzc-J39TLNweQUoLw3IGZtkQiEN3lTBv3BXErRjQ.tar.br",
rvn: "https://github.com/jwoudenberg/rvn/releases/download/0.3.0/6AqhP_-5msgMDvUgoJF-aFwcFpFGCSzmvL3sghcXUXM.tar.br",
weaver: "https://github.com/smores56/weaver/releases/download/0.4.0/xgCr4fYD-5UsEArgh3kgk-JxqJcXBMbHlOb5jEl4yEk.tar.br",
}

import ArgParser
Expand Down Expand Up @@ -69,6 +70,10 @@ runWith = \args ->
runCliApp Pkg "main" "" packages args.update
|> Task.onErr \_ -> Task.err (Exit 1 "")

Ok (Upgrade { filename, toUpgrade }) ->
runUpgrades filename toUpgrade args.update
|> Task.onErr \_ -> Task.err (Exit 1 "")

Err NoSubcommand ->
Stdout.line! ArgParser.extendedUsage
Task.err (Exit 1 "")
Expand All @@ -78,19 +83,19 @@ runWith = \args ->
runCliApp : [App, Pkg], Str, Str, List Str, Bool -> Task {} _
runCliApp = \type, fileName, platform, packages, forceUpdate ->
loadRepoData forceUpdate
|> Task.attempt \reposRes ->
when reposRes is
Ok repos ->
getAppStubsIfNeeded! (Dict.keys repos.platforms) forceUpdate
fileExists = checkForFile! "$(fileName).roc"
if fileExists then
Stdout.line! "Error: $(fileName).roc already exists. $(redCross)"
Task.err (Exit 1 "")
else
createRocFile! { fileName, platform, packages, type } repos
Stdout.line! "Created $(fileName).roc $(greenCheck)"

Err e -> Task.err e
|> Task.attempt \reposRes ->
when reposRes is
Ok repos ->
getAppStubsIfNeeded! (Dict.keys repos.platforms) forceUpdate
fileExists = checkForFile! "$(fileName).roc"
if fileExists then
Stdout.line! "Error: $(fileName).roc already exists. $(redCross)"
Task.err (Exit 1 "")
else
createRocFile! { fileName, platform, packages, type } repos
Stdout.line! "Created $(fileName).roc $(greenCheck)"

Err e -> Task.err e

## Run the TUI application.
## Load the repository data, run the main tui loop, and create the roc file when the user confirms their selections.
Expand All @@ -104,7 +109,7 @@ runTuiApp = \forceUpdate, showSplash ->
Model.init (Dict.keys repos.platforms) (Dict.keys repos.packages) { state: Splash { config: Model.emptyAppConfig } }
else
Model.init (Dict.keys repos.platforms) (Dict.keys repos.packages) {}
model = Task.loop! initialModel runUiLoop #(Model.init (Dict.keys repos.platforms) (Dict.keys repos.packages) {}) runUiLoop
model = Task.loop! initialModel runUiLoop # (Model.init (Dict.keys repos.platforms) (Dict.keys repos.packages) {}) runUiLoop
Stdout.write! (ANSI.toStr Reset)
Tty.disableRawMode! {}
when model.state is
Expand Down Expand Up @@ -134,6 +139,127 @@ runUpdates = \doPfs, doPkgs, doStubs ->

_ -> Task.ok (Done {})

runUpgrades : Str, List Str, Bool -> Task {} _
runUpgrades = \filename, toUpgrade, forceUpdate ->
{ prefix, dependencies: depLines, rest: remainder } =
File.readBytes filename
|> Task.onErr! \_ -> Task.ok []
|> splitFile
|> Task.fromResult!

loadRepoData forceUpdate
|> Task.attempt \reposRes ->
when reposRes is
Ok repos ->
newDeps = Task.loop! { oldDeps: depLines, newDeps: [] } \{ oldDeps, newDeps } ->
when oldDeps is
[depLine, .. as rest] ->
newDep = upgradeDep! depLine toUpgrade repos
Task.ok (Step { oldDeps: rest, newDeps: List.append newDeps newDep })

[] ->
Task.ok (Done newDeps)
if List.len newDeps == List.len depLines then
dependencies =
newDeps
|> List.map \line -> List.append line '\n'
|> List.join
newFile = List.join [prefix, ['{', '\n'], dependencies, ['}'], remainder]
File.writeBytes! newFile filename
Task.ok {}
else
Task.ok {}

Err e -> Task.err e

splitFile : List U8 -> Result { prefix : List U8, dependencies : List (List U8), rest : List U8 } [NotFound]
splitFile = \bytes ->
{ before: prefix, after: most } = List.splitFirst? bytes '{'
{ before: deps, after: rest } = List.splitFirst? most '}'
dependencies =
deps
|> List.splitOn '\n'
|> List.dropIf \line -> line |> trimL |> List.isEmpty
|> justify

Ok { prefix, dependencies, rest }

justify : List (List U8) -> List (List U8)
justify = \lines ->
indent =
when lines is
[_, secondLine, ..] -> getIndent secondLine
[firstLine] ->
firstLine
|> getIndent
|> \i -> if List.len i < 2 then [' ', ' ', ' ', ' '] else i

[] -> []
lines
|> List.map \line ->
trimmed = trimL line
List.concat indent trimmed

trimL = \bytes ->
when bytes is
[' ', .. as rest] -> trimL rest
['\t', .. as rest] -> trimL rest
_ -> bytes

trimR = \bytes ->
when bytes is
[.. as rest, ' '] -> trimR rest
[.. as rest, '\t'] -> trimR rest
_ -> bytes

getIndent : List U8 -> List U8
getIndent = \bytes ->
whitespace = [' ', '\t']
List.walkUntil bytes [] \indent, byte ->
if List.contains whitespace byte then
Continue (List.append indent byte)
else
Break indent

upgradeDep : List U8, List Str, { packages : Dict Str RepositoryEntry, platforms : Dict Str RepositoryEntry } -> Task (List U8) _
upgradeDep = \depLine, toUpgrade, repos ->
isPlatform = \line ->
Str.fromUtf8 line
|> Result.withDefault ""
|> Str.contains " platform "
repo =
depLine
|> Str.fromUtf8
|> Result.withDefault ""
|> Str.splitOn "/"
|> List.get 4
|> Result.withDefault ""
if List.contains toUpgrade repo || List.isEmpty toUpgrade then
newUrl =
if isPlatform depLine then
Dict.get repos.platforms repo
|> Result.map \entry -> entry.url
|> Result.withDefault ""
else
Dict.get repos.packages repo
|> Result.map \entry -> entry.url
|> Result.withDefault ""
prefix =
depLine
|> trimR
|> List.splitLast ' '
|> Result.withDefault { before: [], after: [] }
|> \split -> split.before
|> Str.fromUtf8
|> Result.withDefault ""
newDep = "$(prefix) \"$(newUrl)\"," |> Str.toUtf8
if !(Str.isEmpty prefix) && !(Str.isEmpty newUrl) then
Task.ok newDep
else
Task.ok depLine
else
Task.ok depLine

## The main loop for running the TUI.
## Checks the terminal size, draws the screen, reads input, and handles the input.
runUiLoop : Model -> Task [Step Model, Done Model] _
Expand All @@ -155,8 +281,8 @@ getTerminalSize =
Stdout.write! cmd
# Read the cursor position
Stdin.bytes {}
|> Task.map ANSI.parseCursor
|> Task.map! \{ row, col } -> { width: col, height: row }
|> Task.map ANSI.parseCursor
|> Task.map! \{ row, col } -> { width: col, height: row }

## Generate the list of draw functions which will be used to draw the screen.
render : Model -> List ANSI.DrawFn
Expand Down Expand Up @@ -332,20 +458,20 @@ getAndCreateDataDir =
checkForDir : Str -> Task Bool _
checkForDir = \path ->
Path.isDir (Path.fromStr path)
|> Task.attempt! \res ->
when res is
Ok bool -> Task.ok bool
_ -> Task.ok Bool.false
|> Task.attempt! \res ->
when res is
Ok bool -> Task.ok bool
_ -> Task.ok Bool.false

## Check if a file exists at the given path.
## Guarantee Task.ok Bool result.
checkForFile : Str -> Task Bool _
checkForFile = \filename ->
Path.isFile (Path.fromStr filename)
|> Task.attempt! \res ->
when res is
Ok bool -> Task.ok bool
_ -> Task.ok Bool.false
|> Task.attempt! \res ->
when res is
Ok bool -> Task.ok bool
_ -> Task.ok Bool.false

## Load the repository data from the local cache. If the cache does not exist,
## or the user requests an update, fetch the latest data from the remote repository.
Expand Down Expand Up @@ -706,8 +832,8 @@ getAppStub : Str -> Task (List U8) _
getAppStub = \platform ->
dataDir = getAndCreateDataDir!
File.readBytes "$(dataDir)/app-stubs/$(platform)"
|> Task.onErr \_ -> Task.ok []
|> Task.map! \bytes -> if List.isEmpty bytes then bytes else List.prepend bytes '\n'
|> Task.onErr \_ -> Task.ok []
|> Task.map! \bytes -> if List.isEmpty bytes then bytes else List.prepend bytes '\n'

buildRocPackage : List Str, Dict Str RepositoryEntry -> List U8
buildRocPackage = \packageList, packageRepo ->
Expand All @@ -721,3 +847,6 @@ buildRocPackage = \packageList, packageRepo ->
else
"package [] {\n$(pkgsStr)}\n" |> Str.toUtf8

# getFileContents : Str -> Task (List Str) _
# getFileContents = \filename ->

0 comments on commit 3119fc7

Please sign in to comment.