-- | References to things on Unison Share, as written in the @share.*@ commands.
module Unison.Cli.Share.Ref
  ( ShareBranch (..),
    ShareRef (..),
    parseShareRef,
    SearchFilter (..),
    parseSearchWords,
  )
where

import Data.Text qualified as Text
import Unison.Prelude

-- | Which branch (or release) of a project to look at.
data ShareBranch
  = -- | The latest release, or the default branch if there are no releases.
    ShareLatest
  | -- | A release, by version, e.g. @2.1.2@ for @\@user/project/releases/2.1.2@.
    ShareRelease Text
  | ShareBranchNamed Text
  deriving stock (ShareBranch -> ShareBranch -> Bool
(ShareBranch -> ShareBranch -> Bool)
-> (ShareBranch -> ShareBranch -> Bool) -> Eq ShareBranch
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ShareBranch -> ShareBranch -> Bool
== :: ShareBranch -> ShareBranch -> Bool
$c/= :: ShareBranch -> ShareBranch -> Bool
/= :: ShareBranch -> ShareBranch -> Bool
Eq, Int -> ShareBranch -> ShowS
[ShareBranch] -> ShowS
ShareBranch -> String
(Int -> ShareBranch -> ShowS)
-> (ShareBranch -> String)
-> ([ShareBranch] -> ShowS)
-> Show ShareBranch
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ShareBranch -> ShowS
showsPrec :: Int -> ShareBranch -> ShowS
$cshow :: ShareBranch -> String
show :: ShareBranch -> String
$cshowList :: [ShareBranch] -> ShowS
showList :: [ShareBranch] -> ShowS
Show)

-- | @\@user/project[/branch]:path.to.definition@
data ShareRef = ShareRef
  { -- | The owner's handle, without the leading @\@@.
    ShareRef -> Text
handle :: Text,
    ShareRef -> Text
slug :: Text,
    ShareRef -> ShareBranch
branch :: ShareBranch,
    ShareRef -> Maybe Text
path :: Maybe Text
  }
  deriving stock (ShareRef -> ShareRef -> Bool
(ShareRef -> ShareRef -> Bool)
-> (ShareRef -> ShareRef -> Bool) -> Eq ShareRef
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ShareRef -> ShareRef -> Bool
== :: ShareRef -> ShareRef -> Bool
$c/= :: ShareRef -> ShareRef -> Bool
/= :: ShareRef -> ShareRef -> Bool
Eq, Int -> ShareRef -> ShowS
[ShareRef] -> ShowS
ShareRef -> String
(Int -> ShareRef -> ShowS)
-> (ShareRef -> String) -> ([ShareRef] -> ShowS) -> Show ShareRef
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ShareRef -> ShowS
showsPrec :: Int -> ShareRef -> ShowS
$cshow :: ShareRef -> String
show :: ShareRef -> String
$cshowList :: [ShareRef] -> ShowS
showList :: [ShareRef] -> ShowS
Show)

-- | Parse @\@user/project[/branch]:path@, where the branch may be @releases/<version>@.
parseShareRef :: Text -> Either Text ShareRef
parseShareRef :: Text -> Either Text ShareRef
parseShareRef Text
input = do
  let (Text
projectPart, Text
pathPart) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
Text.breakOn Text
":" Text
input
  path <-
    case Text -> Text -> Maybe Text
Text.stripPrefix Text
":" Text
pathPart of
      Maybe Text
Nothing -> Maybe Text -> Either Text (Maybe Text)
forall a b. b -> Either a b
Right Maybe Text
forall a. Maybe a
Nothing
      Just Text
"" -> Text -> Either Text (Maybe Text)
forall a b. a -> Either a b
Left (Text
"Expected a name after the `:` in " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
input)
      Just Text
p -> Maybe Text -> Either Text (Maybe Text)
forall a b. b -> Either a b
Right (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
p)
  case Text.splitOn "/" projectPart of
    (Text -> Text -> Maybe Text
Text.stripPrefix Text
"@" -> Just Text
handle) : Text
slug : [Text]
rest
      | Bool -> Bool
not (Text -> Bool
Text.null Text
handle) Bool -> Bool -> Bool
&& Bool -> Bool
not (Text -> Bool
Text.null Text
slug) -> do
          branch <-
            case [Text]
