diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Revision history for nvfetcher
 
+## 0.2.0.0
+
+* Generated package sources will be sorted alphabetically.
+* CLI program now supports `nix-git-prefetch` configurations in TOML.
+* Lenses are added for some data types.
+* CLI options are no loger inherited from Shake. Now `nvfetcher` has its own CLI options with completion support.
+
 ## 0.1.0.0
 
 * First version. Released on an unsuspecting world.
diff --git a/Main_example.hs b/Main_example.hs
--- a/Main_example.hs
+++ b/Main_example.hs
@@ -11,7 +11,7 @@
 packageSet = do
   define $ package "feeluown-core" `fromPypi` "feeluown"
 
-  define $ package "qliveplayer" `fromGitHub` ("IsoaSFlus", "QLivePlayer")
+  define $ package "qliveplayer" `fromGitHub'` ("IsoaSFlus", "QLivePlayer", fetchSubmodules .~ True)
 
   define $
     package "fcitx5-pinyin-zhwiki"
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@
 [qliveplayer]
 src.github = "IsoaSFlus/QLivePlayer"
 fetch.github = "IsoaSFlus/QLivePlayer"
+git.fetchSubmodules = true
 ```
 
 running `nvfetcher build` will create `sources.nix` like:
@@ -33,22 +34,22 @@
 {
   feeluown-core = {
     pname = "feeluown-core";
-    version = "3.7.6";
+    version = "3.7.7";
     src = fetchurl {
-      sha256 = "1bsz149dv3j5sfbynjrqsqbkkxdxkdlq4sdx2vi8whvfwfg0j2f0";
-      url = "https://pypi.io/packages/source/f/feeluown/feeluown-3.7.6.tar.gz";
+      sha256 = "06d3j39ff9znqxkhp9ly81lcgajkhg30hyqxy2809yn23xixg3x2";
+      url = "https://pypi.io/packages/source/f/feeluown/feeluown-3.7.7.tar.gz";
     };
   };
   qliveplayer = {
     pname = "qliveplayer";
-    version = "3.22.0";
+    version = "3.22.1";
     src = fetchgit {
       url = "https://github.com/IsoaSFlus/QLivePlayer";
-      rev = "3.22.0";
-      fetchSubmodules = false;
+      rev = "3.22.1";
+      fetchSubmodules = true;
       deepClone = false;
       leaveDotGit = false;
-      sha256 = "192g42pvibms2rsjh68fck4bj59b10ay9zqcf2hqhcka0xmnyb09";
+      sha256 = "00zqg28q5xrbgql0kclgkhd15fc02qzsrvi0qg8lg3qf8a53v263";
     };
   };
 }
@@ -58,7 +59,7 @@
 and nvfetcher will help us keep their version and prefetched SHA256 sums up-to-date, stored in `sources.nix`.
 Shake will help us handle necessary rebuilds -- we check versions of packages during each run, but only prefetch them when needed.
 
-## Live examples
+### Live examples
 
 How to use the generated sources file? Here are some examples:
 
@@ -66,30 +67,47 @@
 
 * Nick Cao's [flakes repo](https://gitlab.com/NickCao/flakes/-/tree/master/pkgs)
 
-## Usage
+## Installation
 
-Basically, there are two ways to use nvfetcher, where the difference is how we provide package sources definitions to it. 
-No matter which way you use it in, CLI options are inherited from shake with two targets, typically used as:
+`nvfetcher` package is available in [nixpkgs](https://github.com/NixOS/nixpkgs), so you can try it with:
 
-* `nvfetcher build` - our main purpose, creating `sources.nix`
-* `nvfetcher clean` - clean up cache and remove `sources.nix`
+```
+$ nix-shell -p nvfetcher
+```
 
-> nvfetcher uses `build` as the target if no specified
+This repo also has flakes support:
 
-> You can specify `-j` to enable parallelism
+```
+$ nix run github:berberman/nvfetcher
+```
 
+To use it as a Haskell library, the package is available on [Hackage](https://hackage.haskell.org/package/nvfetcher).
+
+## Usage
+
+Basically, there are two ways to use nvfetcher, where the difference is how we provide package sources definitions to it.
+
 ### CLI
 
 To run nvfetcher as a CLI program, you'll need to provide package sources defined in TOML.
 
-Aavailable CLI options:
-* `-c` (`--config`) - path to the TOML configuration file
-* `-o` (`--output`) - path to the output nix file
-* `-v` (`--version`) - print nvfetcher version
-* `-l` (`--log`) - path to log file, where nvfetcher dumps the version changes 
+```
+Available options:
+  --version                Show version
+  --help                   Show this help text
+  -c,--config FILE         Path to nvfetcher TOML config
+                           (default: "nvfetcher.toml")
+  -o,--output FILE         Path to output nix file (default: "sources.nix")
+  -l,--changelog FILE      Dump version changes to a file
+  -j NUM                   Number of threads (0: detected number of processors)
+                           (default: 0)
+  -t,--timing              Show build time
+  -v,--verbose             Verbose mode
+  TARGET                   Two targets are available: build and clean
+```
 
 Each *package* corresponds to a TOML table, whose name is encoded as table key;
-there are two pairs in each table:
+there are two fields and four optional git prefetch configuration in each table:
 * a nvchecker configuration, how to track version updates
   * `src.github = owner/repo` - the latest gituhb release
   * `src.pypi = pypi_name` - the latest pypi release
@@ -104,11 +122,20 @@
   * `fetch.git = git_url` or `git_url:rev` (default to `$ver` if no `rev` specified)
   * `fetch.url = url`
 
+* optional git prefetch configuration, which makes sense only when the fetcher equals to `fetch.github` or `fetch.git`.
+They can exist simultanesouly.
+  * `git.branch = branch_name` - branch to fetch
+  * `git.deepClone` - a bool value to control deep clone
+  * `git.fetchSubmodules` - a bool value to control fetching submodules
+  * `git.leaveDotGit` - a bool value to control leaving dot git
+
 You can find an example of the configuration file, see [`nvfetcher_example.toml`](nvfetcher_example.toml).
 
 ### Haskell library
 
-nvfetcher itsetlf is a Haskell library as well, whereas the CLI program is just a trivial wrapper of the library. You can create a Haskell program depending on it directly, creating an entry point. In this case, we can define packages in Haskell language, getting rid of TOML constraints.
+nvfetcher itsetlf is a Haskell library as well, whereas the CLI program is just a trivial wrapper of the library.
+You can create a Haskell program depending on it directly, by using the `runNvFetcher` entry point.
+In this case, we can define packages in Haskell language, getting rid of TOML constraints.
 
 You can find an example of using nvfetcher in the library way, see [`Main_example.hs`](Main_example.hs).
 
@@ -130,3 +157,11 @@
 ## Contributing
 
 Issues and PRs are always welcome. **\_(:з」∠)\_**
+
+Building from source:
+
+```
+$ git clone https://github.com/berberman/nvfetcher
+$ nix develop
+$ cabal build
+```
diff --git a/app/Config.hs b/app/Config.hs
--- a/app/Config.hs
+++ b/app/Config.hs
@@ -7,10 +7,15 @@
 module Config where
 
 import Control.Applicative ((<|>))
+import Data.Coerce (coerce)
 import Data.List.NonEmpty (NonEmpty ((:|)))
+import Data.Maybe (fromMaybe)
 import qualified Data.Text as T
-import NvFetcher
+import Lens.Micro
+import Lens.Micro.Extras (view)
 import NvFetcher.NixFetcher
+import NvFetcher.Types
+import NvFetcher.Types.Lens
 import Toml (TOML, TomlCodec, (.=))
 import qualified Toml
 import Validation (validationToEither)
@@ -22,8 +27,21 @@
     go (Right x : xs) se sp = go xs se (x : sp)
     go [] [] sp = Right sp
     go [] se _ = Left se
-    tables = [fmap (uncurry (Package k)) $ validationToEither $ Toml.runTomlCodec iCodec v | (Toml.unKey -> (Toml.unPiece -> k) :| _, v) <- Toml.toList $ Toml.tomlTables toml]
-    iCodec = (,) <$> versionSourceCodec .= fst <*> fetcherCodec .= snd
+    tables = [fmap (toPackage k) $ validationToEither $ Toml.runTomlCodec iCodec v | (Toml.unKey -> (Toml.unPiece -> k) :| _, v) <- Toml.toList $ Toml.tomlTables toml]
+    toPackage k (v, f, g@GitOptions {..}) =
+      let f' v = case f v of
+            x@FetchGit {} ->
+              x
+                & branch .~ goBranch
+                & deepClone .~ fromMaybe False goDeepClone
+                & fetchSubmodules .~ fromMaybe False goFetchSubmodules
+                & leaveDotGit .~ fromMaybe False goLeaveDotGit
+            x ->
+              if not $ isGitOptionsDefault g
+                then error $ "Try to set git-prefetch configuration for a url fetcher: " <> show x
+                else x
+       in Package k v f'
+    iCodec = (,,) <$> versionSourceCodec .= (view _1) <*> fetcherCodec .= (view _2) <*> gitOptionsCodec .= (view _3)
 
 versionSourceCodec :: TomlCodec VersionSource
 versionSourceCodec =
@@ -35,46 +53,46 @@
     id
     ( Toml.textBy
         ( \case
-            GitHubRelease {..} -> owner <> "/" <> repo
+            GitHubRelease {..} -> _owner <> "/" <> _repo
             _ -> error "impossible"
         )
         ( \x -> case T.split (== '/') x of
-            [owner, repo] -> Right GitHubRelease {..}
+            [_owner, _repo] -> Right GitHubRelease {..}
             _ -> Left "unexpected github srouce: it should be something like [owner]/[repo]"
         )
         "src.github"
     )
     <|> Toml.dimatch
       ( \case
-          Git {..} -> Just vurl
+          Git {..} -> Just _vurl
           _ -> Nothing
       )
       Git
       (Toml.text "src.git")
     <|> Toml.dimatch
       ( \case
-          Pypi {..} -> Just pypi
+          Pypi {..} -> Just _pypi
           _ -> Nothing
       )
       Pypi
       (Toml.text "src.pypi")
     <|> Toml.dimatch
       ( \case
-          ArchLinux {..} -> Just archpkg
+          ArchLinux {..} -> Just _archpkg
           _ -> Nothing
       )
       ArchLinux
       (Toml.text "src.archpkg")
     <|> Toml.dimatch
       ( \case
-          Aur {..} -> Just aur
+          Aur {..} -> Just _aur
           _ -> Nothing
       )
       Aur
       (Toml.text "src.aur")
     <|> Toml.dimatch
       ( \case
-          Manual {..} -> Just manual
+          Manual {..} -> Just _manual
           _ -> Nothing
       )
       Manual
@@ -87,18 +105,18 @@
       id
       ( Toml.textBy
           ( \case
-              Repology {..} -> repology <> ":" <> repo
+              Repology {..} -> _repology <> ":" <> _repo
               _ -> error "impossible"
           )
           ( \t -> case T.split (== ':') t of
-              [repology, repo] -> Right Repology {..}
+              [_repology, _repo] -> Right Repology {..}
               _ -> Left "unexpected repology source: it should be something like [project]:[repo]"
           )
           "src.repology"
       )
 
 unsupportError :: a
-unsupportError = error "serialization of fetchers is unsupported"
+unsupportError = error "serialization is unsupported"
 
 -- | Use it only for deserialization!!!
 fetcherCodec :: TomlCodec PackageFetcher
@@ -108,7 +126,7 @@
     ( \t -> case T.split (== '/') t of
         [owner, rest] -> case T.split (== ':') rest of
           [repo, rawV] ->
-            Right $ \(coerce -> realV) -> gitHubFetcher (owner, repo) $ coerce $ T.replace "$ver" rawV realV
+            Right $ \(coerce -> realV) -> gitHubFetcher (owner, repo) $ coerce $ T.replace "$ver" realV rawV
           [repo] -> Right $ gitHubFetcher (owner, repo)
           _ -> Left "unexpected github fetcher: it should be something like [owner]/[repo] or [owner]/[repo]:[ver]"
         _ -> Left "unexpected github fetcher: it should be something like [owner]/[repo] or [owner]/[repo]:[ver]"
@@ -118,7 +136,7 @@
       unsupportError
       ( \t -> case T.split (== ':') t of
           [fpypi, rawV] ->
-            Right $ \(coerce -> realV) -> pypiFetcher fpypi $ coerce $ T.replace "$ver" rawV realV
+            Right $ \(coerce -> realV) -> pypiFetcher fpypi $ coerce $ T.replace "$ver" realV rawV
           [fpypi] -> Right $ pypiFetcher fpypi
           _ -> Left "unexpected pypi fetcher: it should be something like [pypi] or [pypi]:[ver]"
       )
@@ -127,7 +145,7 @@
       unsupportError
       ( \t -> case T.split (== ':') t of
           [furl, rawV] ->
-            Right $ \(coerce -> realV) -> gitFetcher furl $ coerce $ T.replace "$ver" rawV realV
+            Right $ \(coerce -> realV) -> gitFetcher furl $ coerce $ T.replace "$ver" realV rawV
           [furl] -> Right $ gitFetcher furl
           _ -> Left "unexpected git fetcher: it should be something like [git_url] or [git_url]:[ver]"
       )
@@ -136,3 +154,22 @@
       unsupportError
       (\t -> Right $ \(coerce -> v) -> urlFetcher $ T.replace "$ver" v t)
       "fetch.url"
+
+data GitOptions = GitOptions
+  { goBranch :: Maybe T.Text,
+    goDeepClone :: Maybe Bool,
+    goFetchSubmodules :: Maybe Bool,
+    goLeaveDotGit :: Maybe Bool
+  }
+  deriving (Eq)
+
+isGitOptionsDefault :: GitOptions -> Bool
+isGitOptionsDefault = (== GitOptions Nothing Nothing Nothing Nothing)
+
+gitOptionsCodec :: TomlCodec GitOptions
+gitOptionsCodec =
+  GitOptions
+    <$> Toml.dioptional (Toml.text "git.branch") .= goBranch
+    <*> Toml.dioptional (Toml.bool "git.deepClone") .= goDeepClone
+    <*> Toml.dioptional (Toml.bool "git.fetchSubmodules") .= goFetchSubmodules
+    <*> Toml.dioptional (Toml.bool "git.leaveDotGit") .= goLeaveDotGit
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE ViewPatterns #-}
 
 module Main where
@@ -7,56 +8,112 @@
 import Config
 import qualified Data.Text as T
 import qualified Data.Text.IO as T
-import Data.Version (showVersion)
 import Development.Shake
 import NvFetcher
-import Paths_nvfetcher
-import System.Console.GetOpt
+import Options.Applicative.Simple
+import qualified Paths_nvfetcher as Paths
 import qualified Toml
 
 data CLIOptions = CLIOptions
   { configPath :: FilePath,
     outputPath :: FilePath,
-    versionMode :: Bool,
-    logPath :: Maybe FilePath
+    logPath :: Maybe FilePath,
+    threads :: Int,
+    timing :: Bool,
+    verbose :: Bool,
+    target :: String
   }
   deriving (Show)
 
-defaultCLIArgs :: CLIOptions
-defaultCLIArgs = CLIOptions "nvfetcher.toml" "sources.nix" False Nothing
+cliOptionsParser :: Parser CLIOptions
+cliOptionsParser =
+  CLIOptions
+    <$> strOption
+      ( long "config"
+          <> short 'c'
+          <> metavar "FILE"
+          <> help "Path to nvfetcher TOML config"
+          <> value "nvfetcher.toml"
+          <> showDefault
+          <> completer (bashCompleter "file")
+      )
+      <*> strOption
+        ( long "output"
+            <> short 'o'
+            <> metavar "FILE"
+            <> help "Path to output nix file"
+            <> showDefault
+            <> value "sources.nix"
+            <> completer (bashCompleter "file")
+        )
+      <*> optional
+        ( strOption
+            ( long "changelog"
+                <> short 'l'
+                <> metavar "FILE"
+                <> help "Dump version changes to a file"
+                <> completer (bashCompleter "file")
+            )
+        )
+      <*> ( option
+              auto
+              ( short 'j'
+                  <> metavar "NUM"
+                  <> help "Number of threads (0: detected number of processors)"
+                  <> value 0
+                  <> showDefault
+              )
+          )
+      <*> switch (long "timing" <> short 't' <> help "Show build time")
+      <*> switch (long "verbose" <> short 'v' <> help "Verbose mode")
+      <*> strArgument
+        ( metavar "TARGET"
+            <> help "Two targets are available: build and clean"
+            <> value "build"
+            <> completer (listCompleter ["build", "clean"])
+        )
 
-flags :: [OptDescr (Either String (CLIOptions -> CLIOptions))]
-flags =
-  [ Option ['c'] ["config"] (ReqArg (\s -> Right $ \o -> o {configPath = s}) "FILE") "Path to nvfetcher config",
-    Option ['o'] ["output"] (ReqArg (\s -> Right $ \o -> o {outputPath = s}) "FILE") "Path to output nix file",
-    Option ['v'] ["version"] (NoArg $ Right $ \o -> o {versionMode = True}) "Print nvfetcher version",
-    Option ['l'] ["log"] (ReqArg (\s -> Right $ \o -> o {logPath = Just s}) "FILE") "Path to log file"
-  ]
+version :: String
+version = $(simpleVersion Paths.version)
 
+getCLIOptions :: IO CLIOptions
+getCLIOptions = do
+  (opts, ()) <-
+    simpleOptions
+      version
+      "nvfetcher - generate nix sources expr for the latest version of packages"
+      ( unlines
+          [ "It's important to keep .shake dir to make caches work properly.",
+            "If you change the version source or fetcher of an existing package, please run target \"clean\" to rebuild everything."
+          ]
+      )
+      cliOptionsParser
+      empty
+  pure opts
+
+main :: IO ()
+main = do
+  CLIOptions {..} <- getCLIOptions
+  let args =
+        defaultArgs
+          { argOutputFilePath = outputPath,
+            argActionAfterBuild = maybe (pure ()) logChangesToFile logPath,
+            argTarget = target,
+            argShakeOptions =
+              (argShakeOptions defaultArgs)
+                { shakeTimings = timing,
+                  shakeVerbosity = if verbose then Verbose else Info,
+                  shakeThreads = threads
+                }
+          }
+  toml <- Toml.parse <$> T.readFile configPath
+  case toml of
+    Left e -> error $ T.unpack $ Toml.prettyTomlDecodeError $ Toml.ParseError e
+    Right x -> case parseConfig x of
+      Left e -> error $ T.unpack $ Toml.prettyTomlDecodeErrors e
+      Right pkgs -> runNvFetcher args $ purePackageSet pkgs
+
 logChangesToFile :: FilePath -> Action ()
 logChangesToFile fp = do
   changes <- getVersionChanges
   writeFile' fp $ unlines $ show <$> changes
-
-main :: IO ()
-main = runNvFetcherWith flags $ \flagValues -> do
-  let CLIOptions {..} = foldl (flip id) defaultCLIArgs flagValues
-  if versionMode
-    then putStrLn ("nvfetcher " <> showVersion version) >> pure Nothing
-    else do
-      toml <- Toml.parse <$> T.readFile configPath
-      case toml of
-        Left e -> error $ T.unpack $ Toml.prettyTomlDecodeError $ Toml.ParseError e
-        Right x -> case parseConfig x of
-          Left e -> error $ T.unpack $ Toml.prettyTomlDecodeErrors e
-          Right x ->
-            pure $
-              Just
-                ( defaultArgs
-                    { argOutputFilePath = outputPath,
-                      argActionAfterBuild = case logPath of
-                        Just fp -> logChangesToFile fp
-                        _ -> pure ()
-                    },
-                  purePackageSet x
-                )
diff --git a/nvfetcher.cabal b/nvfetcher.cabal
--- a/nvfetcher.cabal
+++ b/nvfetcher.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.4
 name:            nvfetcher
-version:         0.1.0.0
+version:         0.2.0.0
 synopsis:
   Generate nix sources expr for the latest version of packages
 
@@ -24,17 +24,19 @@
 
 common common-options
   build-depends:
-    , aeson                 ^>=1.5.6
-    , base                  >=4.8    && <5
+    , aeson               ^>=1.5.6
+    , base                >=4.8    && <5
     , binary
     , bytestring
-    , extra                 ^>=1.7.9
-    , free                  ^>=5.1.5
-    , neat-interpolation    ^>=0.5.1
-    , shake                 ^>=0.19.4
+    , containers
+    , extra               ^>=1.7.9
+    , free                ^>=5.1.5
+    , microlens
+    , microlens-th
+    , neat-interpolation  ^>=0.5.1
+    , shake               ^>=0.19.4
     , text
     , transformers
-    , unordered-containers
 
   ghc-options:
     -Wall -Wcompat -Widentities -Wincomplete-uni-patterns
@@ -56,6 +58,7 @@
     NvFetcher.PackageSet
     NvFetcher.ShakeExtras
     NvFetcher.Types
+    NvFetcher.Types.Lens
 
 executable nvfetcher
   import:          common-options
@@ -70,6 +73,7 @@
     , nvfetcher
     , tomland               ^>=1.3.2
     , validation-selective
+    , optparse-simple
 
   ghc-options:     -threaded -rtsopts -with-rtsopts=-N
 
diff --git a/src/NvFetcher.hs b/src/NvFetcher.hs
--- a/src/NvFetcher.hs
+++ b/src/NvFetcher.hs
@@ -20,7 +20,7 @@
 -- import NvFetcher
 --
 -- main :: IO ()
--- main = defaultMain defaultArgs packageSet
+-- main = runNvFetcher defaultArgs packageSet
 --
 -- packageSet :: PackageSet ()
 -- packageSet = do
@@ -42,14 +42,12 @@
   ( Args (..),
     defaultArgs,
     runNvFetcher,
-    runNvFetcherWith,
     module NvFetcher.PackageSet,
     module NvFetcher.Types,
     module NvFetcher.ShakeExtras,
   )
 where
 
-import Control.Monad.Trans.Maybe
 import Data.Text (Text)
 import qualified Data.Text as T
 import Development.Shake
@@ -60,12 +58,13 @@
 import NvFetcher.PackageSet
 import NvFetcher.ShakeExtras
 import NvFetcher.Types
-import System.Console.GetOpt (OptDescr)
 
 -- | Arguments for running nvfetcher
 data Args = Args
-  { -- | tweak shake options
-    argShakeOptions :: ShakeOptions -> ShakeOptions,
+  { -- | Shake options
+    argShakeOptions :: ShakeOptions,
+    -- | Build target
+    argTarget :: String,
     -- | Output file path
     argOutputFilePath :: FilePath,
     -- | Custom rules
@@ -82,12 +81,11 @@
 defaultArgs :: Args
 defaultArgs =
   Args
-    ( \x ->
-        x
-          { shakeTimings = True,
-            shakeProgress = progressSimple
-          }
+    ( shakeOptions
+        { shakeProgress = progressSimple
+        }
     )
+    "build"
     "sources.nix"
     (pure ())
     (pure ())
@@ -95,38 +93,18 @@
 
 -- | Entry point of nvfetcher
 runNvFetcher :: Args -> PackageSet () -> IO ()
-runNvFetcher args packageSet = runNvFetcherWith [] $ const $ pure $ Just (args, packageSet)
-
--- | Like 'runNvFetcher' but allows to define custom cli flags
-runNvFetcherWith ::
-  -- | Custom flags
-  [OptDescr (Either String a)] ->
-  -- | Continuation, the build system won't run if it returns Nothing
-  ([a] -> IO (Maybe (Args, PackageSet ()))) ->
-  IO ()
-runNvFetcherWith flags f = do
-  shakeArgsOptionsWith
-    shakeOptions
-    flags
-    $ \opts flagValues argValues -> runMaybeT $ do
-      (args@Args {..}, packageSet) <- MaybeT $ f flagValues
-      pkgs <- liftIO $ runPackageSet packageSet
-      shakeExtras <- liftIO $ initShakeExtras pkgs
-      let opts' =
-            let old = argShakeOptions opts
-             in old
-                  { shakeExtra = addShakeExtra shakeExtras (shakeExtra old)
-                  }
-          rules = mainRules args
-      pure $ case argValues of
-        [] -> (opts', want ["build"] >> rules)
-        files -> (opts', want files >> rules)
+runNvFetcher args@Args {..} packageSet = do
+  pkgs <- runPackageSet packageSet
+  shakeExtras <- initShakeExtras pkgs
+  let opts =
+        argShakeOptions
+          { shakeExtra = addShakeExtra shakeExtras (shakeExtra argShakeOptions)
+          }
+      rules = mainRules args
+  shake opts $ want [argTarget] >> rules
 
 mainRules :: Args -> Rules ()
 mainRules Args {..} = do
-  addHelpSuffix "It's important to keep .shake dir if you want to get correct version changes and proper cache"
-  addHelpSuffix "Changing input packages will lead to a fully cleanup, requiring to rebuild everything next run"
-
   "clean" ~> do
     removeFilesAfter ".shake" ["//*"]
     removeFilesAfter "." [argOutputFilePath]
@@ -141,7 +119,7 @@
         else do
           putInfo "Changes:"
           putInfo $ unlines $ show <$> changes
-    writeFileChanged argOutputFilePath $ T.unpack $ srouces $ T.unlines body
+    writeFileChanged argOutputFilePath $ T.unpack $ srouces (T.unlines body) <> "\n"
     putInfo $ "Generate " <> argOutputFilePath
     argActionAfterBuild
 
diff --git a/src/NvFetcher/Core.hs b/src/NvFetcher/Core.hs
--- a/src/NvFetcher/Core.hs
+++ b/src/NvFetcher/Core.hs
@@ -42,23 +42,23 @@
       lookupPackage pkg >>= \case
         Nothing -> fail $ "Unkown package key: " <> show pkg
         Just Package {..} -> do
-          (NvcheckerResult version mOldV) <- checkVersion pversion pkg
-          prefetched <- prefetch $ pfetcher version
+          (NvcheckerResult version mOldV) <- checkVersion _pversion pkg
+          prefetched <- prefetch $ _pfetcher version
           case mOldV of
             Nothing ->
-              recordVersionChange pname Nothing version
+              recordVersionChange _pname Nothing version
             Just old
               | old /= version ->
-                recordVersionChange pname (Just old) version
+                recordVersionChange _pname (Just old) version
             _ -> pure ()
-          let result = gen pname version prefetched
+          let result = gen _pname version prefetched
           pure $ RunResult ChangedRecomputeDiff (encode' (result, version)) result
 
 -- | Run the core rule.
 -- Given a package key, run nvchecker and then prefetch it,
 -- resulting a nix source snippet like:
 --
--- @@
+-- @
 -- feeluown-core = {
 --     pname = "feeluown-core";
 --     version = "3.7.7";
@@ -67,7 +67,7 @@
 --       url = "https://pypi.io/packages/source/f/feeluown/feeluown-3.7.7.tar.gz";
 --     };
 --   };
--- @@
+-- @
 generateNixSourceExpr :: PackageKey -> Action NixExpr
 generateNixSourceExpr k = apply1 $ WithPackageKey (Core, k)
 
diff --git a/src/NvFetcher/NixFetcher.hs b/src/NvFetcher/NixFetcher.hs
--- a/src/NvFetcher/NixFetcher.hs
+++ b/src/NvFetcher/NixFetcher.hs
@@ -66,7 +66,7 @@
 
 instance ToNixExpr (NixFetcher Prefetched) where
   -- add quotation marks
-  toNixExpr f = buildNixFetcher (T.pack $ show $ T.unpack $ coerce $ sha256 f) f
+  toNixExpr f = buildNixFetcher (T.pack $ show $ T.unpack $ coerce $ _sha256 f) f
 
 instance ToNixExpr Bool where
   toNixExpr True = "true"
@@ -81,20 +81,20 @@
     let parser = A.withObject "nix-prefetch-git" $ \o -> SHA256 <$> o A..: "sha256"
     (CmdTime t, Stdout out, CmdLine c) <-
       command [EchoStderr False] "nix-prefetch-git" $
-        [T.unpack furl]
-          <> ["--rev", T.unpack $ coerce rev]
-          <> ["--fetch-submodules" | fetchSubmodules]
-          <> ["--branch-name " <> T.unpack b | b <- maybeToList branch]
-          <> ["--deepClone" | deepClone]
-          <> ["--leave-dotGit" | leaveDotGit]
-    putInfo $ "Finishing running " <> c <> ", took " <> show t <> "s"
+        [T.unpack _furl]
+          <> ["--rev", T.unpack $ coerce _rev]
+          <> ["--fetch-submodules" | _fetchSubmodules]
+          <> concat [["--branch-name", T.unpack b] | b <- maybeToList _branch]
+          <> ["--deepClone" | _deepClone]
+          <> ["--leave-dotGit" | _leaveDotGit]
+    putVerbose $ "Finishing running " <> c <> ", took " <> show t <> "s"
     let result = A.parseMaybe parser <=< A.decodeStrict $ out
     case result of
       Just x -> pure x
       _ -> fail $ "Failed to parse output from nix-prefetch-git: " <> T.unpack (T.decodeUtf8 out)
   FetchUrl {..} -> do
-    (CmdTime t, Stdout (T.decodeUtf8 -> out), CmdLine c) <- command [EchoStderr False] "nix-prefetch-url" [T.unpack furl]
-    putInfo $ "Finishing running " <> c <> ", took " <> show t <> "s"
+    (CmdTime t, Stdout (T.decodeUtf8 -> out), CmdLine c) <- command [EchoStderr False] "nix-prefetch-url" [T.unpack _furl]
+    putVerbose $ "Finishing running " <> c <> ", took " <> show t <> "s"
     case takeWhile (not . T.null) $ reverse $ T.lines out of
       [x] -> pure $ coerce x
       _ -> fail $ "Failed to parse output from nix-prefetch-url: " <> T.unpack out
@@ -102,16 +102,16 @@
 buildNixFetcher :: Text -> NixFetcher k -> Text
 buildNixFetcher sha256 = \case
   FetchGit
-    { sha256 = _,
-      rev = toNixExpr -> rev,
-      fetchSubmodules = toNixExpr -> fetchSubmodules,
-      deepClone = toNixExpr -> deepClone,
-      leaveDotGit = toNixExpr -> leaveDotGit,
+    { _sha256 = _,
+      _rev = toNixExpr -> rev,
+      _fetchSubmodules = toNixExpr -> fetchSubmodules,
+      _deepClone = toNixExpr -> deepClone,
+      _leaveDotGit = toNixExpr -> leaveDotGit,
       ..
     } ->
       [trimming|
           fetchgit {
-            url = "$furl";
+            url = "$_furl";
             rev = "$rev";
             fetchSubmodules = $fetchSubmodules;
             deepClone = $deepClone;
@@ -139,7 +139,7 @@
 prefetchRule = void $
   addOracleCache $ \(f :: NixFetcher Fresh) -> do
     sha256 <- runFetcher f
-    pure $ f {sha256 = sha256}
+    pure $ f {_sha256 = sha256}
 
 -- | Run nix fetcher
 prefetch :: NixFetcher Fresh -> Action (NixFetcher Prefetched)
diff --git a/src/NvFetcher/Nvchecker.hs b/src/NvFetcher/Nvchecker.hs
--- a/src/NvFetcher/Nvchecker.hs
+++ b/src/NvFetcher/Nvchecker.hs
@@ -58,7 +58,7 @@
         writeFile' config $ T.unpack $ genNvConfig "pkg" q
         need [config]
         (CmdTime t, Stdout out, CmdLine c) <- cmd $ "nvchecker --logger json -c " <> config
-        putInfo $ "Finishing running " <> c <> ", took " <> show t <> "s"
+        putVerbose $ "Finishing running " <> c <> ", took " <> show t <> "s"
         let out' = T.decodeUtf8 out
             result = mapMaybe (A.decodeStrict . T.encodeUtf8) (T.lines out')
         now <- case result of
@@ -79,48 +79,48 @@
     [trimming|
           [$srcName]
           source = "github"
-          github = "$owner/$repo"
+          github = "$_owner/$_repo"
           use_latest_release = true
     |]
   Git {..} ->
     [trimming|
           [$srcName]
           source = "git"
-          git = "$vurl"
+          git = "$_vurl"
           use_commit = true
     |]
   Aur {..} ->
     [trimming|
           [$srcName]
           source = "aur"
-          aur = "$aur"
+          aur = "$_aur"
           strip_release = true
     |]
   ArchLinux {..} ->
     [trimming|
           [$srcName]
           source = "archpkg"
-          archpkg = "$archpkg"
+          archpkg = "$_archpkg"
           strip_release = true
     |]
   Pypi {..} ->
     [trimming|
           [$srcName]
           source = "pypi"
-          pypi = "$pypi"
+          pypi = "$_pypi"
     |]
   Manual {..} ->
     [trimming|
           [$srcName]
           source = "manual"
-          manual = "$manual"
+          manual = "$_manual"
     |]
   Repology {..} ->
     [trimming|
           [$srcName]
           source = "repology"
-          repology = "$repology"
-          repo = "$repo"
+          repology = "$_repology"
+          repo = "$_repo"
     |]
 
 -- | Run nvchecker
diff --git a/src/NvFetcher/PackageSet.hs b/src/NvFetcher/PackageSet.hs
--- a/src/NvFetcher/PackageSet.hs
+++ b/src/NvFetcher/PackageSet.hs
@@ -41,6 +41,7 @@
 
     -- ** Two-in-one functions
     fromGitHub,
+    fromGitHub',
     fromPypi,
 
     -- ** Version sources
@@ -54,9 +55,11 @@
 
     -- ** Fetchers
     fetchGitHub,
+    fetchGitHub',
     fetchGitHubRelease,
     fetchPypi,
     fetchGit,
+    fetchGit',
     fetchUrl,
 
     -- ** Miscellaneous
@@ -65,6 +68,13 @@
     NotElem,
     coerce,
     liftIO,
+
+    -- * Lenses
+    (&),
+    (.~),
+    (%~),
+    (^.),
+    module NvFetcher.Types.Lens,
   )
 where
 
@@ -72,12 +82,14 @@
 import Control.Monad.IO.Class
 import Data.Coerce (coerce)
 import Data.Kind (Constraint, Type)
-import Data.HashMap.Strict as HMap
+import Data.Map.Strict as HMap
 import Data.Maybe (isJust)
 import Data.Text (Text)
 import GHC.TypeLits
+import Lens.Micro
 import NvFetcher.NixFetcher
 import NvFetcher.Types
+import NvFetcher.Types.Lens
 
 --------------------------------------------------------------------------------
 
@@ -118,13 +130,13 @@
 --
 -- Throws exception as more then one packages with the same name
 -- are defined
-runPackageSet :: PackageSet () -> IO (HashMap PackageKey Package)
+runPackageSet :: PackageSet () -> IO (Map PackageKey Package)
 runPackageSet = \case
   Free (NewPackage p g) ->
     runPackageSet g >>= \m ->
-      if isJust (HMap.lookup (PackageKey $ pname p) m)
-        then fail $ "Duplicate package name: " <> show (pname p)
-        else pure $ HMap.insert (PackageKey $ pname p) p m
+      if isJust (HMap.lookup (PackageKey $ _pname p) m)
+        then fail $ "Duplicate package name: " <> show (_pname p)
+        else pure $ HMap.insert (PackageKey $ _pname p) p m
   Free (EmbedIO action g) -> action >>= runPackageSet . g
   Pure _ -> pure mempty
 
@@ -179,7 +191,7 @@
 -- Example:
 --
 -- @
--- define $ package "nvfetcher-git" `sourceGit` "nvfetcher" `fetchGitHub` ("berberman", "nvfetcher")
+-- define $ package "nvfetcher-git" `sourceGit` "https://github.com/berberman/nvfetcher" `fetchGitHub` ("berberman", "nvfetcher")
 -- @
 define ::
   ( Member PackageName r,
@@ -213,8 +225,16 @@
   (Text, Text) ->
   PackageSet
     (Prod (PackageFetcher : VersionSource : r))
-fromGitHub e p = fetchGitHub (sourceGitHub e p) p
+fromGitHub e (owner, repo) = fromGitHub' e (owner, repo, id)
 
+-- | A synonym of 'fetchGitHub'' and 'sourceGitHub'
+fromGitHub' ::
+  PackageSet (Prod r) ->
+  (Text, Text, NixFetcher Fresh -> NixFetcher Fresh) ->
+  PackageSet
+    (Prod (PackageFetcher : VersionSource : r))
+fromGitHub' e p@(owner, repo, _) = fetchGitHub' (sourceGitHub e (owner, repo)) p
+
 -- | A synonym of 'fetchPypi' and 'sourcePypi'
 fromPypi ::
   PackageSet (Prod r) ->
@@ -239,7 +259,7 @@
   -- | git url
   Text ->
   PackageSet (Prod (VersionSource : r))
-sourceGit e vurl = src e Git {..}
+sourceGit e _vurl = src e Git {..}
 
 -- | This package follows the latest pypi release
 sourcePypi ::
@@ -247,7 +267,7 @@
   -- | pypi name
   Text ->
   PackageSet (Prod (VersionSource : r))
-sourcePypi e pypi = src e Pypi {..}
+sourcePypi e _pypi = src e Pypi {..}
 
 -- | This package follows the version of an Arch Linux package
 sourceArchLinux ::
@@ -255,7 +275,7 @@
   -- | package name in Arch Linux repo
   Text ->
   PackageSet (Prod (VersionSource : r))
-sourceArchLinux e archpkg = src e ArchLinux {..}
+sourceArchLinux e _archpkg = src e ArchLinux {..}
 
 -- | This package follows the version of an Aur package
 sourceAur ::
@@ -263,14 +283,14 @@
   -- | package name in Aur
   Text ->
   PackageSet (Prod (VersionSource : r))
-sourceAur e aur = src e Aur {..}
+sourceAur e _aur = src e Aur {..}
 
 -- | This package follows a pinned version
 sourceManual ::
   PackageSet (Prod r) ->
   Text ->
   PackageSet (Prod (VersionSource : r))
-sourceManual e manual = src e Manual {..}
+sourceManual e _manual = src e Manual {..}
 
 -- | This package follows the version of a repology package
 sourceRepology ::
@@ -288,8 +308,26 @@
   -- | owner and repo
   (Text, Text) ->
   PackageSet (Prod (PackageFetcher : r))
-fetchGitHub e p = fetch e $ gitHubFetcher p
+fetchGitHub e (owner, repo) = fetchGitHub' e (owner, repo, id)
 
+-- | This package is fetched from a github repo
+--
+-- Similar to 'fetchGitHub', but allows a modifier to the fetcher.
+-- For example, you can enable fetch submodules like:
+--
+-- @
+-- define $ package "qliveplayer" `sourceGitHub` ("IsoaSFlus", "QLivePlayer") `fetchGitHub'` ("IsoaSFlus", "QLivePlayer", fetchSubmodules .~ True)
+-- @
+fetchGitHub' ::
+  PackageSet (Prod r) ->
+  -- | owner and repo
+  ( Text,
+    Text,
+    NixFetcher Fresh -> NixFetcher Fresh
+  ) ->
+  PackageSet (Prod (PackageFetcher : r))
+fetchGitHub' e (owner, repo, f) = fetch e $ f . gitHubFetcher (owner, repo)
+
 -- | This package is fetched from a file in github release
 fetchGitHubRelease ::
   PackageSet (Prod r) ->
