Skip to content

Commit 65af18e

Browse files
⅄ trunk → merge-no-output
2 parents 99a307a + 9017daa commit 65af18e

File tree

11 files changed

+127
-15
lines changed

11 files changed

+127
-15
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ jobs:
231231
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
232232
with:
233233
path: ${{env.transcript_test_results}}
234-
key: transcripts-results-${{ matrix.os }}-${{ hashFiles('**/ci.yaml', '**/stack.yaml', '**/package.yaml', '**/*.hs')}}-${{ hashFiles('**/unison-src/**/*.md', '**/unison-src/**/*.u') }}
234+
key: transcripts-results-${{ matrix.os }}-${{ hashFiles('**/ci.yaml', '**/stack.yaml', '**/package.yaml', '**/*.hs')}}-${{ hashFiles('**/unison-src/**/*.md', '**/unison-src/**/*.u', '**/docs/*.md', '**/docs/*.markdown') }}
235235
- name: restore binaries
236236
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
237237
if: steps.cache-transcript-test-results.outputs.cache-hit != 'true'

docs/mcp.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ E.g. on Mac this is likely `/opt/homebrew/bin/ucm`, you can run `which ucm` to f
3030
"command": "<path-to-ucm>",
3131
"args": ["mcp"]
3232
}
33+
}
3334
}
3435
```
3536

36-
E.g. my complete file on Mac looks like this:
37+
_e.g._ my complete file on macOS looks like this:
3738

3839
``` json
3940
{

unison-cli/src/Unison/Codebase/Editor/HandleInput.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ loop e = do
194194
pp <- Cli.getCurrentProjectPath
195195
whenJust env.serverBaseUrl \baseUrl ->
196196
Cli.respond $
197-
PrintMessage $
197+
Literal $
198198
P.lines
199199
[ "The API information is as follows:",
200200
P.newline,
@@ -203,7 +203,7 @@ loop e = do
203203
P.indentN 2 (P.hiBlue ("API: " <> Pretty.text (Server.urlFor Server.Api baseUrl)))
204204
]
205205
CreateMessage pretty ->
206-
Cli.respond $ PrintMessage pretty
206+
Cli.respond $ Literal pretty
207207
ShowRootReflogI -> do
208208
let numEntriesToShow = 500
209209
(schLength, entries) <-

unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ evalUnisonFile mode ppe unisonFile args = do
452452
| not $ null errs ->
453453
False <$ RuntimeUtils.displayDecompileErrors errs
454454
Runtime.Profile prof ->
455-
True <$ Cli.respond (Output.PrintMessage prof)
455+
True <$ Cli.respond (Output.Literal prof)
456456
_ -> pure True
457457
for_ (Map.elems map) \(_loc, kind, hash, _src, value, isHit) -> do
458458
-- only update the watch cache when there are no errors

unison-cli/src/Unison/Codebase/Editor/HandleInput/Names.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ handleNames ::
3939
Cli ()
4040
handleNames _ (nameQuery, Left errMsg) = do
4141
Cli.respond $
42-
PrintMessage $
42+
Literal $
4343
P.lines [prettyNameQuery, errMsg]
4444
where
4545
prettyNameQuery =

unison-cli/src/Unison/Codebase/Editor/HandleInput/RuntimeUtils.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ modeProfSpec (Permissive prof) = prof
5050

5151
displayDecompileErrors :: [DecompError] -> Cli ()
5252
displayDecompileErrors =
53-
Cli.respond . PrintMessage . msg . fmap (P.indentN 2 . P.indentN 2 . renderDecompError)
53+
Cli.respond . Literal . msg . fmap (P.indentN 2 . P.indentN 2 . renderDecompError)
5454
where
5555
msg em = do
5656
P.lines $
@@ -97,7 +97,7 @@ evalUnisonTermE mode ppe useCache tm = do
9797
displayResponse :: Runtime.Response DecompError -> Cli ()
9898
displayResponse (Runtime.DecompErrs errs)
9999
| not $ null errs = displayDecompileErrors errs
100-
displayResponse (Runtime.Profile prof) = Cli.respond (PrintMessage msg)
100+
displayResponse (Runtime.Profile prof) = Cli.respond (Literal msg)
101101
where
102102
msg = P.lines ["Profile Results:", ""] <> prof
103103
displayResponse _ = pure ()

unison-cli/src/Unison/Codebase/Editor/Output.hs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,6 @@ data Output
178178
Success
179179
| -- User did `update` before typechecking a file?
180180
NoUnisonFile
181-
| -- Used in Welcome module to instruct user
182-
PrintMessage (P.Pretty P.ColorText)
183181
| InvalidSourceName String
184182
| SourceLoadFailed String
185183
| -- No main function, the [Type v Ann] are the allowed types
@@ -518,7 +516,6 @@ isFailure o = case o of
518516
SaveTermNameConflict {} -> True
519517
RunResult {} -> False
520518
Success {} -> False
521-
PrintMessage {} -> False
522519
NoUnisonFile {} -> True
523520
InvalidSourceName {} -> True
524521
SourceLoadFailed {} -> True

unison-cli/src/Unison/CommandLine/Main.hs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import System.Console.Haskeline.History qualified as Line
2121
import System.FSNotify qualified as FSNotify
2222
import System.IO (hGetEcho, hPutStrLn, hSetEcho, stderr, stdin)
2323
import System.IO.Error (isDoesNotExistError)
24+
import U.Codebase.Sqlite.Queries qualified as Queries
2425
import Unison.Auth.CredentialManager qualified as AuthN
2526
import Unison.Auth.HTTPClient (AuthenticatedHttpClient)
2627
import Unison.Auth.HTTPClient qualified as AuthN
@@ -45,6 +46,7 @@ import Unison.CommandLine.Welcome qualified as Welcome
4546
import Unison.Parser.Ann (Ann)
4647
import Unison.Prelude
4748
import Unison.PrettyTerminal
49+
import Unison.Project qualified as Project
4850
import Unison.Runtime (Runtime)
4951
import Unison.Runtime.IOSource qualified as IOSource
5052
import Unison.Server.CodebaseServer qualified as Server
@@ -172,8 +174,38 @@ main dir welcome ppIds initialInputs runtime sbRuntime codebase serverBaseUrl uc
172174
ShouldWatchFiles -> allow
173175
)
174176

