diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2020
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,1 @@
+# noli
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/noli.cabal b/noli.cabal
new file mode 100644
--- /dev/null
+++ b/noli.cabal
@@ -0,0 +1,27 @@
+name:                noli
+version:             0.1.0.0
+synopsis:            A static site generator
+description:            A static site generator
+homepage:            https://github.com/githubuser/noli#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Author name here
+maintainer:          example@example.com
+copyright:           2020 Author name here
+category:            Web
+build-type:          Simple
+cabal-version:       >=1.10
+extra-source-files:  README.md
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Noli,
+                       Noli.Types
+  other-modules:       PostUtils
+  default-language:    Haskell2010
+  build-depends:       base >= 4.7 && < 5,
+                       text,
+                       lucid,
+                       cmark,
+                       directory,
+                       regex-compat
diff --git a/src/Noli.hs b/src/Noli.hs
new file mode 100644
--- /dev/null
+++ b/src/Noli.hs
@@ -0,0 +1,76 @@
+module Noli
+  ( buildProject,
+    buildSite,
+    compilePosts,
+  )
+where
+
+import Control.Monad
+  ( unless,
+    when,
+  )
+import Data.Text (unpack)
+import qualified Data.Text.IO as DT
+import Data.Text.Internal (Text)
+import Data.Text.Lazy (toStrict)
+import Lucid.Base
+  ( Html,
+    renderText,
+  )
+import Noli.Types
+import PostUtils
+  ( compilePost,
+    copyFolder,
+    extname,
+  )
+import System.Directory
+  ( createDirectory,
+    doesDirectoryExist,
+    listDirectory,
+    removeDirectoryRecursive,
+    setCurrentDirectory,
+  )
+
+-- | Compile the posts starting from the templates and the folder where the markdown files are.
+compilePosts :: PostTemplate -> FilePath -> IO [Post]
+compilePosts postCompiler fp = do
+  filePaths <- listDirectory fp
+  let markdownFilePaths =
+        Prelude.map (fp ++) $
+          Prelude.filter (\fn -> extname fn == "md") filePaths
+  mapM (compilePost postCompiler) markdownFilePaths
+
+-- | Build the dynamic `Project` which works as a basis for the final static site.
+--   It takes three arguments and returns a compiled `Project`
+buildProject :: Settings -> [Page] -> PostTemplate -> IO Project
+buildProject settings pages postCompiler = do
+  ps <- compilePosts postCompiler $ posts_location settings
+  return Project {posts = ps, pages = pages}
+
+-- | Build the final static site
+-- Takes two arguments and creates the files needed for the static site, saving them in the previously-set `dist_location`.
+buildSite :: Settings -> Project -> IO ()
+buildSite settings' project = do
+  distExists <- doesDirectoryExist $ dist_location settings'
+  when distExists $ removeDirectoryRecursive $ dist_location settings'
+  createDirectory $ dist_location settings'
+  copyFolder
+    (static_location settings')
+    (dist_location settings' ++ "static/")
+  setCurrentDirectory $ dist_location settings'
+  Prelude.mapM_
+    ( \p ->
+        DT.writeFile
+          (unpack (pagename p) ++ ".html")
+          (toStrict $ renderText $ template p)
+    )
+    (pages project)
+  createDirectory "./posts"
+  setCurrentDirectory "./posts"
+  Prelude.mapM_
+    ( \p ->
+        DT.writeFile
+          (unpack (filename p) ++ ".html")
+          (toStrict $ renderText $ compiled_html p)
+    )
+    (posts project)
diff --git a/src/Noli/Types.hs b/src/Noli/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Noli/Types.hs
@@ -0,0 +1,41 @@
+module Noli.Types (Settings (..), Post (..), Project (..), Page (..), PostTemplate) where
+
+import Data.Text
+import Lucid.Base
+
+-- | The "dynamic" representation of the static site
+data Project = Project
+  { posts :: [Post],
+    pages :: [Page]
+  }
+
+data Settings = Settings
+  { -- | The name of the site
+    name :: Text,
+    -- | The author's full name
+    author :: Text,
+    -- | The path to the folder that contains the markdown files
+    posts_location :: FilePath,
+    -- | The path to where the compiled static site will be saved
+    dist_location :: FilePath,
+    -- | The path to the static folder
+    static_location :: FilePath
+  }
+
+data Post = Post
+  { title :: Text,
+    location :: FilePath,
+    filename :: Text,
+    raw :: Text,
+    raw_html :: Text,
+    compiled_html :: Html ()
+  }
+  deriving (Show)
+
+data Page = Page {pagename :: Text, template :: Html ()}
+
+type PostTitle = Text
+
+type PostBody = Text
+
+type PostTemplate = PostTitle -> PostBody -> Html ()
diff --git a/src/PostUtils.hs b/src/PostUtils.hs
new file mode 100644
--- /dev/null
+++ b/src/PostUtils.hs
@@ -0,0 +1,60 @@
+module PostUtils (compilePost, extname, copyFolder) where
+
+import CMark (commonmarkToHtml)
+import Control.Monad (unless)
+import Data.Maybe (fromJust)
+import Data.Text (pack)
+import Data.Text.Internal (Text)
+import Lucid.Base (Html)
+import Noli.Types
+import System.Directory (copyFile, createDirectory, doesDirectoryExist, doesFileExist, listDirectory)
+import Text.Regex (Regex, matchRegex, mkRegex)
+
+postNameRegex :: Regex
+postNameRegex = mkRegex "/?([A-Za-z_]+)\\.md$"
+
+extname :: FilePath -> FilePath
+extname = Prelude.reverse . Prelude.takeWhile ('.' /=) . Prelude.reverse
+
+compilePost :: PostTemplate -> FilePath -> IO Post
+compilePost postCompiler fp = do
+  fileContents <- pack <$> readFile fp
+  let compiledFileContents = commonmarkToHtml [] fileContents
+      fn = fromJust $ getPostFileName fp
+      t = getPostName fn
+      compiledHtml = postCompiler (pack t) compiledFileContents
+  return
+    Post
+      { title = pack t,
+        location = fp,
+        filename = pack fn,
+        raw = fileContents,
+        raw_html = compiledFileContents,
+        compiled_html = compiledHtml
+      }
+
+getPostFileName :: FilePath -> Maybe String
+getPostFileName fp = case match of
+  Nothing -> Nothing
+  Just (x : xs) -> Just x
+  where
+    match = matchRegex postNameRegex fp
+
+getPostName :: String -> String
+getPostName fn =
+  let repl '_' = ' '
+      repl x = x
+   in Prelude.map repl fn
+
+copyFolder :: FilePath -> FilePath -> IO ()
+copyFolder source destination = do
+  destinationExists <- doesDirectoryExist destination
+  unless destinationExists $ createDirectory destination
+  sourceFiles <- listDirectory source
+  Prelude.mapM_ copyFile' sourceFiles
+  where
+    copyFile' source_file = do
+      is_file <- doesFileExist $ source ++ source_file
+      if is_file
+        then copyFile (source ++ source_file) (destination ++ source_file)
+        else copyFolder (source ++ source_file ++ "/") (destination ++ source_file ++ "/")
