Skip to content

Commit b60bc20

Browse files
committed
Initial commit
0 parents  commit b60bc20

22 files changed

+1489
-0
lines changed

.gitignore

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
dist
2+
dist-*
3+
cabal-dev
4+
*.o
5+
*.hi
6+
*.hie
7+
*.chi
8+
*.chs.h
9+
*.dyn_o
10+
*.dyn_hi
11+
.hpc
12+
.hsenv
13+
.cabal-sandbox/
14+
cabal.sandbox.config
15+
*.prof
16+
*.aux
17+
*.hp
18+
*.eventlog
19+
.stack-work/
20+
cabal.project.local
21+
cabal.project.local~
22+
.HTF/
23+
.ghc.environment.*
24+
.stack-work/
25+
*~
26+
*.cabal
27+
tasks.py
28+
*.log

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog for `demake`
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to the
7+
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
8+

Justfile

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# BUILDOPTS := "--fast --haddock-deps"
2+
BUILDOPTS := "--fast"
3+
PROG := "demake"
4+
5+
# generate local hoogle index
6+
gen-local-hoogle:
7+
stack hoogle -- generate --local
8+
9+
dev-deps:
10+
# LTS might not contain things we need
11+
cabal install ghcide hindent ghcid
12+
13+
# prepare project ready for development
14+
dev-init: dev-deps build
15+
16+
# start local hoogle server
17+
hoogle: gen-local-hoogle
18+
stack hoogle -- server --local --port=8080
19+
20+
# run for given file
21+
run file="test/02.mk":
22+
stack exec {{PROG}} -- {{ file }}
23+
24+
# build all targets
25+
build:
26+
stack build {{ BUILDOPTS }}
27+
28+
# watch for changes, run for sample file when complete
29+
watch:
30+
stack build {{ BUILDOPTS }} --no-run-tests --file-watch --exec "stack exec {{PROG}} -- -D test/01.mk"
31+
32+
watch-test:
33+
stack build {{ BUILDOPTS }} --file-watch --test
34+
35+
# lint all files
36+
lint:
37+
hlint --git
38+
39+
# run tests
40+
test:
41+
stack test --fast
42+
43+
# format code
44+
fmt: fmt-f
45+
46+
fmt-f:
47+
fourmolu --indentation 2 --mode inplace src/**/*.hs app/*.hs test/*.hs
48+
49+
amend-if-changed:
50+
git status || git --amend --no-edit commit .
51+
52+
# new version
53+
bump kind="minor": fmt amend-if-changed
54+
bumpver update --{{ kind }}
55+
56+
# List TODOs
57+
todo:
58+
bat .todo.md
59+
60+
# copy to ~/.local/bin/
61+
install:
62+
stack install

LICENSE

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2022 Edvard Majakari
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# demake
2+
3+
Version 0.1.0
4+
5+
## Overview
6+
7+
Helper to convert Makefiles to Python invoke files.
8+
9+
## Rationale
10+
11+
Makefiles typically contain rules to build files based on dependencies, whereas invoke is used for task automation. These are two separate things.
12+
13+
GNU Make is a powerful tool, so it is often used for task automation as well, mainly because it is already present in many development environments, and most people are familiar with the basic syntax
14+
15+
Problem is, Make is not very suitable for complex rules. Error handling, condition logic, passing arbitrary command line arguments etc is not where it really shines.
16+
17+
In the end there shouldn't even be battle between Make and task automation tools. In fact, in some cases it makes totally sense to have both PyInvoke for convenient task automation, and GNU Make for incremental builds. PyInvoke, like many task automation tools, doesn't offer support for building items automatically based on extension, or running build targets based on file modification time. In such cases, it would be totally reasonable to use PyInvoke for front-end to developers, and let Make handle incremental builds and nothing else.
18+
19+
Sure, complex make tasks can be extracted to shell scripts, but then the same arguments about awkward programmability would arise as with Make.
20+
21+
## Purpose of demake
22+
23+
demake tries to help you here by converting existing Makefile to PyInvoke tasks.py. It is by no means complete and it is quite likely that you need to modify generated `tasks.py` before it is useful, with the exception of very trivial Makefiles. It should still save you from some manual work, and for me it was a good excuse to something bit more serious with Haskell than just to dabble with ghci solving Project Euler problems.
24+
25+
## Sample run
26+
27+
## TODO
28+
29+
Ideas only, haven't committed to anything yet
30+
31+
- [Justfile](https://github.com/casey/just) support
32+
- [Shake](https://github.com/casey/just) support
33+
34+
## Credits
35+
36+
Nicolas Mattia's [Makefile library](https://github.com/nmattia/makefile),
37+
which I modified a bit to support parsing of top-level and inline comments
38+

Setup.hs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Distribution.Simple
2+
3+
main = defaultMain

app/Main.hs

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
3+
import qualified Control.Monad
4+
import Data.Makefile (Makefile)
5+
import Data.Makefile.Parse (parseAsMakefile)
6+
import Options.Applicative
7+
import PyEncode (
8+
asString,
9+
toPyEntries,
10+
)
11+
import System.Directory (doesFileExist)
12+
import System.Exit (die)
13+
import Text.Pretty.Simple
14+
15+
data Args = Args
16+
{ file :: String
17+
, debug :: Bool
18+
, parseOnly :: Bool
19+
, force :: Bool
20+
, output :: FilePath
21+
}
22+
23+
input :: Parser Args
24+
input =
25+
Args
26+
<$> strArgument
27+
( showDefault
28+
<> value "Makefile"
29+
<> metavar "MAKEFILE"
30+
<> showDefault
31+
<> help "Makefile to read"
32+
)
33+
<*> switch (long "debug" <> short 'd' <> help "Produce debug output")
34+
<*> switch
35+
( long "parse-only"
36+
<> short 'p'
37+
<> help
38+
"Implies --debug, produces only parsed structure"
39+
)
40+
<*> switch
41+
(long "force" <> short 'f' <> help "Overwrite tasks.py if it exists")
42+
<*> strOption
43+
( long "output"
44+
<> short 'o'
45+
<> showDefault
46+
<> value "tasks.py"
47+
<> help
48+
"tasks file to write"
49+
)
50+
51+
parseFile :: FilePath -> IO Makefile
52+
parseFile fpath = do
53+
parsed <- parseAsMakefile fpath
54+
case parsed of
55+
Left _ -> error $ "unable to parse " ++ fpath ++ ", aborting"
56+
Right mf -> return mf
57+
58+
run :: Args -> IO ()
59+
run Args{file = f, debug = False, output = optOutfile, force = optForce, parseOnly = False} =
60+
abortIfExists optOutfile optForce
61+
>> parseFile f
62+
>>= writer optOutfile
63+
. asString
64+
. toPyEntries
65+
run Args{file = f, debug = True, output = optOutfile, force = optForce, parseOnly = False} =
66+
do
67+
abortIfExists optOutfile optForce
68+
mkFl <- parseFile f
69+
debugParsed mkFl
70+
(writer optOutfile . asString . toPyEntries) mkFl
71+
run Args{file = f, parseOnly = True} = parseFile f >>= debugParsed
72+
73+
debugParsed :: Makefile -> IO ()
74+
debugParsed mkFl = do
75+
pPrint mkFl
76+
putStrLn "=>"
77+
pPrint $ toPyEntries mkFl
78+
79+
abortIfExists :: FilePath -> Bool -> IO ()
80+
abortIfExists optOutfile optForce = do
81+
v <- doesFileExist optOutfile
82+
Control.Monad.when (v && not optForce) $
83+
die "tasks.py already exist, aborting (use --force to override)"
84+
85+
-- | "-" causes strings to be printed to stdout
86+
writer :: FilePath -> String -> IO ()
87+
-- TODO: use Data.Text.IO instead of String functions
88+
writer outFile = case outFile of
89+
"-" -> putStrLn
90+
_ -> writeFile outFile
91+
92+
main :: IO ()
93+
main = run =<< execParser opts
94+
where
95+
opts =
96+
info (helper <*> input) $
97+
fullDesc
98+
<> header "demake -- Makefile to invoke converter"
99+
<> progDesc "Generate PyInvoke stub from given Makefile"

bumpver.toml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[bumpver]
2+
current_version = "0.1.0"
3+
version_pattern = "MAJOR.MINOR.PATCH"
4+
commit_message = "bump version {old_version} -> {new_version}"
5+
commit = true
6+
tag = true
7+
push = true
8+
9+
[bumpver.file_patterns]
10+
"README.md" = ["{version}"]
11+
"bumpver.toml" = ['current_version = "{version}"']
12+
"package.yaml" = ["version: {version}"]

hie.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cradle:
2+
stack:

package.yaml

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: demake
2+
version: 0.1.0
3+
github: "edvardm/demake"
4+
license: MIT
5+
author: "Edvard Majakari"
6+
maintainer: "[email protected]"
7+
copyright: "2022 Edvard Majakari"
8+
9+
extra-source-files:
10+
- README.md
11+
- CHANGELOG.md
12+
13+
# Metadata used when publishing your package
14+
synopsis: Parse Makefile to PyInvoke
15+
category: Console
16+
17+
# To avoid duplicated efforts in documentation and dealing with the
18+
# complications of embedding Haddock markup inside cabal files, it is
19+
# common to point users to the README.md file.
20+
description: Please see the README on GitHub at <https://github.com/edvardm/demake#readme>
21+
22+
dependencies:
23+
- base >= 4.7 && < 5
24+
- attoparsec
25+
- text
26+
- pretty-simple
27+
- optparse-applicative
28+
- posix-escape
29+
- containers
30+
- regex-tdfa ^>= 1.3.1
31+
- regex-compat
32+
- regex
33+
- regex-with-pcre
34+
- directory
35+
36+
ghc-options:
37+
# - -Weverything
38+
# - -Wmissing-export-lists
39+
- -Wall
40+
- -Wno-missing-export-lists
41+
- -Wcompat
42+
- -Widentities
43+
- -Wincomplete-record-updates
44+
- -Wincomplete-uni-patterns
45+
- -Wmissing-home-modules
46+
- -Wpartial-fields
47+
- -Wredundant-constraints
48+
- -fwrite-ide-info
49+
- -hiedir=.hie
50+
51+
library:
52+
source-dirs: src
53+
54+
executables:
55+
demake:
56+
main: Main.hs
57+
source-dirs: app
58+
ghc-options:
59+
- -threaded
60+
- -rtsopts
61+
- -with-rtsopts=-N
62+
dependencies:
63+
- demake
64+
65+
tests:
66+
demake-test:
67+
main: Spec.hs
68+
source-dirs:
69+
- test
70+
- src
71+
ghc-options:
72+
- -threaded
73+
- -rtsopts
74+
- -with-rtsopts=-N
75+
dependencies:
76+
- demake
77+
- hspec
78+
- QuickCheck

src/Data/Makefile.hs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+
3+
{- |
4+
Original by: Nicolas Mattia <[email protected]>
5+
URL: https://github.com/nmattia/makefile
6+
License: MIT
7+
8+
Parsing functions modified by Edvard Majakari <[email protected]> to provide basic
9+
support for include directives, comments and filtering conditionals
10+
-}
11+
module Data.Makefile where
12+
13+
import Data.String (IsString)
14+
import qualified Data.Text as T
15+
16+
type Text = T.Text
17+
18+
newtype Makefile = Makefile {entries :: [Entry]}
19+
deriving (Show, Read, Eq)
20+
21+
newtype LineComment = LineComment Text
22+
deriving (Show, Read, Eq)
23+
24+
data Entry
25+
= Rule Target [Dependency] [Command]
26+
| RuleRef Target AssignmentType T.Text T.Text
27+
| Assignment AssignmentType Text Text
28+
| Comment Text
29+
| Include Text (Maybe LineComment)
30+
| Conditional -- for now just ignored
31+
| OtherLine Text
32+
deriving (Show, Read, Eq)
33+
34+
data AssignmentType
35+
= RecursiveAssign
36+
| SimpleAssign
37+
| SimplePosixAssign
38+
| ConditionalAssign
39+
| ShellAssign
40+
| AppendAssign
41+
deriving (Show, Read, Eq, Enum, Bounded)
42+
43+
newtype Target = Target Text
44+
deriving (Show, Read, Eq, IsString)
45+
46+
newtype Dependency = Dependency Text
47+
deriving (Show, Read, Eq, IsString)
48+
49+
newtype Command = Command Text
50+
deriving (Show, Read, Eq, IsString)

0 commit comments

Comments
 (0)