diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..127b251 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,12 @@ + +# Created by https://www.gitignore.io/api/elm +# Edit at https://www.gitignore.io/?templates=elm + +### Elm ### +# elm-package generated files +elm-stuff +# elm-repl generated files +repl-temp-* + +# End of https://www.gitignore.io/api/elm +index.html diff --git a/frontend/elm.json b/frontend/elm.json new file mode 100644 index 0000000..0a95d46 --- /dev/null +++ b/frontend/elm.json @@ -0,0 +1,28 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.0", + "dependencies": { + "direct": { + "elm/browser": "1.0.1", + "elm/core": "1.0.2", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2" + } + }, + "test-dependencies": { + "direct": { + "elm-explorations/test": "1.2.2" + }, + "indirect": { + "elm/random": "1.0.0" + } + } +} diff --git a/frontend/src/Data.elm b/frontend/src/Data.elm new file mode 100644 index 0000000..25108b3 --- /dev/null +++ b/frontend/src/Data.elm @@ -0,0 +1,45 @@ +module Data exposing (Provider(..), Url, pathSeparator, toHost, toUrl) + + +hostname : String +hostname = + "https://cdn.hitsofcode.com/" + + +type Provider + = GitHub + | Bitbucket + + +type alias Url = + { prov : Provider + , user : String + , repo : String + , gitref : String + , file : String + } + + +toHost : Provider -> String +toHost prov = + case prov of + GitHub -> + "github/" + + Bitbucket -> + "bitbucket/" + + +pathSeparator : Provider -> String +pathSeparator prov = + case prov of + GitHub -> + "blob" + + Bitbucket -> + "src" + + +toUrl : Url -> String +toUrl { prov, user, repo, gitref, file } = + hostname ++ toHost prov ++ String.join "/" [ user, repo, pathSeparator prov, gitref, file ] diff --git a/frontend/src/Main.elm b/frontend/src/Main.elm new file mode 100644 index 0000000..c2e56aa --- /dev/null +++ b/frontend/src/Main.elm @@ -0,0 +1,98 @@ +module Main exposing (Model, Msg(..), init, main, update, view) + +import Browser +import Data exposing (Url, toHost, toUrl) +import Html exposing (Html, br, div, input, table, td, text, tr) +import Html.Attributes exposing (disabled, placeholder, style, value) +import Html.Events exposing (onInput) +import Parse exposing (parseUrl) + + +type Msg + = UrlChange String + + +type alias Model = + { url : String + , parsed : Maybe Url + } + + +init : Model +init = + { url = "" + , parsed = Nothing + } + + +update : Msg -> Model -> Model +update msg state = + case msg of + UrlChange newUrl -> + { state | url = newUrl, parsed = parseUrl newUrl } + + +renderUrl : Url -> Html msg +renderUrl { prov, user, repo, file } = + div myStyle + [ table myStyle + [ tr myStyle + [ td myStyle [ text "host" ] + , td myStyle [ text (toHost prov) ] + ] + , tr [] + [ td myStyle [ text "user" ] + , td myStyle [ text user ] + ] + , tr myStyle + [ td myStyle [ text "repo" ] + , td myStyle [ text repo ] + ] + , tr myStyle + [ td myStyle [ text "file" ] + , td myStyle [ text file ] + ] + ] + ] + + +renderMUrl : Maybe Url -> Html msg +renderMUrl mUrl = + mUrl + |> Maybe.map renderUrl + |> Maybe.withDefault (div myStyle [ text "Parse Error" ]) + + +displayMUrl : Maybe Url -> String +displayMUrl mUrl = + mUrl + |> Maybe.map toUrl + |> Maybe.withDefault "" + + +myStyle : List (Html.Attribute msg) +myStyle = + [ style "width" "100%" ] + + +myStyle2 : List (Html.Attribute msg) -> List (Html.Attribute msg) +myStyle2 = + List.append myStyle + + +view : Model -> Html Msg +view state = + div myStyle + [ input (myStyle2 [ placeholder "URL to parse", value state.url, onInput UrlChange ]) [] + , div myStyle + [ text "Parsed URL: " + , br [] [] + , renderMUrl state.parsed + ] + , input (myStyle2 [ placeholder "https://host/////", disabled True, value (displayMUrl state.parsed) ]) [] + ] + + +main : Program () Model Msg +main = + Browser.sandbox { init = init, update = update, view = view } diff --git a/frontend/src/Parse.elm b/frontend/src/Parse.elm new file mode 100644 index 0000000..886cf65 --- /dev/null +++ b/frontend/src/Parse.elm @@ -0,0 +1,87 @@ +module Parse exposing (parseUrl) + +import Data exposing (Provider(..), Url, pathSeparator) + + +parseUrl : String -> Maybe Url +parseUrl url = + stripProtocol url + |> splitProvider + |> Maybe.andThen splitOfHead + |> Maybe.andThen splitOfHead + |> Maybe.andThen splitOfHead + |> Maybe.andThen splitOfHead + |> Maybe.andThen + (\( ( ( ( ( prov, user ), repo ), separator ), gitref ), file ) -> + if List.isEmpty file || (separator /= pathSeparator prov) then + Nothing + + else + Just + { prov = prov + , user = user + , repo = repo + , gitref = gitref + , file = String.join "/" file + } + ) + + +splitOfHead : ( a, List b ) -> Maybe ( ( a, b ), List b ) +splitOfHead ( head, tail ) = + splitPart tail + |> Maybe.map (\( h, t ) -> ( ( head, h ), t )) + + +stripProtocol : String -> String +stripProtocol url = + let + index = + String.indexes "://" url + |> List.head + |> Maybe.withDefault -3 + in + String.dropLeft (index + 3) url + + +parseProvider : String -> Maybe Provider +parseProvider prov = + case String.toLower prov of + "github.com" -> + Just GitHub + + "bitbucket.org" -> + Just Bitbucket + + _ -> + Nothing + + +splitProvider : String -> Maybe ( Provider, List String ) +splitProvider url = + let + split = + String.split "/" url + + parts = + splitPart split + in + parts + |> Maybe.andThen + (\( head, tail ) -> + parseProvider head + |> Maybe.map (\prov -> ( prov, tail )) + ) + + +splitPart : List a -> Maybe ( a, List a ) +splitPart parts = + let + head = + List.head parts + + tail = + List.tail parts + in + head + |> Maybe.andThen (\h -> Maybe.map (\t -> ( h, t )) tail) diff --git a/frontend/tests/ParseTest.elm b/frontend/tests/ParseTest.elm new file mode 100644 index 0000000..807f0c8 --- /dev/null +++ b/frontend/tests/ParseTest.elm @@ -0,0 +1,92 @@ +module ParseTest exposing + ( invalidHostInUrlBitbucket + , invalidHostInUrlGitHub + , invalidUrlBitbucket + , invalidUrlGitHub + , validHttpUrlBitbucket + , validHttpUrlGitHub + , validHttpsUrlBitbucket + , validHttpsUrlGitHub + , validUrlWithoutProtocolBitbucket + , validUrlWithoutProtocolGitHub + ) + +import Data exposing (Provider(..), Url) +import Expect +import Parse exposing (parseUrl) +import Test exposing (Test, test) + + +expectedUrl : Provider -> Url +expectedUrl prov = + { prov = prov, user = "user", repo = "repo", gitref = "master", file = "README.md" } + + +expectedGitHubUrl : Url +expectedGitHubUrl = + expectedUrl GitHub + + +expectedBitbucketUrl : Url +expectedBitbucketUrl = + expectedUrl Bitbucket + + +validHttpsUrlGitHub : Test +validHttpsUrlGitHub = + test "Parsing Valid HTTPS URL for GitHub" + (\_ -> Expect.equal (Just expectedGitHubUrl) (parseUrl "https://GiThUb.CoM/user/repo/blob/master/README.md")) + + +validHttpUrlGitHub : Test +validHttpUrlGitHub = + test "Parsing Valid HTTP URL for GitHub" + (\_ -> Expect.equal (Just expectedGitHubUrl) (parseUrl "http://GiThUb.CoM/user/repo/blob/master/README.md")) + + +validUrlWithoutProtocolGitHub : Test +validUrlWithoutProtocolGitHub = + test "Parsing Valid URL without Protocol for GitHub" + (\_ -> Expect.equal (Just expectedGitHubUrl) (parseUrl "GiThUb.CoM/user/repo/blob/master/README.md")) + + +invalidUrlGitHub : Test +invalidUrlGitHub = + test "Parsing Invalid URL for GitHub" + (\_ -> Expect.equal Nothing (parseUrl "https://GiThUb.CoM/user")) + + +invalidHostInUrlGitHub : Test +invalidHostInUrlGitHub = + test "Parsing Invalid Host in URL for GitHub" + (\_ -> Expect.equal Nothing (parseUrl "https://example.com/user/repo/blob/master/README.md")) + + +validHttpsUrlBitbucket : Test +validHttpsUrlBitbucket = + test "Parsing Valid HTTPS URL for Bitbucket" + (\_ -> Expect.equal (Just expectedBitbucketUrl) (parseUrl "https://bItBuCkEt.OrG/user/repo/src/master/README.md")) + + +validHttpUrlBitbucket : Test +validHttpUrlBitbucket = + test "Parsing Valid HTTP URL for Bitbucket" + (\_ -> Expect.equal (Just expectedBitbucketUrl) (parseUrl "http://BiTbUcKeT.oRg/user/repo/src/master/README.md")) + + +validUrlWithoutProtocolBitbucket : Test +validUrlWithoutProtocolBitbucket = + test "Parsing Valid URL without Protocol for Bitbucket" + (\_ -> Expect.equal (Just expectedBitbucketUrl) (parseUrl "bitbucket.org/user/repo/src/master/README.md")) + + +invalidUrlBitbucket : Test +invalidUrlBitbucket = + test "Parsing Invalid URL for Bitbucket" + (\_ -> Expect.equal Nothing (parseUrl "https://bitBucket.ORG/user")) + + +invalidHostInUrlBitbucket : Test +invalidHostInUrlBitbucket = + test "Parsing Invalid Host in URL for Bitbucket" + (\_ -> Expect.equal Nothing (parseUrl "https://example.com/user/repo/blob/src/README.md"))