Skip to content

Commit a1a39ea

Browse files
committed
Start Haskell implementation, move Haskell code to sub-directory
1 parent 63d3ad3 commit a1a39ea

21 files changed

+312
-56
lines changed

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
/.stack-work
2-
/*.cabal
1+
/.nickel
2+
/cli-spec.json

app/Main.hs

-8
This file was deleted.

changelog.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Haskell Template
1+
# Oclis Changelog
22

3-
- 2023-11-27 - 0.0.0.0
4-
- Initial release
3+
- [TBD] - 0.1.0.0
4+
- First implementation of code generation for Haskell and PureScript

haskell/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.stack-work
2+
/*.cabal
3+
/dist-newstyle

haskell/app/Main.hs

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
{-# LANGUAGE QuasiQuotes #-}
3+
4+
module Main where
5+
6+
import Protolude (
7+
Bool (..),
8+
IO,
9+
Maybe (..),
10+
pure,
11+
putText,
12+
when,
13+
($),
14+
(&),
15+
(<>),
16+
)
17+
import Protolude qualified as P
18+
19+
import Oclis as O
20+
import Oclis.App as OA
21+
import Oclis.Option as OO
22+
import Text.RawString.QQ (r)
23+
24+
25+
myCliApp :: OA.App
26+
myCliApp =
27+
OA.defaultApp
28+
{ name = "my-cli-app"
29+
, version = Just "1.0"
30+
, OA.description =
31+
Just
32+
[r|
33+
# My CLI App
34+
35+
This is a CLI app that does something
36+
|]
37+
, options =
38+
[ OO.Option
39+
{ long = Just "verbose"
40+
, short = Nothing
41+
, OO.description = Just "Enable verbose output"
42+
, argument = ArgNone
43+
, defaultValue = P.undefined -- Boolean False
44+
, required = False
45+
}
46+
, OO.Option
47+
{ long = Just "log-file"
48+
, short = Just 'l'
49+
, OO.description = Just "Log file to write to"
50+
, argument = ArgOne "FILE"
51+
, defaultValue = P.undefined -- Boolean False
52+
, required = False
53+
}
54+
]
55+
, run =
56+
\opts _args -> do
57+
P.putText "Hello, world!"
58+
59+
when (opts & O.hasFlag "verbose") $ do
60+
P.putText "Verbose output enabled"
61+
62+
case opts & O.getOptionVal "log-file" of
63+
Nothing -> P.putText "No log file specified"
64+
Just file -> P.putText $ "Writing logs to " <> file
65+
, commands = []
66+
}
67+
68+
69+
main :: IO ()
70+
main = do
71+
putText "Test"
72+
-- TODO: execute myCliApp args
73+
pure ()
File renamed without changes.

haskell/makefile

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.PHONY: help
2+
help: makefile
3+
@tail -n +4 makefile | grep ".PHONY"
4+
5+
6+
.PHONY: test
7+
test:
8+
stack test
9+
10+
11+
.PHONY: build
12+
build:
13+
stack build
14+
15+
16+
.PHONY: install
17+
install:
18+
stack install

package.yaml haskell/package.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ extra-source-files:
1313
- readme.md
1414

1515
dependencies:
16+
- aeson
1617
- base
1718
- protolude
19+
- raw-strings-qq
1820

1921
default-extensions:
2022
- NoImplicitPrelude

haskell/readme.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Oclis for Haskell
2+
3+
## Used Technologies
4+
5+
- [Stack](https://docs.haskellstack.org/en/stable/README/)
6+
- [Fourmolu](https://fourmolu.github.io)
7+
- [Haskell Language Server ](https://github.com/haskell/haskell-language-server)
8+
- [Protolude](https://github.com/protolude/protolude)
9+
- [Hspec](https://hspec.github.io)

haskell/source/Oclis.hs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module Oclis (
2+
module Oclis.App,
3+
module Oclis.Option,
4+
hasFlag,
5+
getOptionVal,
6+
)
7+
where
8+
9+
import Protolude (Bool, Maybe, Text)
10+
import Protolude qualified as P
11+
12+
import Oclis.App
13+
import Oclis.Option hiding (description)
14+
15+
16+
hasFlag :: Text -> [Option] -> Bool
17+
hasFlag = P.undefined
18+
19+
20+
getOptionVal :: Text -> [Option] -> Maybe Text
21+
getOptionVal = P.undefined

haskell/source/Oclis/App.hs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module Oclis.App where
2+
3+
import GHC.Show (show)
4+
import Protolude (IO, Maybe (..), Show, Text, pure)
5+
6+
import Oclis.Option (Option)
7+
8+
9+
data App = App
10+
{ name :: Text
11+
, version :: Maybe Text
12+
, description :: Maybe Text
13+
, options :: [Option]
14+
, run :: [Option] -> [Text] -> IO ()
15+
, commands :: [App]
16+
}
17+
18+
19+
instance Show App where
20+
show _ = "<app>"
21+
22+
23+
defaultApp :: App
24+
defaultApp =
25+
App
26+
{ name = "oclis"
27+
, version = Nothing
28+
, description = Nothing
29+
, options = []
30+
, run = \_ _ -> pure ()
31+
, commands = []
32+
}

haskell/source/Oclis/Option.hs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module Oclis.Option (
2+
Argument (..),
3+
Option (..),
4+
defaultOption,
5+
) where
6+
7+
import Protolude (Bool (..), Char, Eq, Maybe (..), Show, Text)
8+
9+
import Data.Aeson (Value)
10+
11+
12+
data Argument
13+
= ArgNone
14+
| ArgOne Text
15+
| ArgMany Text
16+
deriving (Show, Eq)
17+
18+
19+
data Option = Option
20+
{ long :: Maybe Text
21+
, short :: Maybe Char
22+
, description :: Maybe Text
23+
, argument :: Argument
24+
, required :: Bool
25+
, defaultValue :: Maybe Value
26+
}
27+
deriving (Show, Eq)
28+
29+
30+
defaultOption :: Option
31+
defaultOption =
32+
Option
33+
{ long = Nothing
34+
, short = Nothing
35+
, description = Nothing
36+
, argument = ArgNone
37+
, required = False
38+
, defaultValue = Nothing
39+
}

stack.yaml haskell/stack.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
resolver: lts-21.15
1+
resolver: lts-22.8
22
packages:
33
- "."
44

stack.yaml.lock haskell/stack.yaml.lock

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
packages: []
77
snapshots:
88
- completed:
9-
sha256: 350737ef1c4c748f4c7ff56b6e74f2f6d15039a2f148662a8ec1aded016b80d0
10-
size: 640033
11-
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/15.yaml
12-
original: lts-21.15
9+
sha256: 56ef9e03804cb4827866e762dc9752eeb392adda8f4811690da110dd9a165b9e
10+
size: 714105
11+
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/8.yaml
12+
original: lts-22.8

haskell/tests/Spec.hs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{-# LANGUAGE OverloadedRecordDot #-}
2+
3+
import Protolude (IO, ($))
4+
import Test.Hspec (describe, hspec, it, shouldBe)
5+
6+
import Oclis
7+
8+
9+
main :: IO ()
10+
main = hspec $ do
11+
describe "Oclis" $ do
12+
it "has a default app" $ do
13+
defaultApp.name `shouldBe` "oclis"

makefile

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
.PHONY: test
2-
test:
3-
stack test
1+
.PHONY: help
2+
help: makefile
3+
@tail -n +4 makefile | grep ".PHONY"
4+
45

56
.PHONY: build
6-
build:
7-
stack build
7+
build: cli-spec.json
8+
89

9-
.PHONY: install
10-
install:
11-
stack install
10+
cli-spec.json: oclis-contract.ncl oclis.ncl
11+
echo '(import "oclis-contract.ncl") & (import "oclis.ncl")' \
12+
| nickel export --format json > $@

oclis-contract.ncl

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Contract for Oclis specifcations
2+
{
3+
name
4+
| String
5+
| doc "The name of the command",
6+
version
7+
| String,
8+
description
9+
| String
10+
| optional,
11+
}

oclis.ncl

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
name = "oclis",
3+
version = "0.1.0.0",
4+
description = m%"
5+
Generate a CLI parser and executor
6+
from a given `oclis.ncl` specification file.
7+
"%,
8+
run = "runApp"
9+
}

readme.md

+63-13
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,71 @@
1-
# Haskell Template
1+
# Oclis
22

3-
Opinionated template for new Haskell projects.
3+
CLI (Command Line Interface) app builder
4+
based on a simple, obvious specification file.
45

56

6-
## Used Technologies
7+
## Motivation
78

8-
- [Stack](https://docs.haskellstack.org/en/stable/README/)
9-
- [Fourmolu](https://fourmolu.github.io)
10-
- [Haskell Language Server ](https://github.com/haskell/haskell-language-server)
11-
- [Protolude](https://github.com/protolude/protolude)
9+
Building a CLI application is a repetitive task.
10+
The same code is written over and over again.
11+
But fear not, Oclis is here to help you out!
1212

1313

1414
## Usage
1515

16-
1. Clone this repository
17-
1. Rename the folder to your project name
18-
1. Replace all occurences of `haskell-template` with your project name
19-
1. Run `stack test` to build the project and run the tests
20-
1. Run `stack run` to run the project
21-
1. Run `stack install` to install the project (make it available in your PATH)
16+
1. Write a simple specification file.
17+
2. Run `oclis` to generate the CLI parsing code for the language of your choice.
18+
3. Define the handler functions for your commands.
19+
4. Done 🎉
20+
21+
22+
## Related
23+
24+
### Tools
25+
26+
- [CLI Definition Language] - DSL for defining command line interfaces
27+
of C++ programs.
28+
- [Decli] - Declarative CLI tool builder.
29+
- [docopt] - Command-line interface description language.
30+
- [make-cli] - Declarative CLI framework for Node.js.
31+
32+
[CLI Definition Language]: https://www.codesynthesis.com/projects/cli/
33+
[Decli]: https://github.com/woile/decli
34+
[docopt]: http://docopt.org/
35+
[make-cli]: https://github.com/dword-design/make-cli
36+
37+
38+
### Specifications
39+
40+
- [clig.dev] - Command Line Interface Guidelines.
41+
- [GNU Table of Long Options][gtolo]
42+
- [Heroku CLI Style Guide][hcsg]
43+
- [OpenAutoComplete] - CLI autocomplete specification.
44+
- [POSIX Utility Conventions][puc]
45+
46+
[clig.dev]: https://clig.dev
47+
[gtolo]:
48+
https://www.gnu.org/prep/standards/html_node/Option-Table.html#Option-Table
49+
[hcsg]: https://devcenter.heroku.com/articles/cli-style-guide
50+
[OpenAutoComplete]: https://github.com/openautocomplete/openautocomplete
51+
[puc]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
52+
53+
54+
### Generate GUIs From CLI
55+
56+
- [Claui] - A GUI generator for [clap] using [egui].
57+
- [Gooey] - Turn CLI programs into a full GUI application.
58+
- [Klask] - Automatically create GUI applications from [clap] apps.
59+
60+
[clap]: https://github.com/clap-rs/clap
61+
[Claui]: https://github.com/grantshandy/claui
62+
[egui]: https://github.com/emilk/egui
63+
[Gooey]: https://github.com/chriskiehl/Gooey
64+
[Klask]: https://github.com/MichalGniadek/klask
65+
66+
67+
### Generate GUIs From Simple Code
68+
69+
- [Streamlit] - Turns data scripts into shareable web apps.
70+
71+
[Streamlit]: https://github.com/streamlit/streamlit

0 commit comments

Comments
 (0)