177+
-- On startup, we tell the user about any existing project names that don't pass the new project name regex,
178+
-- which isn't enforced yet.
179+
180+
invalidProjectNamesInputs <- do
181+
projects <- Codebase.runTransaction codebase Queries.loadAllProjects
182+
let invalidProjectNames =
183+
mapMaybe
184+
( \project ->
185+
if Project.isValidNewProjectName project.name
186+
then Nothing
187+
else Just project.name
188+
)
189+
projects
190+
pure case invalidProjectNames of
191+
[] -> []
192+
_ ->
193+
[ Right . CreateMessage . P.warnCallout $
194+
P.wrap "We're updating UCM's project naming rules, and these names won’t be supported much longer:"
195+
<> P.newline
196+
<> P.newline
197+
<> P.group (P.commas (map P.prettyProjectName invalidProjectNames))
198+
<> P.newline
199+
<> P.newline
200+
<> P.wrap
201+
( "Please"
202+
<> IP.makeExample IP.projectRenameInputPattern []
203+
<> "them using only ASCII letters, numbers, and hyphens, of length 2-40 characters."
204+
)
205+
]
206+
175207
let initialState = Cli.loopState0 ppIds
176-
initialInputsRef <- newIORef $ Welcome.run welcome ++ initialInputs
208+
initialInputsRef <- newIORef $ Welcome.run welcome ++ initialInputs ++ invalidProjectNamesInputs
177209
pageOutput <- newIORef True
178210

179211
initialEcho <- hGetEcho stdin