@@ -312,7 +350,18 @@
   -- | git url
   Text ->
   PackageSet (Prod (PackageFetcher : r))
-fetchGit e = fetch e . gitFetcher
+fetchGit e u = fetchGit' e (u, id)
+
+-- | This package is fetched from git
+--
+-- Similar to 'fetchGit', but allows a modifier to the fetcher.
+-- See 'fetchGitHub'' for a concret example.
+fetchGit' ::
+  PackageSet (Prod r) ->
+  -- | git url
+  (Text, NixFetcher Fresh -> NixFetcher Fresh) ->
+  PackageSet (Prod (PackageFetcher : r))
+fetchGit' e (u, f) = fetch e $ f . gitFetcher u
 
 -- | This package is fetched from url
 fetchUrl ::
diff --git a/src/NvFetcher/ShakeExtras.hs b/src/NvFetcher/ShakeExtras.hs
--- a/src/NvFetcher/ShakeExtras.hs
+++ b/src/NvFetcher/ShakeExtras.hs
@@ -27,15 +27,15 @@
 where
 
 import Control.Concurrent.Extra
-import Data.HashMap.Strict (HashMap)
-import qualified Data.HashMap.Strict as HMap
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
 import Development.Shake
 import NvFetcher.Types
 
 -- | Values we use during the build. It's stored in 'shakeExtra'
 data ShakeExtras = ShakeExtras
   { versionChanges :: Var [VersionChange],
-    targetPackages :: HashMap PackageKey Package
+    targetPackages :: Map PackageKey Package
   }
 
 -- | Get our values from shake
