commit 86cf3dd0567a993d3d9b91cd6e1112ea1f87e0d9 Author: Malo Bourgon Date: Tue Jan 28 16:51:26 2020 -0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a81ca3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.stack-work/ +defaults-manager.cabal +*~ \ No newline at end of file diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..b49ee78 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,3 @@ +# Changelog for defaults-manager + +## Unreleased changes diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e637cde --- /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 index 0000000..601951b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# defaults-manager diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/app/Main.hs b/app/Main.hs new file mode 100644 index 0000000..f117164 --- /dev/null +++ b/app/Main.hs @@ -0,0 +1,40 @@ +module Main where + +import Control.Monad ( join ) +import Data.Coerce ( coerce ) +import qualified Data.Set as S +import Options.Applicative + +import Defaults + +-- | Main +main :: IO () +main = join $ execParser opts + +-- | App argument parser +opts :: ParserInfo (IO ()) +opts = info + (commands <**> helper) + (fullDesc <> header "macOS Preferences Manager - a utility for working with macOS preferences") + +-- | App CLI commands +commands :: Parser (IO ()) +commands = hsubparser + (command "watch" + (info + ( watch . S.fromList <$> some + (Domain <$> strArgument + ( metavar "DOMAIN..." + <> completer (listIOCompleter $ fmap coerce . S.toList <$> domains) + <> help "Domain(s) that will be watched" + ) + ) + <|> flag' (watch =<< domains) + ( long "all" + <> short 'a' + <> help "Watch all domains including NSGlobalDomain" + ) + ) + (progDesc "Watch domain(s) for changes") + ) + ) diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..46587de --- /dev/null +++ b/default.nix @@ -0,0 +1,2 @@ +{ nixpkgs ? import {}}: +nixpkgs.pkgs.haskell.packages.ghc865.callPackage ./prefmanager.nix { } diff --git a/package.yaml b/package.yaml new file mode 100644 index 0000000..517ea13 --- /dev/null +++ b/package.yaml @@ -0,0 +1,50 @@ +name: prefmanager +version: 0.1.0.0 +github: malob/prefmanager +author: Malo Bourgon +maintainer: mbourgon@gmail.com +copyright: 2020 Malo Bourgon +license: BSD3 +synopsis: A CLI utility for managing macOS preferences +category: CLI + +extra-source-files: +- README.md + +description: Please see the README on GitHub at + +dependencies: +- base >= 4.7 && < 5 +- async +- containers +- diffmap +- hxt +- optparse-applicative +- plist +- pretty-show +- process + +library: + source-dirs: src + +executables: + prefmanager: + main: Main.hs + source-dirs: app + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - prefmanager + +tests: + prefmanager-test: + main: Spec.hs + source-dirs: test + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - prefmanager diff --git a/prefmanager.cabal b/prefmanager.cabal new file mode 100644 index 0000000..b2e525f --- /dev/null +++ b/prefmanager.cabal @@ -0,0 +1,87 @@ +cabal-version: 1.12 + +-- This file has been generated from package.yaml by hpack version 0.33.0. +-- +-- see: https://github.com/sol/hpack +-- +-- hash: 1af3ad98cee7d7fc142970a5965d8baebd3604edff043fae4c2b5d15e4cde1dc + +name: prefmanager +version: 0.1.0.0 +synopsis: A CLI utility for managing macOS preferences +description: Please see the README on GitHub at +category: CLI +homepage: https://github.com/malob/prefmanager#readme +bug-reports: https://github.com/malob/prefmanager/issues +author: Malo Bourgon +maintainer: mbourgon@gmail.com +copyright: 2020 Malo Bourgon +license: BSD3 +license-file: LICENSE +build-type: Simple +extra-source-files: + README.md + +source-repository head + type: git + location: https://github.com/malob/prefmanager + +library + exposed-modules: + Defaults + other-modules: + Paths_prefmanager + hs-source-dirs: + src + build-depends: + async + , base >=4.7 && <5 + , containers + , diffmap + , hxt + , optparse-applicative + , plist + , pretty-show + , process + default-language: Haskell2010 + +executable prefmanager + main-is: Main.hs + other-modules: + Paths_prefmanager + hs-source-dirs: + app + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: + async + , base >=4.7 && <5 + , containers + , diffmap + , hxt + , optparse-applicative + , plist + , prefmanager + , pretty-show + , process + default-language: Haskell2010 + +test-suite prefmanager-test + type: exitcode-stdio-1.0 + main-is: Spec.hs + other-modules: + Paths_prefmanager + hs-source-dirs: + test + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: + async + , base >=4.7 && <5 + , containers + , diffmap + , hxt + , optparse-applicative + , plist + , prefmanager + , pretty-show + , process + default-language: Haskell2010 diff --git a/prefmanager.nix b/prefmanager.nix new file mode 100644 index 0000000..710edf3 --- /dev/null +++ b/prefmanager.nix @@ -0,0 +1,27 @@ +{ mkDerivation, async, base, containers, diffmap, hpack, hxt +, optparse-applicative, plist, pretty-show, process, stdenv +}: +mkDerivation { + pname = "prefmanager"; + version = "0.1.0.0"; + src = ./.; + isLibrary = true; + isExecutable = true; + libraryHaskellDepends = [ + async base containers diffmap hxt optparse-applicative plist + pretty-show process + ]; + libraryToolDepends = [ hpack ]; + executableHaskellDepends = [ + async base containers diffmap hxt optparse-applicative plist + pretty-show process + ]; + testHaskellDepends = [ + async base containers diffmap hxt optparse-applicative plist + pretty-show process + ]; + prePatch = "hpack"; + homepage = "https://github.com/malob/prefmanager#readme"; + description = "A CLI utility for managing macOS preferences"; + license = stdenv.lib.licenses.bsd3; +} diff --git a/src/Defaults.hs b/src/Defaults.hs new file mode 100644 index 0000000..9d809e6 --- /dev/null +++ b/src/Defaults.hs @@ -0,0 +1,62 @@ +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE ViewPatterns #-} +module Defaults where + +import Control.Concurrent.Async ( mapConcurrently ) +import Data.Coerce ( coerce ) +import Data.List ( delete ) +import Data.Map ( Map(..) ) +import qualified Data.Map as M +import Data.Map.Delta +import Data.Maybe +import Data.Set ( Set(..) ) +import qualified Data.Set as S +import Text.Show.Pretty +import Text.XML.Plist +import Text.XML.HXT.Core +import System.Process ( shell + , readCreateProcess + ) + +newtype Domain = Domain String deriving (Eq, Ord, Show) + +-- | Convenience function for running macOS @defaults@ command. +defaultsCmd :: String -> IO String +defaultsCmd s = readCreateProcess (shell $ "defaults " <> s) "" + +-- | Convenience function for parsing Plist strings +parsePlist :: String -> IO PlObject +parsePlist = readPlistFromString [withValidate no, withSubstDTDEntities no] + +-- | Gets list of domains by running @defaults domains@ and adds @NSGlobalDomain@ to the 'Set'. +domains :: IO (Set Domain) +domains + = S.fromList + . (Domain "NSGlobalDomain" :) + . fmap (Domain . delete ',') + . words + <$> defaultsCmd "domains" + +-- | Runs @defaults export [domain] -@ and parses the output. +export :: Domain -> IO (Map String PlObject) +export (coerce -> d) + = M.fromList + . fromJust + . fromPlDict + <$> (defaultsCmd ("export " <> d <> " -") >>= parsePlist) + +-- | Runs 'export' on the 'Set' of provided domains +exports :: Set Domain -> IO (Map Domain (Map String PlObject)) +exports = (M.fromList <$>) . mapConcurrently (\d -> (d,) <$> export d) . S.toList + +-- | Watches a 'Set' of domains for changes. +watch :: Set Domain -> IO () +watch ds = exports ds >>= go where + go old = do + new <- exports ds + let plDiffs + = M.filter (not . null) + . toDelta + . (\(DeltaUnit o n) -> diff o n) + <$> toDelta (diff old new) + (if null plDiffs then pure () else pPrint plDiffs) *> go new diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..5f9027a --- /dev/null +++ b/stack.yaml @@ -0,0 +1,8 @@ +resolver: lts-14.21 + +packages: +- . + +extra-deps: +- plist-0.0.6 +- diffmap-0.1.0.0 diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000..02ff777 --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,26 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: +- completed: + hackage: plist-0.0.6@sha256:2c25ca88b99581ef0fe44f9d7dc636f24026603624be9c43631d2b03752886b3,1702 + pantry-tree: + size: 463 + sha256: 92af2902f8f62e95ec57a5671267e2882cbe5092eaa36fce82172bbdb768cd7f + original: + hackage: plist-0.0.6 +- completed: + hackage: diffmap-0.1.0.0@sha256:27ea8c315b5dbfb243b2c3b61eab2164534e1d02887a05392dda65995cd36c3e,727 + pantry-tree: + size: 264 + sha256: 76ea9b9ad91d113addadf58cb19e289e84347ab667aa5c7feed0a1ccfa4ed613 + original: + hackage: diffmap-0.1.0.0 +snapshots: +- completed: + size: 524162 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/21.yaml + sha256: 9a55dd75853718f2bbbe951872b36a3b7802fcd71796e0f25b8664f24e34c666 + original: lts-14.21 diff --git a/test/Spec.hs b/test/Spec.hs new file mode 100644 index 0000000..cd4753f --- /dev/null +++ b/test/Spec.hs @@ -0,0 +1,2 @@ +main :: IO () +main = putStrLn "Test suite not yet implemented"