Commit a9673a53ebc7c7bd42847da645dae36909f3605d

Authored by David Shere
1 parent 300f97ca1c
Exists in master

Changed configuration code a bit to allow for easier undefined defaults.

Showing 4 changed files with 41 additions and 33 deletions Inline Diff

foldbanner.cabal View file @ a9673a5
name: foldbanner 1 1 name: foldbanner
version: 0.1.0.4 2 2 version: 0.1.0.5
synopsis: Folding@home banner generator. 3 3 synopsis: Folding@home banner generator.
description: Foldbanner is a utility for generating Folding@home banners for users and teams. It uses a YAML configuration file containing the keys and positioning for the text on the banner. 4 4 description: Foldbanner is a utility for generating Folding@home banners for users and teams. It uses a YAML configuration file containing the keys and positioning for the text on the banner.
license: MIT 5 5 license: MIT
license-file: LICENSE 6 6 license-file: LICENSE
author: David Shere 7 7 author: David Shere
-- maintainer: 8 8 -- maintainer:
copyright: (c) 2012-2013 David Shere 9 9 copyright: (c) 2012-2013 David Shere
-- category: 10 10 -- category:
build-type: Simple 11 11 build-type: Simple
cabal-version: >=1.8 12 12 cabal-version: >=1.8
13 13
executable foldbanner 14 14 executable foldbanner
main-is: Main.hs 15 15 main-is: Main.hs
-- other-modules: 16 16 -- other-modules:
build-depends: base ==4.6.*, bytestring ==0.10.*, containers ==0.5.*, yaml-light ==0.1.*, split ==0.2.*, cairo ==0.12.*, download-curl ==0.1.*, cmdargs ==0.10.*, directory ==1.2.*, xml ==1.3.* 17 17 build-depends: base ==4.6.*, bytestring ==0.10.*, containers ==0.5.*, yaml-light ==0.1.*, split ==0.2.*, cairo ==0.12.*, download-curl ==0.1.*, cmdargs ==0.10.*, directory ==1.2.*, xml ==1.3.*, JuicyPixels
hs-source-dirs: src 18 18 hs-source-dirs: src
19 19
res/cfg/user.yaml View file @ a9673a5
## Example User Configuration File 1 1 ## Example User Configuration File
2 2
# queryURL is the url used to request the XML user statistics file. 3 3 # queryURL is the url used to request the XML user statistics file.
# This is the url for querying the user database 4 4 # This is the url for querying the user database
query_url: http://folding.extremeoverclocking.com/xml/user_summary.php?u= 5 5 query_url: http://folding.extremeoverclocking.com/xml/user_summary.php?u=
6 6
# Position individual stat positions will be offset from. 7 7 # Position individual stat positions will be offset from.
# Defaults to 0,0 if not defined. Defined here anyway just for the sake of example. 8 8 # Defaults to 0,0
position_origin: 9 9 #position_origin:
x: 0 10 10 # x: 0
y: 0 11 11 # y: 0
12 12
# Default font settings. Any of these may be overridden by defining them 13 13 # Default font settings. Any of these may be overridden by defining them
# in an individual key's configuration section. 14 14 # in an individual key's configuration section.
font_face: monospace # The font to use 15 15 #font_face: monospace # The font to use. Defaults to monospace
font_size: 12 # The font size 16 16 #font_size: 12 # The font size. Defaults to 12
stroke_width: 0.25 # The stroke width to apply to the font. 17 17 #stroke_width: 0.25 # The stroke width to apply to the font. Defaults to 0.25
key_color: # The key color 18 18 key_color: # The key color. Defaults to Black (0,0,0,1)
r: 1.0 19 19 r: 1.0
g: 1.0 20 20 g: 1.0
b: 1.0 21 21 b: 1.0
a: 1.0 22 22 a: 1.0
val_color: # The value color 23 23 val_color: # The value color. Defaults to Black (0,0,0,1)
r: 1.0 24 24 r: 1.0
g: 1.0 25 25 g: 1.0
b: 0.0 26 26 b: 0.0
a: 1.0 27 27 a: 1.0
28 28
# Individual stat configs 29 29 # Individual stat configs
stat_configs: 30 30 stat_configs:
- key: User_Name # XML element name for the statistic. 31 31 - key: User_Name # XML element name for the statistic.
type: user # Type of statistic (user, team, status) 32 32 type: user # Type of statistic (user, team, status)
name: " User: " # Display name for this key. 33 33 name: " User: " # Display name for this key.
use_commas: False # Insert commas every three characters? (True or False) 34 34 # use_commas: False # Insert commas every three characters? Defaults to False
position_offset: # lower left corner of the output for this statistic 35 35 position_offset: # lower left corner of the output for this statistic
x: 12 # as offset from the position_origin 36 36 x: 12 # as offset from the position_origin
y: 20 37 37 y: 20
38 38
- key: Team_Rank 39 39 - key: Team_Rank
type: user 40 40 type: user
name: " Rank on Team: " 41 41 name: " Rank on Team: "
use_commas: True 42 42 use_commas: True
position_offset: 43 43 position_offset:
x: 12 44 44 x: 12
y: 32 45 45 y: 32
46 46
- key: Points_24hr_Avg 47 47 - key: Points_24hr_Avg
type: team 48 48 type: team
name: " Team Daily Avg: " 49 49 name: " Team Daily Avg: "
use_commas: True 50 50 use_commas: True
position_offset: 51 51 position_offset:
x: 203 52 52 x: 203
y: 68 53 53 y: 68
54 54
- key: Points_Today 55 55 - key: Points_Today
type: team 56 56 type: team
name: " Points Today: " 57 57 name: " Points Today: "
use_commas: True 58 58 use_commas: True
position_offset: 59 59 position_offset:
x: 203 60 60 x: 203
y: 80 61 61 y: 80
62 62
- key: Users 63 63 - key: Users
type: team 64 64 type: team
name: " Total Users: " 65 65 name: " Total Users: "
use_commas: True 66 66 use_commas: True
position_offset: 67 67 position_offset:
x: 203 68 68 x: 203
y: 44 69 69 y: 44
70 70
- key: Points 71 71 - key: Points
type: team 72 72 type: team
name: " Team Points: " 73 73 name: " Team Points: "
use_commas: True 74 74 use_commas: True
position_offset: 75 75 position_offset:
x: 203 76 76 x: 203
y: 56 77 77 y: 56
78 78
- key: TeamID 79 79 - key: TeamID
type: team 80 80 type: team
name: " Team: " 81 81 name: " Team: "
use_commas: False 82
position_offset: 83 82 position_offset:
x: 203 84 83 x: 203
y: 20 85 84 y: 20
86 85
- key: Team_Name 87 86 - key: Team_Name
type: team 88 87 type: team
name: " " 89 88 name: " "
use_commas: False 90
position_offset: 91 89 position_offset:
x: 203 92 90 x: 203
y: 20 93 91 y: 20
94 92
- key: Rank 95 93 - key: Rank
type: team 96 94 type: team
name: " Rank of Team: " 97 95 name: " Rank of Team: "
use_commas: True 98 96 use_commas: True
position_offset: 99 97 position_offset:
x: 203 100 98 x: 203
y: 32 101 99 y: 32
102 100
- key: Overall_Rank 103 101 - key: Overall_Rank
type: user 104 102 type: user
name: " Overall Rank: " 105 103 name: " Overall Rank: "
use_commas: True 106 104 use_commas: True
position_offset: 107 105 position_offset:
x: 12 108 106 x: 12
y: 44 109 107 y: 44
110 108
- key: WUs 111 109 - key: WUs
type: user 112 110 type: user
name: " User WUs: " 113 111 name: " User WUs: "
use_commas: True 114 112 use_commas: True
position_offset: 115 113 position_offset:
x: 12 116 114 x: 12
y: 80 117 115 y: 80
118 116
- key: Points 119 117 - key: Points
type: user 120 118 type: user
name: " User Points: " 121 119 name: " User Points: "
use_commas: True 122 120 use_commas: True
position_offset: 123 121 position_offset:
x: 12 124 122 x: 12
y: 56 125 123 y: 56
126 124
- key: Points_24hr_Avg 127 125 - key: Points_24hr_Avg
src/BannerConfig.hs View file @ a9673a5
{-# LANGUAGE OverloadedStrings #-} 1 1 {-# LANGUAGE OverloadedStrings #-}
2 2
module BannerConfig 3 3 module BannerConfig
(BannerConfig( 4 4 (BannerConfig(
BannerConfig 5 5 BannerConfig
,queryURL 6 6 ,queryURL
,originPosition 7 7 ,originPosition
,defaultFontFace 8 8 ,defaultFontFace
,defaultFontSize 9 9 ,defaultFontSize
,defaultKeyColor 10 10 ,defaultKeyColor
,defaultValueColor 11 11 ,defaultValueColor
,defaultStrokeWidth 12 12 ,defaultStrokeWidth
--,defaultUseCommas 13 13 ,defaultUseCommas
,statConfigs) 14 14 ,statConfigs)
,StatConfig( 15 15 ,StatConfig(
StatConfig 16 16 StatConfig
,key 17 17 ,key
,keyType 18 18 ,keyType
,name 19 19 ,name
,position 20 20 ,position
,fontFace 21 21 ,fontFace
,fontSize 22 22 ,fontSize
,keyColor 23 23 ,keyColor
,valueColor 24 24 ,valueColor
,strokeWidth 25 25 ,strokeWidth
,useCommas) 26 26 ,useCommas)
,Color( 27 27 ,Color(
Color 28 28 Color
,red 29 29 ,red
,green 30 30 ,green
,blue 31 31 ,blue
,alpha) 32 32 ,alpha)
,Position( 33 33 ,Position(
Position 34 34 Position
,x 35 35 ,x
,y) 36 36 ,y)
,readBannerConfig) 37 37 ,readBannerConfig)
where 38 38 where
39 39
import qualified Data.ByteString.Char8 as BS 40 40 import qualified Data.ByteString.Char8 as BS
import qualified Data.Map as M 41 41 import qualified Data.Map as M
import Data.Map (Map) 42 42 import Data.Map (Map)
import Data.Maybe 43 43 import Data.Maybe
import Data.Yaml.YamlLight 44 44 import Data.Yaml.YamlLight
45 45
data BannerConfig = BannerConfig 46 46 data BannerConfig = BannerConfig
{ queryURL :: Maybe String 47 47 { queryURL :: Maybe String
, originPosition :: Position -- The origin point for writing stats. 48 48 , originPosition :: Position -- The origin point for writing stats.
, defaultFontFace :: String -- Default settings 49 49 , defaultFontFace :: String -- Default settings
, defaultFontSize :: Double -- 50 50 , defaultFontSize :: Double --
, defaultKeyColor :: Color -- 51 51 , defaultKeyColor :: Color --
, defaultValueColor :: Color -- 52 52 , defaultValueColor :: Color --
, defaultStrokeWidth :: Double -- 53 53 , defaultStrokeWidth :: Double --
--, defaultUseCommas :: Bool -- 54 54 , defaultUseCommas :: Bool --
, statConfigs :: [StatConfig] 55 55 , statConfigs :: [StatConfig]
} deriving (Show) 56 56 } deriving (Show)
57 57
data StatConfig = StatConfig 58 58 data StatConfig = StatConfig
{ key :: String -- The statistic's key, ie: Team_Name or Points 59 59 { key :: String -- The statistic's key, ie: Team_Name or Points
, keyType :: String -- The section the key is in: user, team, status 60 60 , keyType :: String -- The section the key is in: user, team, status
, name :: String -- The name to display for the key 61 61 , name :: String -- The name to display for the key
, position :: Position -- The x,y position for this statistic 62 62 , position :: Position -- The x,y position for this statistic
, fontFace :: Maybe String -- The font to use 63 63 , fontFace :: Maybe String -- The font to use
, fontSize :: Maybe Double -- The font size 64 64 , fontSize :: Maybe Double -- The font size
, keyColor :: Maybe Color -- The color to use for the key name 65 65 , keyColor :: Maybe Color -- The color to use for the key name
, valueColor :: Maybe Color -- The color to use for the value 66 66 , valueColor :: Maybe Color -- The color to use for the value
, strokeWidth :: Maybe Double -- The amount to stroke the text 67 67 , strokeWidth :: Maybe Double -- The amount to stroke the text
, useCommas :: Maybe Bool -- Wether or not to add commas 68 68 , useCommas :: Bool -- Wether or not to add commas
} deriving (Show) 69 69 } deriving (Show)
70 70
data Color = Color 71 71 data Color = Color
{ red :: Double 72 72 { red :: Double
, green :: Double 73 73 , green :: Double
, blue :: Double 74 74 , blue :: Double
, alpha :: Double 75 75 , alpha :: Double
} deriving (Show) 76 76 } deriving (Show)
77 77
data Position = Position 78 78 data Position = Position
{ x :: Double 79 79 { x :: Double
, y :: Double 80 80 , y :: Double
} deriving (Show) 81 81 } deriving (Show)
82 82
83 83
-- Converts a configuration file into a BannerConfig value 84 84 -- Converts a configuration file into a BannerConfig value
readBannerConfig :: FilePath -> IO (Maybe BannerConfig) 85 85 readBannerConfig :: FilePath -> IO (Maybe BannerConfig)
readBannerConfig configFile = do 86 86 readBannerConfig configFile = do
yaml <- parseYamlFile configFile 87 87 yaml <- parseYamlFile configFile
return $ getBannerConfig yaml 88 88 return $ getBannerConfig yaml
where 89 89 where
getBannerConfig map = unMap map >>= makeBannerConfig 90 90 getBannerConfig map = unMap map >>= makeBannerConfig
makeBannerConfig map = Just 91 91 makeBannerConfig map = Just
( BannerConfig 92 92 ( BannerConfig
{ queryURL = getString "query_url" map 93 93 { queryURL = getString "query_url" map
, originPosition = getPosition "position_origin" map 94 94 , originPosition = getPosition "position_origin" map
, defaultFontFace = fromJust $ getString "font_face" map 95 95 , defaultFontFace = fromMaybe "monospace" $ getString "font_face" map
, defaultFontSize = fromJust $ getString "font_size" map >>= toDouble 96 96 , defaultFontSize = fromMaybe 12.0 $ getString "font_size" map >>= toDouble
, defaultKeyColor = fromJust $ getColor "key_color" map 97 97 , defaultKeyColor = fromMaybe (Color 0 0 0 1) $ getColor "key_color" map
, defaultValueColor = fromJust $ getColor "val_color" map 98 98 , defaultValueColor = fromMaybe (Color 0 0 0 1) $ getColor "val_color" map
, defaultStrokeWidth = fromJust $ getString "stroke_width" map >>= toDouble 99 99 , defaultStrokeWidth = fromMaybe 0.25 $ getString "stroke_width" map >>= toDouble
--, defaultUseCommas = getString "use_commas" map >>= toBool 100 100 , defaultUseCommas = fromMaybe False $ getString "use_commas" map >>= toBool
, statConfigs = getStatConfigs map }) 101 101 , statConfigs = getStatConfigs map })
toDouble val = Just (read val :: Double) 102 102 toDouble val = Just (read val :: Double)
toBool val = Just (read val :: Bool) 103 103 toBool val = Just (read val :: Bool)
104 104
105 105
106 106
-- The following functions are all used to access fields of the 107 107 -- The following functions are all used to access fields of the
-- configuration file and convert them into their proper types. 108 108 -- configuration file and convert them into their proper types.
getStatConfigs :: Map YamlLight YamlLight -> [StatConfig] 109 109 getStatConfigs :: Map YamlLight YamlLight -> [StatConfig]
getStatConfigs map = case lookup of 110 110 getStatConfigs map = case lookup of
Nothing -> [] 111 111 Nothing -> []
Just val -> val 112 112 Just val -> val
where 113 113 where
lookup = M.lookup (YStr "stat_configs") map >>= unSeq 114 114 lookup = M.lookup (YStr "stat_configs") map >>= unSeq
>>= mapM (\cfgMap -> Just (getStatConfig (unMap cfgMap))) 115 115 >>= mapM (\cfgMap -> Just (getStatConfig (unMap cfgMap)))
getStatConfig map = StatConfig { 116 116 getStatConfig map = StatConfig {
key = fromJust $ getString "key" (fromJust map), 117 117 key = fromMaybe "null" $ getString "key" (fromJust map),
keyType = fromJust $ getString "type" (fromJust map), 118 118 keyType = fromMaybe "null" $ getString "type" (fromJust map),
name = fromJust $ getString "name" (fromJust map), 119 119 name = fromMaybe "null" $ getString "name" (fromJust map),
position = getPosition "position_offset" (fromJust map), 120 120 position = getPosition "position_offset" (fromJust map),
fontFace = getString "font_face" (fromJust map), 121 121 fontFace = getString "font_face" (fromJust map),
fontSize = getString "font_size" (fromJust map) >>= toDouble, 122 122 fontSize = getString "font_size" (fromJust map) >>= toDouble,
keyColor = getColor "key_color" (fromJust map), 123 123 keyColor = getColor "key_color" (fromJust map),
valueColor = getColor "val_color" (fromJust map), 124 124 valueColor = getColor "val_color" (fromJust map),
strokeWidth = getString "stroke_width" (fromJust map) >>= toDouble, 125 125 strokeWidth = getString "stroke_width" (fromJust map) >>= toDouble,
useCommas = getString "use_commas" (fromJust map) >>= toBool 126 126 useCommas = fromMaybe False $ getString "use_commas" (fromJust map) >>= toBool
} 127 127 }
toDouble val = Just (read val :: Double) 128 128 toDouble val = Just (read val :: Double)
toBool val = Just (read val :: Bool) 129 129 toBool val = Just (read val :: Bool)
130 130
getValue :: BS.ByteString -> Map YamlLight YamlLight -> Maybe YamlLight 131 131 getValue :: BS.ByteString -> Map YamlLight YamlLight -> Maybe YamlLight
getValue key map = M.lookup (YStr key) map 132 132 getValue key map = M.lookup (YStr key) map
133 133
getString :: BS.ByteString -> Map YamlLight YamlLight -> Maybe String 134 134 getString :: BS.ByteString -> Map YamlLight YamlLight -> Maybe String
getString key map = getValue key map >>= unStr >>= (\bs -> (Just (BS.unpack bs))) 135 135 getString key map = getValue key map >>= unStr >>= (\bs -> (Just (BS.unpack bs)))
136 136
getColor :: BS.ByteString -> Map YamlLight YamlLight -> Maybe Color 137 137 getColor :: BS.ByteString -> Map YamlLight YamlLight -> Maybe Color
getColor key map = getValue key map >>= unMap >>= makeColor 138 138 getColor key map = getValue key map >>= unMap >>= makeColor
where 139 139 where
makeColor map = Just (Color 140 140 makeColor map = Just (Color
(fromJust (getString "r" map >>= toDouble)) 141 141 (fromMaybe 0 (getString "r" map >>= toDouble))
(fromJust (getString "g" map >>= toDouble)) 142 142 (fromMaybe 0 (getString "g" map >>= toDouble))
(fromJust (getString "b" map >>= toDouble)) 143 143 (fromMaybe 0 (getString "b" map >>= toDouble))
(fromJust (getString "a" map >>= toDouble))) 144 144 (fromMaybe 1 (getString "a" map >>= toDouble)))
toDouble val = Just (read val :: Double) 145 145 toDouble val = Just (read val :: Double)
146 146
getPosition :: BS.ByteString -> Map YamlLight YamlLight -> Position 147 147 getPosition :: BS.ByteString -> Map YamlLight YamlLight -> Position
getPosition key map = case M.lookup (YStr key) map of 148 148 getPosition key map = case M.lookup (YStr key) map of
Nothing -> Position 0 0 149 149 Nothing -> Position 0 0
Just map -> makePosition $ fromJust $ unMap map 150 150 Just map -> makePosition $ fromJust $ unMap map
where 151 151 where
makePosition map = Position 152 152 makePosition map = Position
(fromJust (getString "x" map >>= toDouble)) 153 153 (fromJust (getString "x" map >>= toDouble))
(fromJust (getString "y" map >>= toDouble)) 154 154 (fromJust (getString "y" map >>= toDouble))
toDouble val = Just (read val :: Double) 155 155 toDouble val = Just (read val :: Double)
156 156
{-# LANGUAGE DeriveDataTypeable, RecordWildCards #-} 1 1 {-# LANGUAGE DeriveDataTypeable, RecordWildCards #-}
2 2
-- This program creates a statistics image banner for a user or team 3 3 -- This program creates a statistics image banner for a user or team
-- involved in Folding@home. I used the singleMode.hs example from 4 4 -- involved in Folding@home. I used the singleMode.hs example from
-- http://zuttobenkyou.wordpress.com/2011/04/19/haskell-using-cmdargs-single-and-multi-mode/ 5 5 -- http://zuttobenkyou.wordpress.com/2011/04/19/haskell-using-cmdargs-single-and-multi-mode/
-- to make the commandline options for this program look pretty. 6 6 -- to make the commandline options for this program look pretty.
7 7
import BannerConfig 8 8 import BannerConfig
import Control.Monad (when) 9 9 import Control.Monad (when)
import Data.Char 10 10 import Data.Char
import Data.List 11 11 import Data.List
import Data.List.Split 12 12 import Data.List.Split
import Data.Maybe 13 13 import Data.Maybe
import Graphics.Rendering.Cairo 14 14 import Graphics.Rendering.Cairo
import Network.Curl.Download 15 15 import Network.Curl.Download
import System.Console.CmdArgs 16 16 import System.Console.CmdArgs
import qualified System.Console.CmdArgs as CA 17 17 import qualified System.Console.CmdArgs as CA
import System.Directory 18 18 import System.Directory
import System.Environment (getArgs, withArgs) 19 19 import System.Environment (getArgs, withArgs)
import System.Exit 20 20 import System.Exit
import Text.XML.Light 21 21 import Text.XML.Light
import Text.XML.Light.Cursor 22 22 import Text.XML.Light.Cursor
import Text.XML.Light.Input 23 23 import Text.XML.Light.Input
24 import Codec.Picture.Bitmap
25 import Codec.Picture.Png
26 import Codec.Picture.Jpg
27 import Codec.Picture.Gif
28 import Codec.Picture.Saving
24 29
30
data MyOptions = MyOptions 25 31 data MyOptions = MyOptions
{ config :: FilePath 26 32 { config :: FilePath
, background :: FilePath 27 33 , background :: FilePath
, output :: FilePath 28 34 , output :: FilePath
, stats :: FilePath 29 35 , stats :: FilePath
, ident :: String 30 36 , ident :: String
} deriving (Data, Typeable, Show, Eq) 31 37 } deriving (Data, Typeable, Show, Eq)
32 38
-- Customize your options, including help messages, shortened names, etc. 33 39 -- Customize your options, including help messages, shortened names, etc.
myProgOpts :: MyOptions 34 40 myProgOpts :: MyOptions
myProgOpts = MyOptions 35 41 myProgOpts = MyOptions
{ config = def &= typFile &= help "the banner configuration file" 36 42 { config = def &= typFile &= help "the banner configuration file"
, background = def &= typFile &= help "the background image (must be png)" 37 43 , background = def &= typFile &= help "the background image (must be png)"
, output = def &= typFile &= help "the output file (must be png)" 38 44 , output = def &= typFile &= help "the output file (must be png)"
, stats = def &= typFile &= help "the xml statistics file (from extremeoverclocking.com)" 39 45 , stats = def &= typFile &= help "the xml statistics file (from extremeoverclocking.com)"
, ident = def &= typ "ID" &= help "the user or team id" } 40 46 , ident = def &= typ "ID" &= help "the user or team id" }
41 47
getOpts :: IO MyOptions 42 48 getOpts :: IO MyOptions
getOpts = cmdArgs $ myProgOpts 43 49 getOpts = cmdArgs $ myProgOpts
&= verbosityArgs [explicit, CA.name "Verbose", CA.name "V"] [] 44 50 &= verbosityArgs [explicit, CA.name "Verbose", CA.name "V"] []
&= versionArg [explicit, CA.name "version", CA.name "v", summary _PROGRAM_INFO] 45 51 &= versionArg [explicit, CA.name "version", CA.name "v", summary _PROGRAM_INFO]
&= summary (_PROGRAM_INFO ++ ", " ++ _COPYRIGHT) 46 52 &= summary (_PROGRAM_INFO ++ ", " ++ _COPYRIGHT)
&= help _PROGRAM_ABOUT 47 53 &= help _PROGRAM_ABOUT
&= helpArg [explicit, CA.name "help", CA.name "h"] 48 54 &= helpArg [explicit, CA.name "help", CA.name "h"]
&= program _PROGRAM_NAME 49 55 &= program _PROGRAM_NAME
50 56
_PROGRAM_NAME = "foldbanner" 51 57 _PROGRAM_NAME = "foldbanner"
_PROGRAM_VERSION = "1.0.4" 52 58 _PROGRAM_VERSION = "1.0.5"
_PROGRAM_INFO = _PROGRAM_NAME ++ " version " ++ _PROGRAM_VERSION 53 59 _PROGRAM_INFO = _PROGRAM_NAME ++ " version " ++ _PROGRAM_VERSION
_PROGRAM_ABOUT = "A configurable program for generating statistics banners for Folding@home" 54 60 _PROGRAM_ABOUT = "A configurable program for generating statistics banners for Folding@home"
_COPYRIGHT = "(c) 2012-2013 David Shere" 55 61 _COPYRIGHT = "(c) 2012-2013 David Shere"
56 62
main :: IO () 57 63 main :: IO ()
main = do 58 64 main = do
args <- getArgs 59 65 args <- getArgs
-- If the user did not specify any arguments, pretend as "--help" was given 60 66 -- If the user did not specify any arguments, pretend as "--help" was given
opts <- (if null args then withArgs ["--help"] else id) getOpts 61 67 opts <- (if null args then withArgs ["--help"] else id) getOpts
optionHandler opts 62 68 optionHandler opts
63 69
-- Before directly calling your main program, you should warn your user about incorrect arguments, if any. 64 70 -- Before directly calling your main program, you should warn your user about incorrect arguments, if any.
optionHandler :: MyOptions -> IO () 65 71 optionHandler :: MyOptions -> IO ()
optionHandler opts@MyOptions{..} = do 66 72 optionHandler opts@MyOptions{..} = do
-- Get some error values 67 73 -- Get some error values
bgExists <- doesFileExist background 68 74 bgExists <- doesFileExist background
cfgExists <- doesFileExist config 69 75 cfgExists <- doesFileExist config
-- Take the opportunity here to weed out ugly, malformed, or invalid arguments. 70 76 -- Take the opportunity here to weed out ugly, malformed, or invalid arguments.
when (null config) $ putStrLn "--config is blank!" >> exitWith (ExitFailure 1) 71 77 when (null config) $ putStrLn "--config is blank!" >> exitWith (ExitFailure 1)
when (null background) $ putStrLn "--background is blank!" >> exitWith (ExitFailure 1) 72 78 when (null background) $ putStrLn "--background is blank!" >> exitWith (ExitFailure 1)
when (not cfgExists) $ putStrLn ("config file \"" ++ config ++ "\" doesn't exist.") >> exitWith (ExitFailure 1) 73 79 when (not cfgExists) $ putStrLn ("config file \"" ++ config ++ "\" doesn't exist.") >> exitWith (ExitFailure 1)
when (not bgExists) $ putStrLn ("background file \"" ++ background ++ "\" doesn't exist") >> exitWith (ExitFailure 1) 74 80 when (not bgExists) $ putStrLn ("background file \"" ++ background ++ "\" doesn't exist") >> exitWith (ExitFailure 1)
when (not (isSuffixOf ".png" (map toLower background))) $ putStrLn "background file isn't a PNG image" >> exitWith (ExitFailure 1) 75 81 when (not (isSuffixOf ".png" (map toLower background))) $ putStrLn "background file isn't a PNG image" >> exitWith (ExitFailure 1)
when (null output) $ putStrLn "--output is blank!" >> exitWith (ExitFailure 1) 76 82 when (null output) $ putStrLn "--output is blank!" >> exitWith (ExitFailure 1)
when (not (null ident) && not (null stats)) $ putStrLn "--ident and --stats are both defined! Use one!" >> exitWith (ExitFailure 1) 77 83 when (not (null ident) && not (null stats)) $ putStrLn "--ident and --stats are both defined! Use one!" >> exitWith (ExitFailure 1)
when (null ident && null stats) $ putStrLn "--ident and --stats are blank! Use one!" >> exitWith (ExitFailure 1) 78 84 when (null ident && null stats) $ putStrLn "--ident and --stats are blank! Use one!" >> exitWith (ExitFailure 1)
-- When you're done, pass the (corrected, or not) options to your actual program. 79 85 -- When you're done, pass the (corrected, or not) options to your actual program.
exec opts 80 86 exec opts
81 87
exec :: MyOptions -> IO () 82 88 exec :: MyOptions -> IO ()
exec opts@MyOptions{..} = do 83 89 exec opts@MyOptions{..} = do
cfg <- readBannerConfig config 84 90 cfg <- readBannerConfig config
case cfg of 85 91 case cfg of
Nothing -> putStrLn "Error loading config file." >> exitWith (ExitFailure 1) 86 92 Nothing -> putStrLn "Error loading config file." >> exitWith (ExitFailure 1)
Just cfg -> do 87 93 Just cfg -> do
case (null stats) of 88 94 case (null stats) of
True -> do 89 95 True -> do
stats <- getStats cfg (StatID ident) 90 96 stats <- getStats cfg (StatID ident)
case stats of 91 97 case stats of
Nothing -> putStrLn "Error retrieving stats." 92 98 Nothing -> putStrLn "Error retrieving stats."
Just s -> createBanner cfg s output background 93 99 Just s -> createBanner cfg s output background
False -> do 94 100 False -> do
stats <- getStats cfg (StatFile stats) 95 101 stats <- getStats cfg (StatFile stats)
case stats of 96 102 case stats of
Nothing -> putStrLn "Error reading stats." 97 103 Nothing -> putStrLn "Error reading stats."
Just s -> createBanner cfg s output background 98 104 Just s -> createBanner cfg s output background
99 105
------------------------------------------------------------------------------- 100 106 -------------------------------------------------------------------------------
101 107
data StatSource = StatFile FilePath | StatID String 102 108 data StatSource = StatFile FilePath | StatID String
103 109
-- Retrieve the statistics from the statistics server or local file 104 110 -- Retrieve the statistics from the statistics server or local file
getStats :: BannerConfig -> StatSource -> IO (Maybe Element) 105 111 getStats :: BannerConfig -> StatSource -> IO (Maybe Element)
getStats cfg (StatID id) = do 106 112 getStats cfg (StatID id) = do
case queryURL cfg of 107 113 case queryURL cfg of
Nothing -> putStrLn "No query_url in config file." >> exitWith (ExitFailure 1); 108 114 Nothing -> putStrLn "No query_url in config file." >> exitWith (ExitFailure 1);
Just val -> do 109 115 Just val -> do
xml <- openAsXML $ val ++ id 110 116 xml <- openAsXML $ val ++ id
case xml of 111 117 case xml of
Left e -> do 112 118 Left e -> do
putStrLn e 113 119 putStrLn e
return Nothing 114 120 return Nothing
Right stats -> return $ Just ((onlyElems stats) !! 1) 115 121 Right stats -> return $ Just ((onlyElems stats) !! 1)
116 122
getStats cfg (StatFile file) = do 117 123 getStats cfg (StatFile file) = do
xmlFile <- readFile file 118 124 xmlFile <- readFile file
return $ parseXMLDoc xmlFile 119 125 return $ parseXMLDoc xmlFile
120 126
-- Create the statistics banner and save it to the output file 121 127 -- Create the statistics banner and save it to the output file
createBanner :: BannerConfig -> Element -> FilePath -> FilePath -> IO () 122 128 createBanner :: BannerConfig -> Element -> FilePath -> FilePath -> IO ()
createBanner mainConfig stats out bg = withImageSurfaceFromPNG bg $ \surface -> do 123 129 createBanner mainConfig stats out bg = withImageSurfaceFromPNG bg $ \surface -> do
renderWith surface $ do mapM_ writeStat $ statConfigs mainConfig 124 130 renderWith surface $ do mapM_ writeStat $ statConfigs mainConfig
131 --image <- imageSurfaceGetData surface
132 --case decodeBitmap image of
133 -- Left err -> putStrLn err >> exitWith (ExitFailure 1)
134 -- Right img -> writeDynamicPng out img
surfaceWriteToPNG surface out 125 135 surfaceWriteToPNG surface out
return () 126 136 return ()
where 127 137 where
getData keyType key = case findElementByName keyType key stats of 128 138 getData keyType key = case findElementByName keyType key stats of
Nothing -> "Error" 129 139 Nothing -> "Error"
Just e -> strContent e 130 140 Just e -> strContent e
findElementByName keyType key stats = findElement (unqual key) $ 131 141 findElementByName keyType key stats = findElement (unqual key) $
fromJust $ findElement (unqual keyType) stats 132 142 fromJust $ findElement (unqual keyType) stats
addCommas x = h++t -- addCommas function based on code from here: 133 143 addCommas x = h++t -- addCommas function based on code from here:
where -- http://stackoverflow.com/a/3753207/816685 134 144 where -- http://stackoverflow.com/a/3753207/816685
sp = break (== '.') x 135 145 sp = break (== '.') x
h = reverse (intercalate "," $ chunksOf 3 $ reverse $ fst sp) 136 146 h = reverse (intercalate "," $ chunksOf 3 $ reverse $ fst sp)
t = snd sp 137 147 t = snd sp
writeStat cfg = do 138 148 writeStat cfg = do
selectFontFace statFontFace FontSlantNormal FontWeightNormal 139 149 selectFontFace statFontFace FontSlantNormal FontWeightNormal
setFontSize statFontSize 140 150 setFontSize statFontSize
let value = if fromJust $ useCommas cfg 141 151 let value = if useCommas cfg
then do addCommas $ getData (keyType cfg) (key cfg) 142 152 then do addCommas $ getData (keyType cfg) (key cfg)
else getData (keyType cfg) (key cfg) 143 153 else getData (keyType cfg) (key cfg)
writeText (BannerConfig.name cfg) value statKeyColor statValueColor statStrokeWidth (originPosition mainConfig) (position cfg) 144 154 writeText (BannerConfig.name cfg) value statKeyColor statValueColor statStrokeWidth (originPosition mainConfig) (position cfg)
where 145 155 where
statFontFace = case fontFace cfg of 146 156 statFontFace = case fontFace cfg of
Nothing -> defaultFontFace mainConfig 147 157 Nothing -> defaultFontFace mainConfig
Just val -> val 148 158 Just val -> val
statFontSize = case fontSize cfg of 149 159 statFontSize = case fontSize cfg of
Nothing -> defaultFontSize mainConfig 150 160 Nothing -> defaultFontSize mainConfig
Just val -> val 151 161 Just val -> val
statKeyColor = case keyColor cfg of 152 162 statKeyColor = case keyColor cfg of
Nothing -> defaultKeyColor mainConfig 153 163 Nothing -> defaultKeyColor mainConfig
Just val -> val 154 164 Just val -> val
statValueColor = case valueColor cfg of 155 165 statValueColor = case valueColor cfg of
Nothing -> defaultValueColor mainConfig 156 166 Nothing -> defaultValueColor mainConfig
Just val -> val 157 167 Just val -> val
statStrokeWidth = case strokeWidth cfg of 158 168 statStrokeWidth = case strokeWidth cfg of
Nothing -> defaultStrokeWidth mainConfig 159 169 Nothing -> defaultStrokeWidth mainConfig
Just val -> val 160 170 Just val -> val
161 171
-- Write a string with a color and position to the surface. 162 172 -- Write a string with a color and position to the surface.
writeText :: String -> String -> Color -> Color -> Double -> Position -> Position -> Render () 163 173 writeText :: String -> String -> Color -> Color -> Double -> Position -> Position -> Render ()
writeText keyString valString keyColor valColor strokeWidth (Position orgX orgY) (Position x y) = do 164 174 writeText keyString valString keyColor valColor strokeWidth (Position orgX orgY) (Position x y) = do
165 175
s <- status 166 176 s <- status
case s of 167 177 case s of
StatusSuccess -> render 168 178 StatusSuccess -> render
StatusNoMemory -> liftIO $ putStrLn "Error rendering image. Was the background image a valid PNG image?" >> exitWith (ExitFailure 1) 169 179 StatusNoMemory -> liftIO $ putStrLn "Error rendering image. Was the background image a valid PNG image?" >> exitWith (ExitFailure 1)
170 180
where 171 181 where
render = do 172 182 render = do