@@ -46,7 +46,7 @@
     _ -> fail "ShakeExtras is missing!"
 
 -- | Create an empty 'ShakeExtras' from packages to build
-initShakeExtras :: HashMap PackageKey Package -> IO ShakeExtras
+initShakeExtras :: Map PackageKey Package -> IO ShakeExtras
 initShakeExtras targetPackages = do
   versionChanges <- newVar mempty
   pure ShakeExtras {..}
@@ -55,17 +55,17 @@
 getAllPackageKeys :: Action [PackageKey]
 getAllPackageKeys = do
   ShakeExtras {..} <- getShakeExtras
-  pure $ HMap.keys targetPackages
+  pure $ Map.keys targetPackages
 
 -- | Find a package given its key
 lookupPackage :: PackageKey -> Action (Maybe Package)
 lookupPackage key = do
   ShakeExtras {..} <- getShakeExtras
-  pure $ HMap.lookup key targetPackages
+  pure $ Map.lookup key targetPackages
 
 -- | Check if we need build this package
 isPackageKeyTarget :: PackageKey -> Action Bool
-isPackageKeyTarget k = HMap.member k . targetPackages <$> getShakeExtras
+isPackageKeyTarget k = Map.member k . targetPackages <$> getShakeExtras
 
 -- | Record version change of a package
 recordVersionChange :: PackageName -> Maybe Version -> Version -> Action ()
