{"action":"created","comment":{"url":"https://api.github.com/repos/joehillen/craft/pulls/comments/52412616","id":52412616,"diff_hunk":"@@ -0,0 +1,384 @@\n+{-# LANGUAGE FlexibleInstances #-}\n+{-# LANGUAGE TemplateHaskell #-}\n+\n+module Craft.Systemd where\n+\n+import Control.Lens\n+import Data.List.Utils (join)\n+\n+import Craft\n+import Craft.File\n+\n+-- Helpful: https://www.digitalocean.com/community/tutorials/understanding-systemd-units-and-unit-files\n+\n+-- A service name is simply the name of that service\n+type ServiceName = String\n+\n+-- All systemd Units can be transformed to their plaintext output\n+class WritableUnit a where\n+  transformUnit :: a -> String\n+\n+-- A generic implementation for the String type\n+instance WritableUnit String where\n+  transformUnit sn = show sn\n+\n+-- A generic implementation for the Bool type\n+instance WritableUnit Bool where\n+  transformUnit True = \"true\"\n+  transformUnit False = \"false\"\n+\n+-- A generic implementation for the Int type\n+instance WritableUnit Int where\n+  transformUnit i = show i\n+\n+-- Composite units need to be able to furnish a filename (*without* a path)\n+class CompositeUnit unit where\n+  getFileName :: unit -> FilePath\n+\n+-- | Helpers |--\n+-- =====================================================================\n+-- Functions for extracting optional values to be transformed\n+writeOptionalSection :: WritableUnit a => Maybe a -> String\n+writeOptionalSection unit =\n+  case unit of\n+    {\n+      Nothing -> \"\";\n+      Just u -> transformUnit u ++ \"\\n\"\n+    }\n+\n+-- Optionally write a directive\n+writeOptional :: WritableUnit a => String -> Maybe a -> String\n+writeOptional directive optionalParam =\n+  case optionalParam of\n+    {\n+      Nothing -> \"\";\n+      Just param -> directive ++ \"=\" ++ transformUnit param ++ \"\\n\"\n+    }\n+\n+writeOptionalList :: WritableUnit a => String -> Maybe [a] -> String\n+writeOptionalList directive optionalParams =\n+  case optionalParams of\n+    {\n+      Nothing -> \"\";\n+      Just params -> directive ++ \"=\" ++\n+      (join \" \" (map transformUnit params)) ++ \"\\n\"\n+    }\n+-- =====================================================================\n+\n+-- | [Unit] |--\n+-- =====================================================================\n+data UnitSection = UnitSection { _description   :: String\n+                               , _documentation :: Maybe String\n+                               , _requires      :: Maybe [ServiceName]\n+                               , _wants         :: Maybe [ServiceName]","path":"src/Craft/Systemd.hs","position":73,"original_position":73,"commit_id":"8288e75d8706140d3568d5f44f50b85001040e4f","original_commit_id":"8288e75d8706140d3568d5f44f50b85001040e4f","user":{"login":"joehillen","id":139492,"avatar_url":"https://avatars.githubusercontent.com/u/139492?v=3","gravatar_id":"","url":"https://api.github.com/users/joehillen","html_url":"https://github.com/joehillen","followers_url":"https://api.github.com/users/joehillen/followers","following_url":"https://api.github.com/users/joehillen/following{/other_user}","gists_url":"https://api.github.com/users/joehillen/gists{/gist_id}","starred_url":"https://api.github.com/users/joehillen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joehillen/subscriptions","organizations_url":"https://api.github.com/users/joehillen/orgs","repos_url":"https://api.github.com/users/joehillen/repos","events_url":"https://api.github.com/users/joehillen/events{/privacy}","received_events_url":"https://api.github.com/users/joehillen/received_events","type":"User","site_admin":false},"body":"> Any ideas how to express an optional string then?\r\n\r\nUgh, yeah, this is one of the worst warts in Haskell, for example `head \"\"` is a runtime error. I think for `String` it's fine to use `Maybe String` even though it's just a type synonym for `Maybe [Char]`. To avoid problems, we can handle `Nothing` and `Just \"\"` the same way.","created_at":"2016-02-10T04:06:58Z","updated_at":"2016-02-10T04:06:58Z","html_url":"https://github.com/joehillen/craft/pull/1#discussion_r52412616","pull_request_url":"https://api.github.com/repos/joehillen/craft/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/joehillen/craft/pulls/comments/52412616"},"html":{"href":"https://github.com/joehillen/craft/pull/1#discussion_r52412616"},"pull_request":{"href":"https://api.github.com/repos/joehillen/craft/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/joehillen/craft/pulls/1","id":58828202,"html_url":"https://github.com/joehillen/craft/pull/1","diff_url":"https://github.com/joehillen/craft/pull/1.diff","patch_url":"https://github.com/joehillen/craft/pull/1.patch","issue_url":"https://api.github.com/repos/joehillen/craft/issues/1","number":1,"state":"open","locked":false,"title":"WIP: Initial start of Systemd implementation","user":{"login":"dakrone","id":19060,"avatar_url":"https://avatars.githubusercontent.com/u/19060?v=3","gravatar_id":"","url":"https://api.github.com/users/dakrone","html_url":"https://github.com/dakrone","followers_url":"https://api.github.com/users/dakrone/followers","following_url":"https://api.github.com/users/dakrone/following{/other_user}","gists_url":"https://api.github.com/users/dakrone/gists{/gist_id}","starred_url":"https://api.github.com/users/dakrone/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dakrone/subscriptions","organizations_url":"https://api.github.com/users/dakrone/orgs","repos_url":"https://api.github.com/users/dakrone/repos","events_url":"https://api.github.com/users/dakrone/events{/privacy}","received_events_url":"https://api.github.com/users/dakrone/received_events","type":"User","site_admin":false},"body":"This is a work-in-progress PR so I can get some feedback in a slightly easier way than a chat room :)\r\n\r\nI'm on the fence about a couple of things:\r\n\r\n- I want to enforce that 'service' units contain optional 'unit' section, 'service' section, and optional 'install' section and 'mount' units contain optional 'unit' section, 'mount' section, and optional 'install' section, etc etc etc, but I'm not sure about making each one its own type. I'm sure there's a more Haskell-y way to do this\r\n- Originally I asked about making this a `Config` format and part of the `Config.*` packages, and you said it should just deal with `watchCraft` instead. I'm curious why or why not? It *seems* like a fit for the config format but that's just my thoughts.\r\n- While I am happy that this is getting closer to strongly-typed (still more to do!), it still feels like I'm doing things non-idiomatically for Haskell, I think I'm missing something that would make this feel a little less tedious (or maybe I'm not? :))\r\n","created_at":"2016-02-09T23:50:02Z","updated_at":"2016-02-10T04:06:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c20453dffe2125e14e8625b421069fde590fba7d","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/joehillen/craft/pulls/1/commits","review_comments_url":"https://api.github.com/repos/joehillen/craft/pulls/1/comments","review_comment_url":"https://api.github.com/repos/joehillen/craft/pulls/comments{/number}","comments_url":"https://api.github.com/repos/joehillen/craft/issues/1/comments","statuses_url":"https://api.github.com/repos/joehillen/craft/statuses/8288e75d8706140d3568d5f44f50b85001040e4f","head":{"label":"dakrone:systemd","ref":"systemd","sha":"8288e75d8706140d3568d5f44f50b85001040e4f","user":{"login":"dakrone","id":19060,"avatar_url":"https://avatars.githubusercontent.com/u/19060?v=3","gravatar_id":"","url":"https://api.github.com/users/dakrone","html_url":"https://github.com/dakrone","followers_url":"https://api.github.com/users/dakrone/followers","following_url":"https://api.github.com/users/dakrone/following{/other_user}","gists_url":"https://api.github.com/users/dakrone/gists{/gist_id}","starred_url":"https://api.github.com/users/dakrone/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dakrone/subscriptions","organizations_url":"https://api.github.com/users/dakrone/orgs","repos_url":"https://api.github.com/users/dakrone/repos","events_url":"https://api.github.com/users/dakrone/events{/privacy}","received_events_url":"https://api.github.com/users/dakrone/received_events","type":"User","site_admin":false},"repo":{"id":51182303,"name":"craft","full_name":"dakrone/craft","owner":{"login":"dakrone","id":19060,"avatar_url":"https://avatars.githubusercontent.com/u/19060?v=3","gravatar_id":"","url":"https://api.github.com/users/dakrone","html_url":"https://github.com/dakrone","followers_url":"https://api.github.com/users/dakrone/followers","following_url":"https://api.github.com/users/dakrone/following{/other_user}","gists_url":"https://api.github.com/users/dakrone/gists{/gist_id}","starred_url":"https://api.github.com/users/dakrone/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dakrone/subscriptions","organizations_url":"https://api.github.com/users/dakrone/orgs","repos_url":"https://api.github.com/users/dakrone/repos","events_url":"https://api.github.com/users/dakrone/events{/privacy}","received_events_url":"https://api.github.com/users/dakrone/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dakrone/craft","description":"","fork":true,"url":"https://api.github.com/repos/dakrone/craft","forks_url":"https://api.github.com/repos/dakrone/craft/forks","keys_url":"https://api.github.com/repos/dakrone/craft/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dakrone/craft/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dakrone/craft/teams","hooks_url":"https://api.github.com/repos/dakrone/craft/hooks","issue_events_url":"https://api.github.com/repos/dakrone/craft/issues/events{/number}","events_url":"https://api.github.com/repos/dakrone/craft/events","assignees_url":"https://api.github.com/repos/dakrone/craft/assignees{/user}","branches_url":"https://api.github.com/repos/dakrone/craft/branches{/branch}","tags_url":"https://api.github.com/repos/dakrone/craft/tags","blobs_url":"https://api.github.com/repos/dakrone/craft/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dakrone/craft/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dakrone/craft/git/refs{/sha}","trees_url":"https://api.github.com/repos/dakrone/craft/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dakrone/craft/statuses/{sha}","languages_url":"https://api.github.com/repos/dakrone/craft/languages","stargazers_url":"https://api.github.com/repos/dakrone/craft/stargazers","contributors_url":"https://api.github.com/repos/dakrone/craft/contributors","subscribers_url":"https://api.github.com/repos/dakrone/craft/subscribers","subscription_url":"https://api.github.com/repos/dakrone/craft/subscription","commits_url":"https://api.github.com/repos/dakrone/craft/commits{/sha}","git_commits_url":"https://api.github.com/repos/dakrone/craft/git/commits{/sha}","comments_url":"https://api.github.com/repos/dakrone/craft/comments{/number}","issue_comment_url":"https://api.github.com/repos/dakrone/craft/issues/comments{/number}","contents_url":"https://api.github.com/repos/dakrone/craft/contents/{+path}","compare_url":"https://api.github.com/repos/dakrone/craft/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dakrone/craft/merges","archive_url":"https://api.github.com/repos/dakrone/craft/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dakrone/craft/downloads","issues_url":"https://api.github.com/repos/dakrone/craft/issues{/number}","pulls_url":"https://api.github.com/repos/dakrone/craft/pulls{/number}","milestones_url":"https://api.github.com/repos/dakrone/craft/milestones{/number}","notifications_url":"https://api.github.com/repos/dakrone/craft/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dakrone/craft/labels{/name}","releases_url":"https://api.github.com/repos/dakrone/craft/releases{/id}","deployments_url":"https://api.github.com/repos/dakrone/craft/deployments","created_at":"2016-02-06T00:14:52Z","updated_at":"2016-02-06T00:14:53Z","pushed_at":"2016-02-09T23:44:40Z","git_url":"git://github.com/dakrone/craft.git","ssh_url":"git@github.com:dakrone/craft.git","clone_url":"https://github.com/dakrone/craft.git","svn_url":"https://github.com/dakrone/craft","homepage":null,"size":222,"stargazers_count":0,"watchers_count":0,"language":"Haskell","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"joehillen:master","ref":"master","sha":"9c842272461677770df697ec194f3199b674f15c","user":{"login":"joehillen","id":139492,"avatar_url":"https://avatars.githubusercontent.com/u/139492?v=3","gravatar_id":"","url":"https://api.github.com/users/joehillen","html_url":"https://github.com/joehillen","followers_url":"https://api.github.com/users/joehillen/followers","following_url":"https://api.github.com/users/joehillen/following{/other_user}","gists_url":"https://api.github.com/users/joehillen/gists{/gist_id}","starred_url":"https://api.github.com/users/joehillen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joehillen/subscriptions","organizations_url":"https://api.github.com/users/joehillen/orgs","repos_url":"https://api.github.com/users/joehillen/repos","events_url":"https://api.github.com/users/joehillen/events{/privacy}","received_events_url":"https://api.github.com/users/joehillen/received_events","type":"User","site_admin":false},"repo":{"id":40329763,"name":"craft","full_name":"joehillen/craft","owner":{"login":"joehillen","id":139492,"avatar_url":"https://avatars.githubusercontent.com/u/139492?v=3","gravatar_id":"","url":"https://api.github.com/users/joehillen","html_url":"https://github.com/joehillen","followers_url":"https://api.github.com/users/joehillen/followers","following_url":"https://api.github.com/users/joehillen/following{/other_user}","gists_url":"https://api.github.com/users/joehillen/gists{/gist_id}","starred_url":"https://api.github.com/users/joehillen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joehillen/subscriptions","organizations_url":"https://api.github.com/users/joehillen/orgs","repos_url":"https://api.github.com/users/joehillen/repos","events_url":"https://api.github.com/users/joehillen/events{/privacy}","received_events_url":"https://api.github.com/users/joehillen/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/joehillen/craft","description":"","fork":false,"url":"https://api.github.com/repos/joehillen/craft","forks_url":"https://api.github.com/repos/joehillen/craft/forks","keys_url":"https://api.github.com/repos/joehillen/craft/keys{/key_id}","collaborators_url":"https://api.github.com/repos/joehillen/craft/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/joehillen/craft/teams","hooks_url":"https://api.github.com/repos/joehillen/craft/hooks","issue_events_url":"https://api.github.com/repos/joehillen/craft/issues/events{/number}","events_url":"https://api.github.com/repos/joehillen/craft/events","assignees_url":"https://api.github.com/repos/joehillen/craft/assignees{/user}","branches_url":"https://api.github.com/repos/joehillen/craft/branches{/branch}","tags_url":"https://api.github.com/repos/joehillen/craft/tags","blobs_url":"https://api.github.com/repos/joehillen/craft/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/joehillen/craft/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/joehillen/craft/git/refs{/sha}","trees_url":"https://api.github.com/repos/joehillen/craft/git/trees{/sha}","statuses_url":"https://api.github.com/repos/joehillen/craft/statuses/{sha}","languages_url":"https://api.github.com/repos/joehillen/craft/languages","stargazers_url":"https://api.github.com/repos/joehillen/craft/stargazers","contributors_url":"https://api.github.com/repos/joehillen/craft/contributors","subscribers_url":"https://api.github.com/repos/joehillen/craft/subscribers","subscription_url":"https://api.github.com/repos/joehillen/craft/subscription","commits_url":"https://api.github.com/repos/joehillen/craft/commits{/sha}","git_commits_url":"https://api.github.com/repos/joehillen/craft/git/commits{/sha}","comments_url":"https://api.github.com/repos/joehillen/craft/comments{/number}","issue_comment_url":"https://api.github.com/repos/joehillen/craft/issues/comments{/number}","contents_url":"https://api.github.com/repos/joehillen/craft/contents/{+path}","compare_url":"https://api.github.com/repos/joehillen/craft/compare/{base}...{head}","merges_url":"https://api.github.com/repos/joehillen/craft/merges","archive_url":"https://api.github.com/repos/joehillen/craft/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/joehillen/craft/downloads","issues_url":"https://api.github.com/repos/joehillen/craft/issues{/number}","pulls_url":"https://api.github.com/repos/joehillen/craft/pulls{/number}","milestones_url":"https://api.github.com/repos/joehillen/craft/milestones{/number}","notifications_url":"https://api.github.com/repos/joehillen/craft/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/joehillen/craft/labels{/name}","releases_url":"https://api.github.com/repos/joehillen/craft/releases{/id}","deployments_url":"https://api.github.com/repos/joehillen/craft/deployments","created_at":"2015-08-06T22:29:46Z","updated_at":"2016-02-09T21:22:25Z","pushed_at":"2016-02-09T23:50:02Z","git_url":"git://github.com/joehillen/craft.git","ssh_url":"git@github.com:joehillen/craft.git","clone_url":"https://github.com/joehillen/craft.git","svn_url":"https://github.com/joehillen/craft","homepage":null,"size":219,"stargazers_count":2,"watchers_count":2,"language":"Haskell","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/joehillen/craft/pulls/1"},"html":{"href":"https://github.com/joehillen/craft/pull/1"},"issue":{"href":"https://api.github.com/repos/joehillen/craft/issues/1"},"comments":{"href":"https://api.github.com/repos/joehillen/craft/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/joehillen/craft/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/joehillen/craft/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/joehillen/craft/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/joehillen/craft/statuses/8288e75d8706140d3568d5f44f50b85001040e4f"}}}}