unison-cli/src/Unison/CommandLine/OutputMessages.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,6 @@ notifyUser dir issueFn = \case
559559
<> "to evaluate something before attempting"
560560
<> "to save it."
561561
Success -> pure $ P.bold "Done."
562-
PrintMessage pretty -> pure pretty
563562
NamespaceEmpty p ->
564563
case p of
565564
(p0 NEList.:| []) ->

unison-core/src/Unison/Project.hs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module Unison.Project
99
projectNameUserSlug,
1010
projectNameToUserProjectSlugs,
1111
prependUserSlugToProjectName,
12+
isValidNewProjectName,
1213
ProjectBranchName,
1314
projectBranchNameUserSlug,
1415
projectBranchNameToValidProjectBranchNameText,
@@ -34,6 +35,7 @@ where
3435

3536
import Data.Char qualified as Char
3637
import Data.Kind (Type)
38+
import Data.Monoid qualified as Monoid
3739
import Data.Text qualified as Text
3840
import Data.Text.Read qualified as Text (decimal)
3941
import Data.These (These (..))
@@ -75,6 +77,56 @@ projectNameParser = do
7577
isStartChar c =
7678
Char.isAlpha c || c == '_'
7779

80+
-- Parse a project name, and whether it ended in a forward slash (which is, of course, not part of the name)
81+
newProjectNameParser :: Megaparsec.Parsec Void Text (ProjectName, Bool)
82+
newProjectNameParser = do
83+
userSlug <-
84+
asum
85+
[ do
86+
user <- userSlugParser
87+
pure (Text.Builder.char '@' <> user <> Text.Builder.char '/'),
88+
pure mempty
89+
]
90+
projectSlug <- projectSlugParser
91+
hasTrailingSlash <- isJust <$> optional (Megaparsec.char '/')
92+
pure (UnsafeProjectName (Text.Builder.run (userSlug <> projectSlug)), hasTrailingSlash)
93+
where
94+
-- Github project regular expression: ^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,39}$
95+
--
96+
-- In English: a-z or 0-9, followed by 1-39 repetitions of a-z or 0-9 or hyphen, with the restriction that any
97+
-- hyphen must be followed by a-z or 0-9
98+
--
99+
-- We implement that here, but with parser combinators: a-z or 0-9, followed by 1 or more chunks of [optional
100+
-- hyphen followed by 1 or more a-z or 0-9], checking length at the end
101+
projectSlugParser :: Megaparsec.Parsec Void Text Text.Builder
102+
projectSlugParser = do
103+
firstChar <- Megaparsec.satisfy isAsciiLowerOrDigit
104+
chunks <- some ((,) <$> optional (Megaparsec.char '-') <*> Megaparsec.takeWhile1P Nothing isAsciiLowerOrDigit)
105+
when (chunksLength chunks > 39) (fail "Project name must be 2-40 characters long.")
106+
pure $
107+
Text.Builder.char firstChar
108+
<> foldMap
109+
( \(maybeHyphen, chunk) ->
110+
maybe (mempty @Text.Builder) Text.Builder.char maybeHyphen <> Text.Builder.text chunk
111+
)
112+
chunks
113+
where
114+
isAsciiLowerOrDigit :: Char -> Bool
115+
isAsciiLowerOrDigit c =
116+
Char.isAsciiLower c || Char.isDigit c
117+
118+
chunksLength :: [(Maybe Char, Text)] -> Int
119+
chunksLength =
120+
Monoid.getSum . foldMap (Monoid.Sum . chunkLength)
121+
122+
chunkLength :: (Maybe Char, Text) -> Int
123+
chunkLength (maybeHyphen, chunk) =
124+
(if isJust maybeHyphen then 1 else 0) + Text.length chunk
125+
126+
isValidNewProjectName :: ProjectName -> Bool
127+
isValidNewProjectName (UnsafeProjectName projectName) =
128+
isRight (Megaparsec.parse newProjectNameParser "" projectName)
129+
78130
-- | Get the user slug at the beginning of a project name, if there is one.
79131
--
80132
-- >>> projectNameUserSlug "@arya/lens"

0 commit comments

Comments
 (0)