DrWho.hs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. module DrWho where
  2. import System.Environment (getArgs)
  3. import System.Directory (renameFile)
  4. import Prelude hiding (div, span)
  5. import qualified Data.Yaml as Yaml
  6. import qualified Data.ByteString as ByteString
  7. import System.IO (hSetBuffering, stdin, stdout, BufferMode (..))
  8. import Data
  9. import HTML
  10. import IntroOutro
  11. --------------------------------------------------------------------------------
  12. -- Output functions
  13. --------------------------------------------------------------------------------
  14. preamble, postamble, tableHeading :: String
  15. preamble = "<html lang=\"en\"><head><title>Avery's Doctor Who Guide</title><meta charset=\"utf-8\" /><link rel=\"stylesheet\" href=\"style.css\"></head><body>"
  16. postamble = "</body></html>"
  17. tableHeading = "<table class=\"maintable\"><tr><th>Story</th><th>Watch?</th><th>Details</th></tr>\n"
  18. output :: Table -> String
  19. output table = preamble +. introduction +. toc table +. "<hr>" +. output' table Nothing +. outro +. postamble
  20. where
  21. output' [] _ = ""
  22. output' (Doctor n seasons : rest) previousDoctor
  23. = "<a name=\"doctor" ++ show n ++ "\"></a>"
  24. +. h1 (ordinal n ++ " Doctor") ++ img ("../images/doctor-who/doctor" ++ show n ++ ".png") ("The " ++ ordinal n ++ " Doctor")
  25. +. p' "nav" (
  26. (case previousDoctor of
  27. Just doctor -> span "nav-link" (a ("#doctor" ++ show doctor) "⮴ previous doctor")
  28. Nothing -> span "nav-link" ("⮴ previous doctor")
  29. )
  30. +. (if length rest > 0
  31. then span "nav-link" (a ("#doctor" ++ show (n + 1)) "⮶ next doctor")
  32. else span "nav-link" ("⮶ next doctor")
  33. )
  34. +. span "nav-link" (a "#top" "⮬ back to top")
  35. )
  36. +. tableHeading +. outputSeasons seasons +. "</table>"
  37. +. output' rest (Just n)
  38. outputSeasons :: [Season] -> String
  39. outputSeasons s = outputSeasons' s Nothing
  40. where
  41. outputSeasons' [] _ = ""
  42. outputSeasons' [season] prevSeason = outputSeason season (prevSeason, Nothing)
  43. outputSeasons' (seasonA : seasonB : rest) prevSeason
  44. = let thisSeason = Just (seasonNum seasonA)
  45. nextSeason = Just (seasonNum seasonB)
  46. in outputSeason seasonA (prevSeason, nextSeason)
  47. ++ outputSeasons' (seasonB : rest) thisSeason
  48. outputSeason :: Season -> (Maybe SeasonNum, Maybe SeasonNum) -> String
  49. outputSeason (Season num stories) (previousSeason, nextSeason)
  50. = tr' "season" ("<td colspan=3>" ++ "<a name=\"season-" ++ num ++ "\"></a>"
  51. +. h3 ("Season " ++ num)
  52. +. p' "nav" (
  53. (case previousSeason of
  54. Just season -> span "nav-link" (a ("#season-" ++ season) "previous season ⬏")
  55. Nothing -> span "nav-link" ("previous season ⬏")
  56. )
  57. +. (case nextSeason of
  58. Just season -> span "nav-link" (a ("#season-" ++ season) "next season ⬎")
  59. Nothing -> span "nav-link" ("next season ⬎")
  60. )
  61. )
  62. +. "</td>")
  63. +. concatMap outputStory stories
  64. outputStory :: Story -> String
  65. outputStory (Story name number numEps missing recc note synopsis review majorPlotChanges)
  66. = let (nameClass, reconstruction)
  67. = case missing of
  68. None -> ("name", "")
  69. Some _ -> ("name-missing", div "reconstruction-partial" partialReconstruction)
  70. All -> ("name-missing", div "reconstruction" fullReconstruction)
  71. partialReconstruction = "<span title=\"Some of the episodes are missing, and had to be reconstructed. Usually still pretty watchable\">" ++ "Partial Reconstruction" ++ "</span>"
  72. fullReconstruction = "<span title=\"All of the episodes are missing, and had to be reconstructed from stills.\">" ++ "Full Reconstruction" ++ "</span>"
  73. in tr' "name"
  74. (td' nameClass
  75. ("<p class=" ++ nameClass ++ ">" ++ name ++ "</p>"
  76. ++ reconstruction
  77. +. "<table>"
  78. +. tr' "info" (td' "info-tag" "Story Number" ++ td (show number))
  79. +. tr' "info" (td' "info-tag" "Number of Episodes" ++ td (show numEps))
  80. +. case missing of
  81. None -> ""
  82. All -> tr' "info" (td' "info-tag" "Missing Episodes?" ++ td "Yes: all")
  83. Some eps -> tr' "info" (td' "info-tag" "Missing Episodes?" ++ td ("Yes: " ++ showEps eps))
  84. +. "</table>\n")
  85. +. td (div (show recc)
  86. (
  87. (case recc of
  88. Highly -> "✨ Highly Recommended ✨"
  89. Yes -> "Watch"
  90. Maybe -> "Maybe"
  91. Partial-> "Partial watch"
  92. No -> "Don't watch"
  93. )
  94. ++ (case note of
  95. Just text -> ", " ++ text
  96. Nothing -> ""
  97. )
  98. )
  99. )
  100. +. td ("<table class=details>"
  101. +. tr' "details" (td' "details-tag" "Synopsis" ++ td' "details-text" synopsis)
  102. +. tr' "details" (td' "details-tag" "Review" ++ td' "details-text" review)
  103. +. (case majorPlotChanges of
  104. Just changes -> tr' "details" (td' "details-tag" "Major Plot Changes"
  105. ++ td' "details-text"
  106. (simplehtml "details" $
  107. (simplehtml "summary" "Show/hide") ++ p changes))
  108. Nothing -> ""
  109. )
  110. +. "</table>"))
  111. ++ "\n"
  112. showEps :: [Int] -> String
  113. showEps [] = "none"
  114. showEps [e] = show e
  115. showEps [e1, e2] = show e1 ++ ", and " ++ show e2
  116. showEps (e:rest) = show e ++ ", " ++ showEps rest
  117. ordinal :: Int -> String
  118. ordinal n = case n of
  119. 1 -> "First"
  120. 2 -> "Second"
  121. 3 -> "Third"
  122. 4 -> "Fourth"
  123. 5 -> "Fifth"
  124. 6 -> "Sixth"
  125. 7 -> "Seventh"
  126. 8 -> "Eighth"
  127. 9 -> "Nineth"
  128. 10 -> "Tenth"
  129. 11 -> "Eleventh"
  130. 12 -> "Twelfth"
  131. 13 -> "Thirteenth"
  132. 14 -> "Fourteenth"
  133. 15 -> "Fifteenth"
  134. 16 -> "Sixteenth"
  135. 17 -> "Seventeenth"
  136. 18 -> "Eighteenth"
  137. 19 -> "Nineteenth"
  138. _ -> error "I haven't accounted for this many doctors"
  139. toc :: Table -> String
  140. toc [] = ""
  141. toc table = div "dimbox" $
  142. "<details>" +. simplehtml "summary" "Table of Contents"
  143. +. "<ol>" +. toc' table +. "</ol>"
  144. +. "</details>"
  145. where
  146. toc' [] = ""
  147. toc' (Doctor n seasons : rest) = li $ a ("#doctor" ++ show n) (ordinal n ++ " Doctor")
  148. +. simplehtml "ul" (tocSeasons seasons)
  149. +. toc' rest
  150. tocSeasons [] = ""
  151. tocSeasons (Season n _ : rest) = li $ a ("#season-" ++ n) ("Season " ++ n)
  152. +. tocSeasons rest
  153. --------------------------------------------------------------------------------
  154. --------------------------------------------------------------------------------
  155. -- Commands that can be run
  156. data Command = Output | AddDoctor | AddSeason | AddStory | Usage
  157. -- | Run a command on a table
  158. run :: Command -> Maybe FilePath -> Table -> IO ()
  159. run Usage _ _ = do args <- getArgs
  160. putStrLn $ "did not recognise args: " ++ concat args
  161. putStrLn "dw Output : outputs the table as html"
  162. putStrLn "dw add doctor : adds a new doctor to the table"
  163. putStrLn "dw add season : adds a new season to the table (interactive)"
  164. putStrLn "dw add story : adds a new story to the table (interactive)"
  165. putStrLn "dw edit story : edit a particular story"
  166. putStrLn ""
  167. putStrLn $ "The table is stored in " ++ file
  168. run Output Nothing table = putStrLn $ output table
  169. run Output (Just outputFile) table = writeFile outputFile (output table)
  170. run AddDoctor _ table = do writeOut $ addDoctor table
  171. run AddSeason _ table = do doctor <- prompt "To doctor: "
  172. season <- prompt "Season Number: "
  173. let result = addSeason (read doctor) season table
  174. case result of
  175. Just newTable -> do writeOut newTable
  176. Nothing -> print "couldn't add season!"
  177. run AddStory _ table = do season <- prompt "Season: "
  178. name <- prompt "Name: "
  179. number <- prompt "Number: "
  180. numEpisodes <- prompt "Number of Episodes: "
  181. missing <- prompt "Missing: "
  182. recommendation <- prompt "Recommendation: "
  183. note <- prompt "Note: "
  184. synopsis <- prompt "Synopsis: "
  185. review <- prompt "Review: "
  186. let result = addStory (Story name (read number) (read numEpisodes) (read missing) (read recommendation) (readNote note) synopsis review Nothing) season table
  187. case result of
  188. Just newTable -> do writeOut newTable
  189. Nothing -> print "couldn't add story!"
  190. realMain :: IO ()
  191. realMain = do arg <- getArgs
  192. hSetBuffering stdin LineBuffering
  193. hSetBuffering stdout NoBuffering
  194. fileContent <- ByteString.readFile file
  195. let Right table = Yaml.decodeEither fileContent
  196. case arg of
  197. ["output"] -> run Output Nothing table
  198. ["output", outputFile] -> run Output (Just outputFile) table
  199. ["add", "doctor"] -> run AddDoctor Nothing table
  200. ["add", "season"] -> run AddSeason Nothing table
  201. ["add", "story"] -> run AddStory Nothing table
  202. _ -> run Usage Nothing table
  203. -- | Prompt the user for a response
  204. prompt :: String -> IO String
  205. prompt text = putStr text >> getLine >>= return
  206. file, tmpfile, backup :: FilePath
  207. file = "DrWhoDB.yaml"
  208. tmpfile = "DrWhoDB_tmp"
  209. backup = "DrWhoDB.bak"
  210. readNote :: String -> Maybe Note
  211. readNote "" = Nothing
  212. readNote n = Just n
  213. writeOut :: Table -> IO ()
  214. writeOut table = do ByteString.writeFile tmpfile (Yaml.encode table)
  215. renameFile file backup
  216. renameFile tmpfile file