diff --git a/src/NvFetcher/Types.hs b/src/NvFetcher/Types.hs
--- a/src/NvFetcher/Types.hs
+++ b/src/NvFetcher/Types.hs
@@ -95,13 +95,13 @@
 
 -- | The input of nvchecker
 data VersionSource
-  = GitHubRelease {owner :: Text, repo :: Text}
-  | Git {vurl :: Text}
-  | Pypi {pypi :: Text}
-  | ArchLinux {archpkg :: Text}
-  | Aur {aur :: Text}
-  | Manual {manual :: Text}
-  | Repology {repology :: Text, repo :: Text}
+  = GitHubRelease {_owner :: Text, _repo :: Text}
+  | Git {_vurl :: Text}
+  | Pypi {_pypi :: Text}
+  | ArchLinux {_archpkg :: Text}
+  | Aur {_aur :: Text}
+  | Manual {_manual :: Text}
+  | Repology {_repology :: Text, _repo :: Text}
   deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData)
 
 -- | The result of running nvchecker
@@ -123,15 +123,15 @@
 -- | If the package is prefetched, then we can obtain the SHA256
 data NixFetcher (k :: Prefetch)
   = FetchGit
-      { furl :: Text,
-        rev :: Version,
-        branch :: Maybe Text,
-        deepClone :: Bool,
-        fetchSubmodules :: Bool,
-        leaveDotGit :: Bool,
-        sha256 :: PrefetchResult k
+      { _furl :: Text,
+        _rev :: Version,
+        _branch :: Maybe Text,
+        _deepClone :: Bool,
+        _fetchSubmodules :: Bool,
+        _leaveDotGit :: Bool,
+        _sha256 :: PrefetchResult k
       }
-  | FetchUrl {furl :: Text, sha256 :: PrefetchResult k}
+  | FetchUrl {_furl :: Text, _sha256 :: PrefetchResult k}
   deriving (Typeable, Generic)
 
 -- | Prefetch status
@@ -174,9 +174,9 @@
 -- /i.e. can only be concatenated with other strings,/
 -- /in case we can't check the equality between fetcher functions./
 data Package = Package
-  { pname :: PackageName,
-    pversion :: VersionSource,
-    pfetcher :: PackageFetcher
+  { _pname :: PackageName,
+    _pversion :: VersionSource,
+    _pfetcher :: PackageFetcher
   }
 
 -- | Package key is the name of a package.
diff --git a/src/NvFetcher/Types/Lens.hs b/src/NvFetcher/Types/Lens.hs
new file mode 100644
--- /dev/null
+++ b/src/NvFetcher/Types/Lens.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+-- | Copyright: (c) 2021 berberman
+-- SPDX-License-Identifier: MIT
+-- Maintainer: berberman <berberman@yandex.com>
+-- Stability: experimental
+-- Portability: portable
+-- Lenses for "NvFetcher.Types"
+module NvFetcher.Types.Lens where
+
+import Lens.Micro.TH
+import NvFetcher.Types
+
+makeLenses ''VersionSource
+
+makeLenses ''NixFetcher
+
+makeLenses ''Package