rest of
              [] -> ShareBranch -> Either Text ShareBranch
forall a b. b -> Either a b
Right ShareBranch
ShareLatest
              [Text
"releases", Text
version] | Bool -> Bool
not (Text -> Bool
Text.null Text
version) -> ShareBranch -> Either Text ShareBranch
forall a b. b -> Either a b
Right (Text -> ShareBranch
ShareRelease Text
version)
              [Text
name] | Bool -> Bool
not (Text -> Bool
Text.null Text
name) Bool -> Bool -> Bool
&& Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= Text
"releases" -> ShareBranch -> Either Text ShareBranch
forall a b. b -> Either a b
Right (Text -> ShareBranch
ShareBranchNamed Text
name)
              [Text]
_ -> Text -> Either Text ShareBranch
forall a b. a -> Either a b
Left (Text
"I don't understand the branch in " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
input Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
". Use @user/project/branch or @user/project/releases/1.2.3")
          Right ShareRef {handle, slug, branch, path}
    [Text]
_ -> Text -> Either Text ShareRef
forall a b. a -> Either a b
Left (Text
"Expected a reference like @user/project, @user/project/releases/1.2.3 or @user/project:some.name, but got " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
input)

-- | Narrows a definition search to one user's projects, or to one project.
data SearchFilter
  = -- | @\@user@
    FilterUser Text
  | -- | @\@user/project@
    FilterProject Text
  deriving stock (SearchFilter -> SearchFilter -> Bool
(SearchFilter -> SearchFilter -> Bool)
-> (SearchFilter -> SearchFilter -> Bool) -> Eq SearchFilter
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SearchFilter -> SearchFilter -> Bool
== :: SearchFilter -> SearchFilter -> Bool
$c/= :: SearchFilter -> SearchFilter -> Bool
/= :: SearchFilter -> SearchFilter -> Bool
Eq, Int -> SearchFilter -> ShowS
[SearchFilter] -> ShowS
SearchFilter -> String
(Int -> SearchFilter -> ShowS)
-> (SearchFilter -> String)
-> ([SearchFilter] -> ShowS)
-> Show SearchFilter
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SearchFilter -> ShowS
showsPrec :: Int -> SearchFilter -> ShowS
$cshow :: SearchFilter -> String
show :: SearchFilter -> String
$cshowList :: [SearchFilter] -> ShowS
showList :: [SearchFilter] -> ShowS
Show)

-- | Split the words of a search into the query and an optional trailing @in \@user@ or @in \@user/project@.
parseSearchWords :: [Text] -> Either Text (Text, Maybe SearchFilter)
parseSearchWords :: [Text] -> Either Text (Text, Maybe SearchFilter)
parseSearchWords [Text]
words_ =
  case [Text] -> [Text]
forall a. [a] -> [a]
reverse [Text]
words_ of
    Text
target : Text
"in" : [Text]
queryBackwards
      | Just Text
names <- Text -> Text -> Maybe Text
Text.stripPrefix Text
"@" Text
target -> do
          filter_ <-
            case HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
Text.splitOn Text
"/" Text
names of
              [Text
user] | Bool -> Bool
not (Text -> Bool
Text.null Text
user) -> SearchFilter -> Either Text SearchFilter
forall a b. b -> Either a b
Right (Text -> SearchFilter
FilterUser Text
target)
              [Text
user, Text
project] | Bool -> Bool
not (Text -> Bool
Text.null Text
user) Bool -> Bool -> Bool
&& Bool -> Bool
not (Text -> Bool
Text.null Text
project) -> SearchFilter -> Either Text SearchFilter
forall a b. b -> Either a b
Right (Text -> SearchFilter
FilterProject Text
target)
              [Text]
_ -> Text -> Either Text SearchFilter
forall a b. a -> Either a b
Left (Text
"Expected @user or @user/project after `in`, but got " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
target)
          when (null queryBackwards) (Left "Expected something to search for before `in`")
          Right (Text.unwords (reverse queryBackwards), Just filter_)
    [Text]
_ -> (Text, Maybe SearchFilter)
-> Either Text (Text, Maybe SearchFilter)
forall a b. b -> Either a b
Right ([Text] -> Text
Text.unwords [Text]
words_, Maybe SearchFilter
forall a. Maybe a
Nothing)