From c36794773ba961b80c8f980d53074b675c8ae050 Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 07:13:34 +0800 Subject: [PATCH 01/11] chore: drop old Nix setup in favour of a simpler setup (broken) Also, this 1. bumps our GHC from v9.0 to v9.2, and 2. breaks the codebase due to breaking changes in the upgraded Brick library that comes along with the pinned nixpkgs. --- default.nix | 138 +++++++++++++++++++++++++++++++++- nix/default.nix | 60 --------------- nix/lib/mk-haskell-app.nix | 31 ++++++++ nix/lib/mk-haskell-docker.nix | 38 ++++++++++ nix/lib/mk-haskell.nix | 15 ++++ nix/lib/read-yaml.nix | 27 +++++++ nix/sources.json | 16 ++-- nix/sources.nix | 114 ++++++++++++++-------------- shell.nix | 30 -------- 9 files changed, 314 insertions(+), 155 deletions(-) delete mode 100644 nix/default.nix create mode 100644 nix/lib/mk-haskell-app.nix create mode 100644 nix/lib/mk-haskell-docker.nix create mode 100644 nix/lib/mk-haskell.nix create mode 100644 nix/lib/read-yaml.nix delete mode 100644 shell.nix diff --git a/default.nix b/default.nix index ca7d167..d13d26a 100644 --- a/default.nix +++ b/default.nix @@ -1,3 +1,137 @@ -{ ... }: +{ sources ? import ./nix/sources.nix +, compiler ? "default" +, system ? builtins.currentSystem +, dockerImageRegistry ? "registry.docker.decafhub.com" +, dockerImageRepository ? null +, dockerImageTag ? null +, ... +}: -(import ./nix { }).thisPackageInstallable +let + ################## + ## LOAD NIXPKGS ## + ################## + + ## Import nixpkgs pinned by niv: + pkgs = import sources.nixpkgs { inherit system; }; + + ################## + ## LOAD HELPERS ## + ################## + + ## Load the YAML reader: + readYAML = pkgs.callPackage ./nix/lib/read-yaml.nix { }; + + ## Load Haskell package factory: + mkHaskell = pkgs.callPackage ./nix/lib/mk-haskell.nix { }; + + ## Load Haskell application factory: + mkHaskellApp = pkgs.callPackage ./nix/lib/mk-haskell-app.nix { }; + + ## Load Docker image factory for Haskell application: + mkHaskellDocker = pkgs.callPackage ./nix/lib/mk-haskell-docker.nix { }; + + ############# + ## HASKELL ## + ############# + + ## Get Haskell packages in the project: + thisHaskellPackages = { + main = { + name = "decaf-client"; + path = ./.; + }; + subs = [ ]; + }; + + ## Get Haskell packages in the project as a list: + thisHaskellPackagesAll = [ thisHaskellPackages.main ] ++ thisHaskellPackages.subs; + + ## Get the main Haskell package specification: + packageSpec = readYAML (thisHaskellPackages.main.path + "/package.yaml"); + + ## Get base Haskell package set: + baseHaskell = if compiler == "default" then pkgs.haskellPackages else pkgs.haskell.packages.${compiler}; + + ## Get this Haskell package set: + thisHaskell = mkHaskell { + haskell = baseHaskell; + packages = thisHaskellPackagesAll; + overrides = self: super: { }; + }; + + ########### + ## SHELL ## + ########### + + ## Prepare Nix shell: + thisShell = thisHaskell.shellFor { + ## Define packages for the shell: + packages = p: builtins.map (x: p.${x.name}) thisHaskellPackagesAll; + + ## Enable Hoogle: + withHoogle = false; + + ## Build inputs for development shell: + buildInputs = [ + ## Haskell related build inputs: + thisHaskell.apply-refact + thisHaskell.cabal-fmt + thisHaskell.cabal-install + thisHaskell.cabal2nix + thisHaskell.fourmolu + thisHaskell.haskell-language-server + thisHaskell.hlint + thisHaskell.hpack + thisHaskell.weeder + + ## Other build inputs for various development requirements: + pkgs.docker-client + pkgs.git + pkgs.git-chglog + ]; + + ## Shell hook for development shell: + shellHook = '' + ## Environment variables: + export PROJECT_DEV_ROOT="$(git rev-parse --show-toplevel)" + + ## Shell aliases: + alias riched="${pkgs.rich-cli}/bin/rich --emoji --center --width 120 --panel rounded --theme rrt --hyperlinks" + alias devsh-help="riched --pager ''${PROJECT_DEV_ROOT}/README.md" + alias devsh-welcome="riched ''${PROJECT_DEV_ROOT}/README.md" + alias devsh-makedev="hpack && fourmolu -i app/ src/ test/ && hlint app/ src/ test/ && cabal build -O0 && cabal v1-test" + + ## Greet: + devsh-welcome + echo + echo '**Run devsh-welcome to see Welcome notice again**' | riched -m - + echo '**Run devsh-help to see help notice**' | riched -m - + ''; + }; + + ################# + ## APPLICATION ## + ################# + + ## Get the installable application (only static executable): + thisApp = mkHaskellApp { + drv = thisHaskell.${thisHaskellPackages.main.name}; + }; + + ############ + ## DOCKER ## + ############ + + thisDocker = mkHaskellDocker { + app = thisApp; + registry = dockerImageRegistry; + repository = if isNull dockerImageRepository then packageSpec.name else dockerImageRepository; + tag = if isNull dockerImageTag then packageSpec.version else dockerImageTag; + }; +in +if pkgs.lib.inNixShell then thisShell else { + shell = thisShell; + app = thisApp; + docker = thisDocker; +} diff --git a/nix/default.nix b/nix/default.nix deleted file mode 100644 index 0fe1bb4..0000000 --- a/nix/default.nix +++ /dev/null @@ -1,60 +0,0 @@ -{ compiler ? "ghc90" -, ... -}: - -let - ## Import sources: - sources = import ./sources.nix; - - ## Import telosnix: - telosnix = import sources.telosnix { }; - - ## Import nixpkgs: - pkgs = import telosnix.pkgs-sources.unstable { }; - - ## Get Haskell for package development purposes: - haskell = telosnix.tools.haskell.getHaskell - { - pkgs = pkgs; - compiler = compiler; - }; - - ## Get this package: - thisPackage = haskell.callCabal2nixWithOptions "decaf-client" ../. "" { }; - - ## Get this package's Haskell dependencies: - thisPackageDeps = pkgs.haskell.lib.compose.getHaskellBuildInputs thisPackage; - - ## Get our GHC for development: - thisGhc = haskell.ghcWithPackages (_: thisPackageDeps); - - ## Get Haskell development tools: - haskell-dev-tools = with haskell; - [ - ## Our GHC with all packages required to build and test our package: - thisGhc - - ## Various haskell tools: - apply-refact - cabal-install - cabal2nix - fourmolu - haskell-language-server - hlint - hpack - ]; - - ## Get the installable package: - thisPackageInstallable = pkgs.haskell.lib.dontCheck (pkgs.haskell.lib.justStaticExecutables thisPackage); -in -{ - sources = sources; - telosnix = telosnix; - pkgs = pkgs; - haskell = haskell; - thisPackage = thisPackage; - thisPackageDeps = thisPackageDeps; - ghc = thisGhc; - haskell-dev-tools = haskell-dev-tools; - thisPackageInstallable = thisPackageInstallable; -} diff --git a/nix/lib/mk-haskell-app.nix b/nix/lib/mk-haskell-app.nix new file mode 100644 index 0000000..0ac1d68 --- /dev/null +++ b/nix/lib/mk-haskell-app.nix @@ -0,0 +1,31 @@ +{ pkgs, ... }: + +## Function that makes a Haskell application. +{ drv +, name ? drv.pname +, nativeBuildInputs ? [ ] +, binPaths ? [ ] +}: +let + ## We need these inputs at buildtime: + extraNativeBuildInputs = [ + pkgs.git + pkgs.makeWrapper + ] ++ nativeBuildInputs; + + ## We need these inputs at runtime: + binPath = pkgs.lib.makeBinPath binPaths; + + ## Post-fixup process: + extraPostFixup = '' + wrapProgram $out/bin/${name} --prefix PATH : ${binPath} + ''; +in +pkgs.haskell.lib.justStaticExecutables ( + drv.overrideAttrs (oldAttrs: + rec { + nativeBuildInputs = (oldAttrs.nativeBuildInputs or [ ]) ++ extraNativeBuildInputs; + postFixup = (oldAttrs.postFixup or "") + extraPostFixup; + } + ) +) diff --git a/nix/lib/mk-haskell-docker.nix b/nix/lib/mk-haskell-docker.nix new file mode 100644 index 0000000..f0d7359 --- /dev/null +++ b/nix/lib/mk-haskell-docker.nix @@ -0,0 +1,38 @@ +{ pkgs, ... }: + +## Function that makes a Docker image for the given Haskell application. +{ app +, name ? app.pname +, registry +, repository +, tag +, entrypoint ? "${app}/bin/${name}" +, cmd ? null +}: +pkgs.dockerTools.buildImage { + name = registry + "/" + repository; + tag = tag; + created = "now"; + + copyToRoot = pkgs.buildEnv { + name = "image-root"; + paths = [ pkgs.cacert ]; + pathsToLink = [ "/etc" ]; + }; + + runAsRoot = '' + #!${pkgs.runtimeShell} + ${pkgs.dockerTools.shadowSetup} + mkdir /tmp + chmod 777 /tmp + groupadd -r users + useradd -r -g users patron + ''; + + config = { + Entrypoint = [ "${app}/bin/${name}" ]; + Cmd = cmd; + + User = "patron"; + }; +} diff --git a/nix/lib/mk-haskell.nix b/nix/lib/mk-haskell.nix new file mode 100644 index 0000000..64514be --- /dev/null +++ b/nix/lib/mk-haskell.nix @@ -0,0 +1,15 @@ +{ pkgs, ... }: + +## Function that makes a Haskell. +{ haskell +, packages +, overrides +}: +let + packageFromSpec = self: { name, path }: { + ${name} = self.callCabal2nix name path { }; + }; +in +haskell.override { + overrides = self: super: builtins.foldl' (a: c: a // packageFromSpec self c) (overrides self super) packages; +} diff --git a/nix/lib/read-yaml.nix b/nix/lib/read-yaml.nix new file mode 100644 index 0000000..d89721a --- /dev/null +++ b/nix/lib/read-yaml.nix @@ -0,0 +1,27 @@ +## This file is a verbatim copy of: +## +## https://github.com/cdepillabout/stacklock2nix/blob/8408f57e929ca713e508f45dc3d846eca20c3379/nix/build-support/stacklock2nix/read-yaml.nix + +{ runCommand, remarshal }: + +# Read a YAML file into a Nix datatype using IFD. +# +# Similar to: +# +# > builtins.fromJSON (builtins.readFile ./somefile) +# +# but takes an input file in YAML instead of JSON. +# +# readYAML :: Path -> a +# +# where `a` is the Nixified version of the input file. +path: + +let + jsonOutputDrv = + runCommand + "from-yaml" + { nativeBuildInputs = [ remarshal ]; } + "remarshal -if yaml -i \"${path}\" -of json -o \"$out\""; +in +builtins.fromJSON (builtins.readFile jsonOutputDrv) diff --git a/nix/sources.json b/nix/sources.json index d842c0e..2f2a3df 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -1,14 +1,14 @@ { - "telosnix": { - "branch": "v0.0.4", - "description": null, + "nixpkgs": { + "branch": "release-23.05", + "description": "Nix Packages collection", "homepage": null, - "owner": "telostat", - "repo": "telos.nix", - "rev": "4d45ee0f57fb6979781b726cdf87eb68f4ef672d", - "sha256": "0my9pmbvyfpj3n747zmwhl6fk8mhnmirl18zmlpy29g1pcd891k2", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "79947b703cc6937356ae1a9fdbca9d906e035ee1", + "sha256": "1rjd62bmpa7nzg9hz1f3ijbvwj5widwgpak89f3l9vfagpjph82f", "type": "tarball", - "url": "https://github.com/telostat/telos.nix/archive/4d45ee0f57fb6979781b726cdf87eb68f4ef672d.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/79947b703cc6937356ae1a9fdbca9d906e035ee1.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/nix/sources.nix b/nix/sources.nix index 9a01c8a..fe3dadf 100644 --- a/nix/sources.nix +++ b/nix/sources.nix @@ -10,33 +10,34 @@ let let name' = sanitizeName name + "-src"; in - if spec.builtin or true then - builtins_fetchurl { inherit (spec) url sha256; name = name'; } - else - pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; fetch_tarball = pkgs: name: spec: let name' = sanitizeName name + "-src"; in - if spec.builtin or true then - builtins_fetchTarball { name = name'; inherit (spec) url sha256; } - else - pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; fetch_git = name: spec: let ref = - if spec ? ref then spec.ref else + spec.ref or ( if spec ? branch then "refs/heads/${spec.branch}" else - if spec ? tag then "refs/tags/${spec.tag}" else - abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; - submodules = if spec ? submodules then spec.submodules else false; + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" + ); + submodules = spec.submodules or false; submoduleArg = let nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; emptyArgWithWarning = - if submodules == true + if submodules then builtins.trace ( @@ -44,15 +45,15 @@ let + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " + "does not support them" ) - {} - else {}; + { } + else { }; in - if nixSupportsSubmodules - then { inherit submodules; } - else emptyArgWithWarning; + if nixSupportsSubmodules + then { inherit submodules; } + else emptyArgWithWarning; in - builtins.fetchGit - ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); + builtins.fetchGit + ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); fetch_local = spec: spec.path; @@ -86,16 +87,16 @@ let hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; hasThisAsNixpkgsPath = == ./.; in - if builtins.hasAttr "nixpkgs" sources - then sourcesNixpkgs - else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then - import {} - else - abort - '' - Please specify either (through -I or NIX_PATH=nixpkgs=...) or - add a package called "nixpkgs" to your sources.json. - ''; + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import { } + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; # The actual fetching function. fetch = pkgs: name: spec: @@ -115,13 +116,13 @@ let # the path directly as opposed to the fetched source. replace = name: drv: let - saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; in - if ersatz == "" then drv else - # this turns the string into an actual Nix path (for both absolute and - # relative paths) - if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; # Ports of functions for older nix versions @@ -132,7 +133,7 @@ let ); # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 - range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); + range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); @@ -143,43 +144,46 @@ let concatStrings = builtins.concatStringsSep ""; # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 - optionalAttrs = cond: as: if cond then as else {}; + optionalAttrs = cond: as: if cond then as else { }; # fetchTarball version that is compatible between all the versions of Nix builtins_fetchTarball = { url, name ? null, sha256 }@attrs: let inherit (builtins) lessThan nixVersion fetchTarball; in - if lessThan nixVersion "1.12" then - fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) - else - fetchTarball attrs; + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchTarball attrs; # fetchurl version that is compatible between all the versions of Nix builtins_fetchurl = { url, name ? null, sha256 }@attrs: let inherit (builtins) lessThan nixVersion fetchurl; in - if lessThan nixVersion "1.12" then - fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) - else - fetchurl attrs; + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchurl attrs; # Create the final "sources" from the config mkSources = config: - mapAttrs ( - name: spec: - if builtins.hasAttr "outPath" spec - then abort - "The values in sources.json should not have an 'outPath' attribute" - else - spec // { outPath = replace name (fetch config.pkgs name spec); } - ) config.sources; + mapAttrs + ( + name: spec: + if builtins.hasAttr "outPath" spec + then + abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) + config.sources; # The "config" used by the fetchers mkConfig = { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null - , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile) , system ? builtins.currentSystem , pkgs ? mkPkgs sources system }: rec { @@ -191,4 +195,4 @@ let }; in -mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } +mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 06b50ab..0000000 --- a/shell.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ ... }: - -let - ## Import this codebase's Nix helper set: - nix = import ./nix { }; - - ## Get packages: - pkgs = nix.pkgs; -in -pkgs.mkShell { - buildInputs = [ - ## Fancy stuff: - pkgs.figlet - pkgs.lolcat - - ## Release stuff: - pkgs.busybox - pkgs.gh - pkgs.git - pkgs.git-chglog - ] ++ nix.haskell-dev-tools; - - shellHook = '' - figlet -w 999 "DECAF CLIENT DEV SHELL" | lolcat -S 42 - - ## Make sure that doctest finds correct GHC executable and libraries: - export NIX_GHC=${nix.ghc}/bin/ghc - export NIX_GHC_LIBDIR=${nix.ghc}/lib/${nix.ghc.meta.name} - ''; -} From 0e2af74ff84376c8f280c8841d5e1f964234148b Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 07:17:35 +0800 Subject: [PATCH 02/11] refactor: adopt breaking changes introduced in brick v1.0 --- app/Decaf/Client/Cli/SubCommands/Tui.hs | 52 ++++++++++++------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/app/Decaf/Client/Cli/SubCommands/Tui.hs b/app/Decaf/Client/Cli/SubCommands/Tui.hs index 0ebf38e..faee076 100644 --- a/app/Decaf/Client/Cli/SubCommands/Tui.hs +++ b/app/Decaf/Client/Cli/SubCommands/Tui.hs @@ -75,31 +75,31 @@ app = } -handleEvent :: TuiState -> Brick.BrickEvent () e -> Brick.EventM () (Brick.Next TuiState) -handleEvent s (Brick.VtyEvent ev) = case ev of - Vty.EvKey Vty.KEsc [] -> Brick.halt s - Vty.EvKey (Vty.KChar 'q') _ -> Brick.halt s - Vty.EvKey (Vty.KChar 'a') _ -> Brick.continue (s {tuiStateWorld = TuiStateWorldAbout}) - Vty.EvKey (Vty.KChar 'r') _ -> reloadTuiProfileInfos s >>= Brick.continue - Vty.EvKey _ _ -> Brick.continue s - Vty.EvMouseDown _n _i _but _mods -> Brick.continue s - Vty.EvMouseUp _n _i _mBut -> Brick.continue s - Vty.EvResize _n _i -> Brick.continue s - Vty.EvPaste _bs -> Brick.continue s - Vty.EvLostFocus -> Brick.continue s - Vty.EvGainedFocus -> Brick.continue s -handleEvent b _ = Brick.continue b +handleEvent :: Brick.BrickEvent () e -> Brick.EventM () TuiState () +handleEvent (Brick.VtyEvent ev) = case ev of + Vty.EvKey Vty.KEsc [] -> Brick.halt + Vty.EvKey (Vty.KChar 'q') _ -> Brick.halt + Vty.EvKey (Vty.KChar 'a') _ -> Brick.modify (\s -> s {tuiStateWorld = TuiStateWorldAbout}) + Vty.EvKey (Vty.KChar 'r') _ -> reloadTuiProfileInfos + Vty.EvKey _ _ -> pure () + Vty.EvMouseDown _n _i _but _mods -> pure () + Vty.EvMouseUp _n _i _mBut -> pure () + Vty.EvResize _n _i -> pure () + Vty.EvPaste _bs -> pure () + Vty.EvLostFocus -> pure () + Vty.EvGainedFocus -> pure () +handleEvent _ = pure () attrMap :: Brick.AttrMap attrMap = Brick.attrMap Vty.defAttr - [ ("info", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.black}) - , ("muted", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.cyan}) - , ("success", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.green}) - , ("warning", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.yellow}) - , ("failure", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.red}) + [ (Brick.attrName "info", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.black}) + , (Brick.attrName "muted", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.cyan}) + , (Brick.attrName "success", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.green}) + , (Brick.attrName "warning", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.yellow}) + , (Brick.attrName "failure", Vty.defAttr {Vty.attrForeColor = Vty.SetTo Vty.red}) ] @@ -159,20 +159,18 @@ mkProfilesTableRow TuiProfileInfo {..} = , widget tuiProfileInfoCountPolicy ] where - widget (Left err) = Brick.withAttr "failure" $ Brick.txt err - widget (Right sv) = Brick.withAttr "success" $ Brick.txt sv + widget (Left err) = Brick.withAttr (Brick.attrName "failure") $ Brick.txt err + widget (Right sv) = Brick.withAttr (Brick.attrName "success") $ Brick.txt sv -- * Event Logic -reloadTuiProfileInfos :: TuiState -> Brick.EventM () TuiState -reloadTuiProfileInfos state@TuiState {..} = do +reloadTuiProfileInfos :: Brick.EventM () TuiState () +reloadTuiProfileInfos = do + TuiState {..} <- Brick.get infos <- liftIO $ getTuiProfileInfos tuiStateProfiles - pure $ - state - { tuiStateWorld = TuiStateWorldProfileInfos infos - } + Brick.modify (\s -> s {tuiStateWorld = TuiStateWorldProfileInfos infos}) getTuiProfileInfos :: [DC.DecafProfile] -> IO [TuiProfileInfo] From a2dbfb0c2d3da8ce0a3129cb4ebfe8c5e37b21e7 Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 07:19:39 +0800 Subject: [PATCH 03/11] fix: adopt /version endpoint response type change in DECAF Estate API --- app/Decaf/Client/Cli/SubCommands/Tui.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Decaf/Client/Cli/SubCommands/Tui.hs b/app/Decaf/Client/Cli/SubCommands/Tui.hs index faee076..b0fdd44 100644 --- a/app/Decaf/Client/Cli/SubCommands/Tui.hs +++ b/app/Decaf/Client/Cli/SubCommands/Tui.hs @@ -223,7 +223,7 @@ getBaristaVersion client = do getEstateVersion :: DC.DecafClient -> IO (Either T.Text T.Text) getEstateVersion client = do value <- DC.runDecafEstateJson (DC.path "version") client - case ACD.parseEither (ACD.at ["value"] ACD.auto) value of + case ACD.parseEither ACD.text value of Left err -> pure $ Left (T.pack err) Right sv -> pure $ Right sv From 7804494c1be836b4087da8432f30082ec23b63b3 Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 07:25:17 +0800 Subject: [PATCH 04/11] chore: drop static build Our team is using Nix, anyway. Once it is possible/easier to produce statically build binaries via Nix, we can consider resuming static binary distribution. --- Dockerfile | 26 ------ README.md | 18 ---- cabal.project.freeze.tmpl | 188 -------------------------------------- compile-static.sh | 19 ---- release.sh | 13 --- 5 files changed, 264 deletions(-) delete mode 100644 Dockerfile delete mode 100644 cabal.project.freeze.tmpl delete mode 100755 compile-static.sh diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index c8d6d1a..0000000 --- a/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -## Define the base image: -FROM utdemir/ghc-musl:v24-ghc902 - -## Configure shell: -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -## Install upx: -RUN apk add --no-cache upx - -## Add application files: -COPY / /app - -## Change the working directory: -WORKDIR /app/ - -## Rename cabal.project.freeze: -RUN mv cabal.project.freeze.tmpl cabal.project.freeze - -## Update cabal: -RUN cabal update - -## Build static executable: -RUN cabal build --enable-executable-static && \ - cp "$(cabal list-bin decafcli)" /app/decafcli && \ - strip /app/decafcli && \ - upx /app/decafcli diff --git a/README.md b/README.md index 8c1c9ff..eef7db7 100644 --- a/README.md +++ b/README.md @@ -133,24 +133,6 @@ $ nix-shell Specification](https://pvp.haskell.org/), such as `1.2.3.4`. Note that there is no `v` prefix. -## Static Compilation - -We are able to statically build, strip and compress the executable inside a -Docker image. Following command performs the necessary compilation, copy the -file to host machine and inform the user about the path to the copied file: - -```sh -./compile-static.sh -``` - -> **IMPORTANT:** Note that everytime we update Nix sources or add a new -> dependency to Haskell project, we must regenerate the freeze file: -> -> ```sh -> cabal freeze -> mv cabal.project.freeze cabal.project.freeze.tmpl -> ``` - ## License [BSD 3-Clause License](./LICENSE). Copyright (c) 2020-2022, Teloscube Pte Ltd. diff --git a/cabal.project.freeze.tmpl b/cabal.project.freeze.tmpl deleted file mode 100644 index 085512a..0000000 --- a/cabal.project.freeze.tmpl +++ /dev/null @@ -1,188 +0,0 @@ -active-repositories: hackage.haskell.org:merge -constraints: any.HUnit ==1.6.2.0, - any.OneTuple ==0.3.1, - any.QuickCheck ==2.14.2, - any.StateVar ==1.2.2, - any.aeson ==2.0.3.0, - any.aeson-combinators ==0.1.0.1, - any.ansi-terminal ==0.11.3, - any.ansi-wl-pprint ==0.6.9, - any.appar ==0.1.8, - any.array ==0.5.4.0, - any.asn1-encoding ==0.9.6, - any.asn1-parse ==0.9.5, - any.asn1-types ==0.3.4, - any.assoc ==1.0.2, - any.async ==2.2.4, - any.attoparsec ==0.14.4, - any.auto-update ==0.1.6, - any.base ==4.15.1.0, - any.base-compat ==0.11.2, - any.base-compat-batteries ==0.11.2, - any.base-orphans ==0.8.7, - any.base16-bytestring ==1.0.2.0, - any.base64-bytestring ==1.2.1.0, - any.basement ==0.0.15, - any.bifunctors ==5.5.13, - any.binary ==0.8.8.0, - any.blaze-builder ==0.4.2.2, - any.blaze-html ==0.9.1.2, - any.blaze-markup ==0.8.2.8, - any.brick ==0.68.1, - any.bsb-http-chunked ==0.0.0.4, - any.byteorder ==1.0.4, - any.bytestring ==0.10.12.1, - any.call-stack ==0.4.0, - any.case-insensitive ==1.2.1.0, - any.cereal ==0.5.8.3, - any.clock ==0.8.3, - any.code-page ==0.2.1, - any.colour ==2.3.6, - any.comonad ==5.0.8, - any.conduit ==1.3.4.2, - any.conduit-extra ==1.3.6, - any.config-ini ==0.2.4.0, - any.connection ==0.3.1, - any.containers ==0.6.4.1, - any.contravariant ==1.5.5, - any.cookie ==0.4.5, - any.cryptonite ==0.29, - any.data-clist ==0.2, - any.data-default-class ==0.1.2.0, - any.data-fix ==0.3.2, - any.deepseq ==1.4.5.0, - any.directory ==1.3.6.2, - any.distributive ==0.6.2.1, - any.dlist ==1.0, - any.doctest ==0.18.2, - any.easy-file ==0.2.2, - any.exceptions ==0.10.4, - any.fail ==4.9.0.0, - any.fast-logger ==3.1.1, - any.filepath ==1.4.2.1, - any.ghc ==9.0.2, - any.ghc-bignum ==1.1, - any.ghc-boot ==9.0.2, - any.ghc-boot-th ==9.0.2, - any.ghc-heap ==9.0.2, - any.ghc-paths ==0.1.0.12, - any.ghc-prim ==0.7.0, - any.ghci ==9.0.2, - any.hashable ==1.3.5.0, - any.haskell-src-exts ==1.23.1, - any.haskell-src-meta ==0.8.11, - any.hourglass ==0.2.12, - any.hpc ==0.6.1.0, - any.hspec ==2.8.5, - any.hspec-core ==2.8.5, - any.hspec-discover ==2.8.5, - any.hspec-expectations ==0.8.2, - any.http-client ==0.7.13.1, - any.http-client-tls ==0.3.6.1, - any.http-conduit ==2.3.8, - any.http-date ==0.0.11, - any.http-types ==0.12.3, - any.http2 ==3.0.3, - any.indexed-traversable ==0.1.2, - any.indexed-traversable-instances ==0.1.1.1, - any.integer-gmp ==1.1, - any.integer-logarithms ==1.0.3.1, - any.iproute ==1.7.12, - any.libyaml ==0.1.2, - any.megaparsec ==9.2.2, - any.memory ==0.16.0, - any.microlens ==0.4.12.0, - any.microlens-mtl ==0.2.0.2, - any.microlens-th ==0.4.3.10, - any.mime-types ==0.1.1.0, - any.monad-control ==1.0.3.1, - any.monad-parallel ==0.7.2.5, - any.mono-traversable ==1.0.15.3, - any.mtl ==2.2.2, - any.nats ==1.1.2, - any.network ==3.1.2.7, - any.network-byte-order ==0.1.6, - any.network-uri ==2.6.4.1, - any.old-locale ==1.0.0.7, - any.old-time ==1.1.0.3, - any.optparse-applicative ==0.16.1.0, - any.parallel ==3.2.2.0, - any.parsec ==3.1.14.0, - any.parser-combinators ==1.3.0, - any.pem ==0.2.4, - any.pretty ==1.1.3.6, - any.primitive ==0.7.3.0, - any.process ==1.6.13.2, - any.psqueues ==0.2.7.3, - any.quickcheck-io ==0.2.0, - any.random ==1.2.1.1, - any.regex-base ==0.94.0.2, - any.regex-compat ==0.95.2.1, - any.regex-posix ==0.96.0.1, - any.resourcet ==1.2.6, - any.rts ==1.0.2, - any.safe ==0.3.19, - any.scientific ==0.3.7.0, - any.scotty ==0.12, - any.semialign ==1.2.0.1, - any.semigroupoids ==5.3.7, - any.setenv ==0.1.1.3, - any.simple-sendfile ==0.2.30, - any.socks ==0.6.1, - any.split ==0.2.3.5, - any.splitmix ==0.1.0.4, - any.stm ==2.5.0.0, - any.streaming-commons ==0.2.2.4, - any.strict ==0.4.0.1, - any.string-interpolate ==0.3.1.2, - any.syb ==0.7.2.2, - any.tagged ==0.8.6.1, - any.template-haskell ==2.17.0.0, - any.terminfo ==0.4.1.5, - any.text ==1.2.5.0, - any.text-conversions ==0.3.1.1, - any.text-short ==0.1.5, - any.text-zipper ==0.11, - any.tf-random ==0.5, - any.th-abstraction ==0.4.5.0, - any.th-compat ==0.1.4, - any.th-expand-syns ==0.4.10.0, - any.th-lift ==0.8.2, - any.th-orphans ==0.13.14, - any.th-reify-many ==0.1.10, - any.these ==1.1.1.1, - any.time ==1.9.3, - any.time-compat ==1.9.6.1, - any.time-manager ==0.0.0, - any.tls ==1.5.8, - any.transformers ==0.5.6.2, - any.transformers-base ==0.4.6, - any.transformers-compat ==0.6.6, - any.typed-process ==0.2.10.1, - any.unix ==2.7.2.2, - any.unix-compat ==0.5.4, - any.unix-time ==0.4.8, - any.unliftio ==0.2.22.0, - any.unliftio-core ==0.2.0.1, - any.unordered-containers ==0.2.17.0, - any.utf8-string ==1.0.2, - any.uuid-types ==1.0.5, - any.vault ==0.3.1.5, - any.vector ==0.12.3.1, - any.vector-algorithms ==0.8.0.4, - any.void ==0.7.3, - any.vty ==5.33, - any.wai ==3.2.3, - any.wai-extra ==3.1.12.1, - any.wai-logger ==2.4.0, - any.warp ==3.3.21, - any.witherable ==0.4.2, - any.word-wrap ==0.5, - any.word8 ==0.1.3, - any.x509 ==1.7.7, - any.x509-store ==1.6.9, - any.x509-system ==1.6.7, - any.x509-validation ==1.6.12, - any.yaml ==0.11.8.0, - any.zlib ==0.6.3.0 -index-state: hackage.haskell.org 2022-09-28T00:41:23Z diff --git a/compile-static.sh b/compile-static.sh deleted file mode 100755 index 955a71d..0000000 --- a/compile-static.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -## Build the Docker image statically: -docker build -t decafcli . >&2 - -## Create a Docker container from the Docker image built above and get its identifier: -_id="$(docker create decafcli)" - -## Create a temporary directory and define the output path: -_path="$(mktemp -d)/decafcli" - -## Copy the statically compiled executable: -docker cp "${_id}:/app/decafcli" "${_path}" - -## Remove the container: -docker rm -v "${_id}" >&2 - -## Inform the enduser about the path: -echo "${_path}" diff --git a/release.sh b/release.sh index b179e91..d8e87cf 100755 --- a/release.sh +++ b/release.sh @@ -73,23 +73,10 @@ git commit -m "chore(release): ${_version}" _log "Tagging version..." git tag -a -m "Release ${_version}" "${_version}" -_log "Build statically linked executable..." -_outfile_raw="$(./compile-static.sh)" - -_log "Copying and renaming statically linked executable (${_outfile_raw})..." -_outfile="decafcli-Linux-x86_64-static" -cp "${_outfile_raw}" "${_outfile}" - _log "Pushing changes to remote..." git push --follow-tags origin main _log "Creating the release..." gh release create "${_version}" --title "v${_version}" --generate-notes -_log "Uploading release artifacts..." -gh release upload "${_version}" "${_outfile}" - -_log "Cleaning up release artifacts..." -rm "${_outfile}" - _log "Finished!" From 0481114f44842d91df33d31fc917ea57e41ea54d Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 07:28:17 +0800 Subject: [PATCH 05/11] chore: update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4c0d513..7602960 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *~ -.stack-work/ +dist-newstyle/ +dist/ profiles.yaml result From 77ba6f44cb24d74ae2d517b237717b93e0b9eafc Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 07:53:05 +0800 Subject: [PATCH 06/11] chore(test): revisit test setup Most notably, we are now switching from doctest to doctest-parallel. Some doctests are broken. Following commit will attempt to fix them. --- .gitignore | 1 + README.md | 10 ++++++++-- Setup.hs | 2 -- cabal.project | 2 ++ decaf-client.cabal | 11 ++++++----- package.yaml | 8 ++++---- tests/doctest/doctest.hs | 8 ++++++++ {test => tests/test}/Main.hs | 0 {test => tests/test}/Mocking.hs | 0 9 files changed, 29 insertions(+), 13 deletions(-) delete mode 100644 Setup.hs create mode 100644 cabal.project create mode 100644 tests/doctest/doctest.hs rename {test => tests/test}/Main.hs (100%) rename {test => tests/test}/Mocking.hs (100%) diff --git a/.gitignore b/.gitignore index 7602960..32d8d9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *~ +.ghc.environment* dist-newstyle/ dist/ profiles.yaml diff --git a/README.md b/README.md index eef7db7..775f588 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,13 @@ nix-shell Build: ```sh -cabal build +cabal build -O0 +``` + +Test: + +```sh +cabal test -O0 ``` Open VSCode if you wish to do so: @@ -113,7 +119,7 @@ code . Run hlint: ```sh -hlint app/ src/ test/ +hlint app/ src/ tests/ ``` Format codebase: diff --git a/Setup.hs b/Setup.hs deleted file mode 100644 index 9a994af..0000000 --- a/Setup.hs +++ /dev/null @@ -1,2 +0,0 @@ -import Distribution.Simple -main = defaultMain diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..1b97420 --- /dev/null +++ b/cabal.project @@ -0,0 +1,2 @@ +packages: . +write-ghc-environment-files: always diff --git a/decaf-client.cabal b/decaf-client.cabal index c1fed50..3abaa0c 100644 --- a/decaf-client.cabal +++ b/decaf-client.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.7. +-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack @@ -97,11 +97,12 @@ test-suite decaf-client-doctest other-modules: Paths_decaf_client hs-source-dirs: - ./ - ghc-options: -Wall -Wunused-packages -threaded + tests/doctest + ghc-options: -Wall -threaded build-depends: base >=4.7 && <5 - , doctest + , decaf-client + , doctest-parallel default-language: Haskell2010 test-suite decaf-client-test @@ -111,7 +112,7 @@ test-suite decaf-client-test Mocking Paths_decaf_client hs-source-dirs: - test + tests/test ghc-options: -Wall -Wunused-packages -threaded -rtsopts -with-rtsopts=-N build-depends: aeson diff --git a/package.yaml b/package.yaml index a8b65d6..57908d1 100644 --- a/package.yaml +++ b/package.yaml @@ -69,7 +69,7 @@ executables: tests: decaf-client-test: main: Main.hs - source-dirs: test + source-dirs: tests/test ghc-options: - -Wall - -Wunused-packages @@ -87,10 +87,10 @@ tests: decaf-client-doctest: main: doctest.hs - source-dirs: . + source-dirs: tests/doctest ghc-options: - -Wall - - -Wunused-packages - -threaded dependencies: - - doctest + - decaf-client + - doctest-parallel diff --git a/tests/doctest/doctest.hs b/tests/doctest/doctest.hs new file mode 100644 index 0000000..f1f4785 --- /dev/null +++ b/tests/doctest/doctest.hs @@ -0,0 +1,8 @@ +module Main where + +import System.Environment (getArgs) +import Test.DocTest (mainFromCabal) + + +main :: IO () +main = mainFromCabal "decaf-client" =<< getArgs diff --git a/test/Main.hs b/tests/test/Main.hs similarity index 100% rename from test/Main.hs rename to tests/test/Main.hs diff --git a/test/Mocking.hs b/tests/test/Mocking.hs similarity index 100% rename from test/Mocking.hs rename to tests/test/Mocking.hs From be9014bebfb12f49c52b5d8e58a58876d3773155 Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 07:55:02 +0800 Subject: [PATCH 07/11] test: fix doctests Unlike doctest, doctest-parallel needs explicit instructions to enable language extensions. --- src/Decaf/Client/DecafCredentials.hs | 5 +++++ src/Decaf/Client/DecafProfile.hs | 6 ++++++ src/Decaf/Client/DecafRemote.hs | 5 +++++ src/Decaf/Client/DecafRequest.hs | 7 +++++++ src/Decaf/Client/Internal/Http.hs | 5 +++++ 5 files changed, 28 insertions(+) diff --git a/src/Decaf/Client/DecafCredentials.hs b/src/Decaf/Client/DecafCredentials.hs index f578aed..7587b18 100644 --- a/src/Decaf/Client/DecafCredentials.hs +++ b/src/Decaf/Client/DecafCredentials.hs @@ -9,6 +9,11 @@ import Decaf.Client.Internal.Utils (commonAesonOptions) import GHC.Generics (Generic) +-- $setup +-- +-- >>> :set -XOverloadedStrings + + -- | Data definition for available DECAF credentials types. -- -- >>> Data.Aeson.encode (DecafCredentialsHeader "some-header-value") diff --git a/src/Decaf/Client/DecafProfile.hs b/src/Decaf/Client/DecafProfile.hs index f7d2eeb..c474d20 100644 --- a/src/Decaf/Client/DecafProfile.hs +++ b/src/Decaf/Client/DecafProfile.hs @@ -21,6 +21,12 @@ import GHC.Generics (Generic) import GHC.Stack (HasCallStack) +-- $setup +-- +-- >>> :set -XOverloadedStrings +-- >>> :set -XTypeApplications + + -- | A DECAF Instance user profile for constructing a -- 'Decaf.Client.DecafClient.DecafClient'. -- diff --git a/src/Decaf/Client/DecafRemote.hs b/src/Decaf/Client/DecafRemote.hs index 3bf7e9d..6236254 100644 --- a/src/Decaf/Client/DecafRemote.hs +++ b/src/Decaf/Client/DecafRemote.hs @@ -12,6 +12,11 @@ import qualified Network.URI as U import Text.Read (readMaybe) +-- $setup +-- +-- >>> :set -XOverloadedStrings + + -- | Type definition for addressing a remote DECAF Instance. -- -- >>> DecafRemote "example.com" Nothing False diff --git a/src/Decaf/Client/DecafRequest.hs b/src/Decaf/Client/DecafRequest.hs index a78a6b1..48c7506 100644 --- a/src/Decaf/Client/DecafRequest.hs +++ b/src/Decaf/Client/DecafRequest.hs @@ -24,6 +24,11 @@ import Paths_decaf_client (version) import Text.Printf (printf) +-- $setup +-- +-- >>> :set -XOverloadedStrings + + -- * Data Definitions @@ -164,6 +169,8 @@ decafGraphqlQueryNoVars = flip MkDecafGraphqlQuery (Aeson.object []) -- | Initializes a request with DECAF Instance URL and authentication credentials. -- +-- >>> import Decaf.Client.DecafRemote (DecafRemote(..)) +-- >>> import Decaf.Client.DecafCredentials (DecafCredentials(..)) -- >>> initRequest (DecafRemote "example.com" Nothing False) (DecafCredentialsHeader "OUCH") -- DecafRequest { -- decafRequestRemote = http://example.com diff --git a/src/Decaf/Client/Internal/Http.hs b/src/Decaf/Client/Internal/Http.hs index 9814078..8665c13 100644 --- a/src/Decaf/Client/Internal/Http.hs +++ b/src/Decaf/Client/Internal/Http.hs @@ -32,6 +32,11 @@ import qualified Network.HTTP.Simple as HS import Network.HTTP.Types (Status (statusCode), queryTextToQuery) +-- $setup +-- +-- >>> :set -XOverloadedStrings + + -- * HTTP Request Runners From 92600cae60b504bc9e9b3fb040af24f2a4a9f188 Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 08:08:22 +0800 Subject: [PATCH 08/11] chore(dev): reformat {.hlint,package}.yaml file --- .hlint.yaml | 40 +++++++-------- package.yaml | 138 +++++++++++++++++++++++++-------------------------- 2 files changed, 89 insertions(+), 89 deletions(-) diff --git a/.hlint.yaml b/.hlint.yaml index 9ccda89..42b7af5 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -9,37 +9,37 @@ ####################### - modules: - - {name: Control.Monad.Error, within: []} - - {name: [Data.Aeson], as: Aeson} - - {name: Data.ByteString, as: B } - - {name: Data.ByteString.Char8, as: BC } - - {name: Data.ByteString.Lazy, as: BL } - - {name: Data.ByteString.Lazy.Char8, as: BLC } - - {name: Data.List, as: List } - - {name: Data.Text, as: T } - - {name: Data.Text.Lazy, as: TL } - - {name: Data.Text.Encoding, as: TE } + - { name: Control.Monad.Error, within: [] } + - { name: [Data.Aeson], as: Aeson } + - { name: Data.ByteString, as: B } + - { name: Data.ByteString.Char8, as: BC } + - { name: Data.ByteString.Lazy, as: BL } + - { name: Data.ByteString.Lazy.Char8, as: BLC } + - { name: Data.List, as: List } + - { name: Data.Text, as: T } + - { name: Data.Text.Lazy, as: TL } + - { name: Data.Text.Encoding, as: TE } ########################## # EXTENSION RESTRICTIONS # ########################## - extensions: - - default: false # All extension are banned by default - - name: # Only these listed extensions can be used - - DeriveGeneric - - GADTs - - OverloadedStrings - - QuasiQuotes - - RecordWildCards - - ScopedTypeVariables + - default: false # All extension are banned by default + - name: # Only these listed extensions can be used + - DeriveGeneric + - GADTs + - OverloadedStrings + - QuasiQuotes + - RecordWildCards + - ScopedTypeVariables ################ # CUSTOM RULES # ################ # Replace a $ b $ c with a . b $ c -- group: {name: dollar, enabled: true} +- group: { name: dollar, enabled: true } # Generalise map to fmap, ++ to <> -- group: {name: generalise, enabled: true} +- group: { name: generalise, enabled: true } diff --git a/package.yaml b/package.yaml index 57908d1..f19e3ff 100644 --- a/package.yaml +++ b/package.yaml @@ -1,96 +1,96 @@ -name: decaf-client -version: 0.0.1 -github: "teloscube/decaf-client-haskell" -license: BSD3 -author: "Vehbi Sinan Tunalioglu" -maintainer: "vst@vsthost.com" -copyright: "2020-2022 Teloscube Pte Ltd" +name: decaf-client +version: 0.0.1 +github: "teloscube/decaf-client-haskell" +license: BSD3 +author: "Vehbi Sinan Tunalioglu" +maintainer: "vst@vsthost.com" +copyright: "2020-2022 Teloscube Pte Ltd" extra-source-files: -- README.md -- CHANGELOG.md + - README.md + - CHANGELOG.md -synopsis: DECAF API Client Suite for Haskell -category: Web +synopsis: DECAF API Client Suite for Haskell +category: Web -description: Please see the README on GitHub at +description: Please see the README on GitHub at dependencies: -- base >= 4.7 && < 5 + - base >= 4.7 && < 5 library: source-dirs: src ghc-options: - - -Wall - - -Wunused-packages - dependencies: - - aeson - - base64-bytestring - - bytestring - - case-insensitive - - exceptions - - http-conduit - - http-types - - network-uri - - text - - yaml - -executables: - decafcli: - main: Main.hs - source-dirs: app - ghc-options: - -Wall - -Wunused-packages - - -threaded - - -rtsopts - - -with-rtsopts=-N - dependencies: + dependencies: - aeson - - aeson-combinators - - blaze-html - - blaze-markup - - brick + - base64-bytestring - bytestring - - data-default-class - - decaf-client + - case-insensitive - exceptions + - http-conduit - http-types - - monad-parallel - - optparse-applicative - - scotty - - string-interpolate + - network-uri - text - - vector - - vty - - warp - yaml +executables: + decafcli: + main: Main.hs + source-dirs: app + ghc-options: + - -Wall + - -Wunused-packages + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - aeson + - aeson-combinators + - blaze-html + - blaze-markup + - brick + - bytestring + - data-default-class + - decaf-client + - exceptions + - http-types + - monad-parallel + - optparse-applicative + - scotty + - string-interpolate + - text + - vector + - vty + - warp + - yaml + tests: decaf-client-test: - main: Main.hs - source-dirs: tests/test + main: Main.hs + source-dirs: tests/test ghc-options: - - -Wall - - -Wunused-packages - - -threaded - - -rtsopts - - -with-rtsopts=-N + - -Wall + - -Wunused-packages + - -threaded + - -rtsopts + - -with-rtsopts=-N dependencies: - - aeson - - bytestring - - decaf-client - - exceptions - - hspec - - text - - unordered-containers + - aeson + - bytestring + - decaf-client + - exceptions + - hspec + - text + - unordered-containers decaf-client-doctest: - main: doctest.hs - source-dirs: tests/doctest + main: doctest.hs + source-dirs: tests/doctest ghc-options: - - -Wall - - -threaded + - -Wall + - -threaded dependencies: - - decaf-client - - doctest-parallel + - decaf-client + - doctest-parallel From e070b3e5749e1d367f462e3ab08a713107ba875a Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 08:10:18 +0800 Subject: [PATCH 09/11] chore: update copyright year --- LICENSE | 2 +- README.md | 6 +++++- package.yaml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index d951ae4..ba0fb6e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020-2022, Teloscube Pte Ltd +Copyright (c) 2020-2023, Teloscube Pte Ltd Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index 775f588..457d5a6 100644 --- a/README.md +++ b/README.md @@ -141,4 +141,8 @@ there is no `v` prefix. ## License -[BSD 3-Clause License](./LICENSE). Copyright (c) 2020-2022, Teloscube Pte Ltd. +[BSD 3-Clause License]. Copyright (c) 2020-2023, Teloscube Pte Ltd. + + + +[BSD 3-Clause License]: ./LICENSE diff --git a/package.yaml b/package.yaml index f19e3ff..d5ee152 100644 --- a/package.yaml +++ b/package.yaml @@ -4,7 +4,7 @@ github: "teloscube/decaf-client-haskell" license: BSD3 author: "Vehbi Sinan Tunalioglu" maintainer: "vst@vsthost.com" -copyright: "2020-2022 Teloscube Pte Ltd" +copyright: "2020-2023 Teloscube Pte Ltd" extra-source-files: - README.md From 2da21cb7afbec8cdfddd9658bde8be43f735cf61 Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 08:22:07 +0800 Subject: [PATCH 10/11] docs: update README --- README.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 457d5a6..99e075a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# DECAF API Client Suite For Haskell +# DECAF CLI Application and Haskell Client Library -A Haskell client library to DECAF API and a small application demonstrating -library usage and providing some utilities for working with DECAF instances. +This repository mainly provides a Haskell client library for DECAF APIs and a +small application (1) demonstrating library usage and (2) providing some +utilities for working with DECAF Instances. > **TODO:** Provide full README. @@ -36,6 +37,7 @@ $ decafcli --help decafcli - DECAF Command-line Client Application Usage: decafcli [--version] COMMAND + DECAF Client Available options: @@ -110,12 +112,6 @@ Test: cabal test -O0 ``` -Open VSCode if you wish to do so: - -```sh -code . -``` - Run hlint: ```sh @@ -128,6 +124,12 @@ Format codebase: fourmolu -i app/ src/ test/ ``` +Run: + +```sh +cabal run -O0 decafcli -- --help +``` + ## Release Process ```console @@ -135,9 +137,9 @@ $ nix-shell [inside-nix-shell]$ ./release.sh -n ``` -... whereby `` follows [Haskell PVP -Specification](https://pvp.haskell.org/), such as `1.2.3.4`. Note that -there is no `v` prefix. +... where `` follows [Haskell PVP Specification], such as `1.2.3.4`. + +Note that there is no `v` prefix. ## License @@ -146,3 +148,4 @@ there is no `v` prefix. [BSD 3-Clause License]: ./LICENSE +[Haskell PVP Specification]: https://pvp.haskell.org From 1d6780615c9c9f445af16884b38dd85ee1fcb4f1 Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Thu, 28 Sep 2023 13:39:26 +0800 Subject: [PATCH 11/11] feat: add multipart/form-data support --- decaf-client.cabal | 3 ++- package.yaml | 1 + src/Decaf/Client/DecafRequest.hs | 20 ++++++++++++++++++++ src/Decaf/Client/Internal/Http.hs | 13 ++++++++++--- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/decaf-client.cabal b/decaf-client.cabal index 3abaa0c..a4c595f 100644 --- a/decaf-client.cabal +++ b/decaf-client.cabal @@ -13,7 +13,7 @@ homepage: https://github.com/teloscube/decaf-client-haskell#readme bug-reports: https://github.com/teloscube/decaf-client-haskell/issues author: Vehbi Sinan Tunalioglu maintainer: vst@vsthost.com -copyright: 2020-2022 Teloscube Pte Ltd +copyright: 2020-2023 Teloscube Pte Ltd license: BSD3 license-file: LICENSE build-type: Simple @@ -49,6 +49,7 @@ library , bytestring , case-insensitive , exceptions + , http-client , http-conduit , http-types , network-uri diff --git a/package.yaml b/package.yaml index d5ee152..48fb56c 100644 --- a/package.yaml +++ b/package.yaml @@ -29,6 +29,7 @@ library: - bytestring - case-insensitive - exceptions + - http-client - http-conduit - http-types - network-uri diff --git a/src/Decaf/Client/DecafRequest.hs b/src/Decaf/Client/DecafRequest.hs index 48c7506..67f6de7 100644 --- a/src/Decaf/Client/DecafRequest.hs +++ b/src/Decaf/Client/DecafRequest.hs @@ -14,6 +14,7 @@ import Decaf.Client.DecafCredentials (DecafCredentials (DecafCredentialsHeader)) import Decaf.Client.DecafRemote (DecafRemote (..), remoteToUrl) import Decaf.Client.Internal.Utils (commonAesonOptions, dropTrailing) import GHC.Generics (Generic) +import qualified Network.HTTP.Client.MultipartFormData as Http.Client import Network.HTTP.Types ( Header, QueryText, @@ -47,6 +48,7 @@ data DecafRequest = DecafRequest , decafRequestTrailingSlash :: !Bool , decafRequestQuery :: !QueryText , decafRequestPayload :: !(Maybe DecafRequestPayload) + , decafRequestParts :: !(Maybe [Http.Client.Part]) , decafRequestCheckResponse :: !Bool } @@ -66,6 +68,7 @@ instance Show DecafRequest where , " decafRequestTrailingSlash = " <> show (decafRequestTrailingSlash x) , " decafRequestQuery = " <> show (decafRequestQuery x) , " decafRequestPayload = " <> show (decafRequestPayload x) + , " decafRequestParts = " <> maybe "None" (const "Some") (decafRequestParts x) , " decafRequestCheckResponse = " <> show (decafRequestCheckResponse x) , "}" ] @@ -183,6 +186,7 @@ decafGraphqlQueryNoVars = flip MkDecafGraphqlQuery (Aeson.object []) -- decafRequestTrailingSlash = False -- decafRequestQuery = [] -- decafRequestPayload = Nothing +-- decafRequestParts = None -- decafRequestCheckResponse = True -- } initRequest :: DecafRemote -> DecafCredentials -> DecafRequest @@ -205,6 +209,7 @@ defaultRequest = , decafRequestTrailingSlash = False , decafRequestQuery = [] , decafRequestPayload = Nothing + , decafRequestParts = Nothing , decafRequestCheckResponse = True } @@ -280,6 +285,7 @@ remote = setRemote -- decafRequestTrailingSlash = False -- decafRequestQuery = [] -- decafRequestPayload = Nothing +-- decafRequestParts = None -- decafRequestCheckResponse = True -- } setNamespace :: DecafRequestPath -> DecafRequestCombinator @@ -301,6 +307,7 @@ setNamespace n request = request {decafRequestNamespace = n} -- decafRequestTrailingSlash = False -- decafRequestQuery = [] -- decafRequestPayload = Nothing +-- decafRequestParts = None -- decafRequestCheckResponse = True -- } namespace :: T.Text -> DecafRequestCombinator @@ -495,6 +502,19 @@ noPayload :: DecafRequestCombinator noPayload = setNoPayload +-- ** Multipart/Form-Data Combinators + + +-- | Sets multipart/form-data parts for 'DecafRequest'. +setParts :: [Http.Client.Part] -> DecafRequestCombinator +setParts ps request = request {decafRequestParts = Just ps} + + +-- | Sets no multipart/form-data parts for 'DecafRequest'. +setNoParts :: DecafRequestCombinator +setNoParts request = request {decafRequestParts = Nothing} + + -- ** Response Checkers diff --git a/src/Decaf/Client/Internal/Http.hs b/src/Decaf/Client/Internal/Http.hs index 8665c13..47d46bc 100644 --- a/src/Decaf/Client/Internal/Http.hs +++ b/src/Decaf/Client/Internal/Http.hs @@ -27,6 +27,7 @@ import Decaf.Client.DecafRequest (DecafRequest (..), DecafRequestPayload (..), u import Decaf.Client.DecafResponse (DecafResponse (..)) import Decaf.Client.Internal.Utils (compose) import GHC.Stack (HasCallStack) +import qualified Network.HTTP.Client.MultipartFormData as Http.Client import qualified Network.HTTP.Conduit as HC import qualified Network.HTTP.Simple as HS import Network.HTTP.Types (Status (statusCode), queryTextToQuery) @@ -56,7 +57,7 @@ performDecafRequest => DecafRequest -> m (DecafResponse BL.ByteString) performDecafRequest request = do - response <- mkResponse <$> (HS.httpLBS (compileRequest request) `catch` throwHttpException request) + response <- mkResponse <$> (HS.httpLBS =<< compileRequest request `catch` throwHttpException request) when (decafRequestCheckResponse request) (assert2xx response) pure response @@ -118,8 +119,14 @@ type RequestFieldSetter = DecafRequest -> HS.Request -> HS.Request -- | Compiles a DECAF client 'DecafRequest' into a "http-conduit" 'H.Request'. -compileRequest :: DecafRequest -> HS.Request -compileRequest request = compiler request HS.defaultRequest +compileRequest :: MonadIO m => DecafRequest -> m HS.Request +compileRequest request = do + let bareRequest = compiler request HS.defaultRequest + case decafRequestParts request of + Nothing -> pure bareRequest + Just sp -> do + modifiedRequest <- Http.Client.formDataBody sp bareRequest + pure $ HS.setRequestMethod (BC.pack (show (decafRequestMethod request))) modifiedRequest -- | Request compiler.