Revision f952ed9f

b/Makefile.am
763 763
	src/Ganeti/Storage/Lvm/Types.hs \
764 764
	src/Ganeti/Storage/Utils.hs \
765 765
	src/Ganeti/THH.hs \
766
	src/Ganeti/THH/PyRPC.hs \
766 767
	src/Ganeti/THH/PyType.hs \
767 768
	src/Ganeti/THH/Types.hs \
768 769
	src/Ganeti/THH/RPC.hs \
b/src/Ganeti/THH/PyRPC.hs
1
{-# LANGUAGE TemplateHaskell #-}
2

  
3
{-| Combines the construction of RPC server components and their Python stubs.
4

  
5
-}
6

  
7
{-
8

  
9
Copyright (C) 2013 Google Inc.
10

  
11
This program is free software; you can redistribute it and/or modify
12
it under the terms of the GNU General Public License as published by
13
the Free Software Foundation; either version 2 of the License, or
14
(at your option) any later version.
15

  
16
This program is distributed in the hope that it will be useful, but
17
WITHOUT ANY WARRANTY; without even the implied warranty of
18
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19
General Public License for more details.
20

  
21
You should have received a copy of the GNU General Public License
22
along with this program; if not, write to the Free Software
23
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24
02110-1301, USA.
25

  
26
-}
27

  
28
module Ganeti.THH.PyRPC
29
  ( genPyUDSRpcStub
30
  , genPyUDSRpcStubStr
31
  ) where
32

  
33
import Control.Monad
34
import Data.Char (toLower, toUpper)
35
import Data.Functor
36
import Data.Maybe (fromMaybe)
37
import Language.Haskell.TH
38
import Language.Haskell.TH.Syntax (liftString)
39
import Text.PrettyPrint
40

  
41
import Ganeti.THH.Types
42

  
43
-- | The indentation step in generated Python files.
44
pythonIndentStep :: Int
45
pythonIndentStep = 2
46

  
47
-- | A helper function that nests a block of generated output by the default
48
-- step (see 'pythonIndentStep').
49
nest' :: Doc -> Doc
50
nest' = nest pythonIndentStep
51

  
52
-- | The name of an abstract function to which all method in a Python stub
53
-- are forwarded to.
54
genericInvokeName :: String
55
genericInvokeName = "_GenericInvoke"
56

  
57
-- | The name of a function that returns the socket path for reaching the
58
-- appropriate RPC client.
59
socketPathName :: String
60
socketPathName = "_GetSocketPath"
61

  
62
-- | Create a Python expression that applies a given function to a list of
63
-- given expressions
64
apply :: String -> [Doc] -> Doc
65
apply name as = text name <> parens (hcat $ punctuate (text ", ") as)
66

  
67
-- | An empty line block.
68
emptyLine :: Doc
69
emptyLine = text "" -- apparently using 'empty' doesn't work
70

  
71
lowerFirst :: String -> String
72
lowerFirst (x:xs) = toLower x : xs
73
lowerFirst []     = []
74

  
75
upperFirst :: String -> String
76
upperFirst (x:xs) = toUpper x : xs
77
upperFirst []     = []
78

  
79
-- | Creates a method declaration given a function name and a list of
80
-- Haskell types corresponding to its arguments.
81
toFunc :: String -> [Type] -> Q Doc
82
toFunc fname as = do
83
    args <- zipWithM varName [1..] as
84
    let args' = text "self" : args
85
        callName = lowerFirst fname
86
    return $ (text "def" <+> apply fname args') <> colon $+$
87
             nest' (text "return" <+>
88
                    text "self." <>
89
                    apply genericInvokeName (text (show callName) : args)
90
             )
91
  where
92
    -- | Create a name for a method argument, given its index position
93
    -- and Haskell type.
94
    varName :: Int -> Type -> Q Doc
95
    varName _   (VarT n)              = lowerFirstNameQ n
96
    varName _   (ConT n)              = lowerFirstNameQ n
97
    varName idx (AppT ListT t)        = listOf idx t
98
    varName idx (AppT (ConT n) t)
99
      | n == ''[]                     = listOf idx t
100
    varName idx t                     = do
101
      report False $ "Don't know how to make a Python variable name from "
102
                     ++ show t ++ "; using a numbered one."
103
      return $ text ('_' : show idx)
104

  
105
    -- | Create a name for a method argument, knowing that its a list of
106
    -- a given type.
107
    listOf :: Int -> Type -> Q Doc
108
    listOf idx t = (<> text "List") <$> varName idx t
109

  
110
    lowerFirstNameQ :: Name -> Q Doc
111
    lowerFirstNameQ = return . text . lowerFirst . nameBase
112

  
113
-- | Creates a method declaration by inspecting (reifying) Haskell's function
114
-- name.
115
nameToFunc :: Name -> Q Doc
116
nameToFunc name = do
117
    (as, _) <- funArgs `liftM` typeOfFun name
118
    -- If the function has just one argument, try if it isn't a tuple;
119
    -- if not, use the arguments as they are.
120
    let as' = fromMaybe as $ case as of
121
                                [t] -> tupleArgs t -- TODO CHECK!
122
                                _   -> Nothing
123
    toFunc (upperFirst $ nameBase name) as'
124

  
125
-- | Generates a Python class stub, given a class name, the list of Haskell
126
-- functions to expose as methods, and a optionally a piece of code to
127
-- include.
128
namesToClass
129
  :: String       -- ^ the class name
130
  -> Doc          -- ^ Python code to include in the class
131
  -> [Name]       -- ^ the list of functions to include
132
  -> Q Doc
133
namesToClass cname pycode fns = do
134
  fnsCode <- mapM (liftM ($+$ emptyLine) . nameToFunc) fns
135
  return $ vcat [ text "class" <+> apply cname [text "object"] <> colon
136
                , nest' (
137
                    pycode $+$ vcat fnsCode
138
                  )
139
                ]
140

  
141
-- | Takes a list of function names and creates a RPC handler that delegates
142
-- calls to them, as well as writes out the corresponding Python stub.
143
--
144
-- See 'mkRpcM' for the requirements on the passed functions and the returned
145
-- expression.
146
genPyUDSRpcStub
147
  :: String     -- ^ the name of the class to be generated
148
  -> String     -- ^ the name of the constant from @constants.py@ holding
149
                --   the path to a UDS socket
150
  -> [Name]     -- ^ names of functions to include
151
  -> Q Doc
152
genPyUDSRpcStub className constName = liftM (header $+$) .
153
                                      namesToClass className stubCode
154
  where
155
    header = text "# This file is automatically generated, do not edit!" $+$
156
             text "# pylint: disable-all"
157
    stubCode =
158
      abstrMethod genericInvokeName [ text "method", text "*args"] $+$
159
      method socketPathName [] (
160
         text "from ganeti import pathutils" $+$
161
         text "return" <+> text "pathutils." <> text constName)
162
    method name args body =
163
      text "def" <+> apply name (text "self" : args) <> colon $+$
164
      nest' body $+$
165
      emptyLine
166
    abstrMethod name args = method name args $
167
      text "raise" <+> apply "NotImplementedError" []
168

  
169
-- The same as 'genPyUDSRpcStub', but returns the result as a @String@
170
-- expression.
171
genPyUDSRpcStubStr
172
  :: String   -- ^ the name of the class to be generated
173
  -> String   -- ^ the constant in @pathutils.py@ holding the socket path
174
  -> [Name]   -- ^ functions to include
175
  -> Q Exp
176
genPyUDSRpcStubStr className constName names =
177
    liftString . render =<< genPyUDSRpcStub className constName names

Also available in: Unified diff