packages feed

xcffib-1.13.0: generator/xcffibgen.hs

{-
 - Copyright 2014 Tycho Andersen
 -
 - Licensed under the Apache License, Version 2.0 (the "License");
 - you may not use this file except in compliance with the License.
 - You may obtain a copy of the License at
 -
 -   http://www.apache.org/licenses/LICENSE-2.0
 -
 - Unless required by applicable law or agreed to in writing, software
 - distributed under the License is distributed on an "AS IS" BASIS,
 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 - See the License for the specific language governing permissions and
 - limitations under the License.
 -}
module Main where

import Data.XCB.Python.Parse

import Options.Applicative

import System.Directory
import System.FilePath

import Control.Monad (mfilter)
import Data.Char (isAlphaNum, isHexDigit)
import Data.List (isPrefixOf, sort, stripPrefix)
import Data.Maybe (fromMaybe, mapMaybe)
import System.Environment (lookupEnv)
import System.Process (readProcess)

keysymSections :: [String]
keysymSections =
  [ "MISCELLANY", "XKB_KEYS", "3270", "LATIN1", "LATIN2", "LATIN3"
  , "LATIN4", "LATIN8", "LATIN9", "KATAKANA", "ARABIC", "CYRILLIC"
  , "GREEK", "TECHNICAL", "SPECIAL", "PUBLISHING", "APL", "HEBREW"
  , "THAI", "KOREAN", "ARMENIAN", "GEORGIAN", "CAUCASUS", "VIETNAMESE"
  , "CURRENCY", "MATHEMATICAL", "BRAILLE", "SINHALA"
  ]

generateKeysyms :: FilePath -> IO ()
generateKeysyms out = do
  cpp <- fromMaybe "cpp" <$> lookupEnv "CPP"
  let generate outputName headerFile extraArgs shouldKeep = do
        defines <- lines <$> readProcess cpp
          (["-dM"] ++ extraArgs ++ ["-include", headerFile, "-"]) ""
        writeFile (out </> outputName) . unlines . sort $
          mapMaybe (fmap formatDefine . mfilter (shouldKeep . fst) . parseDefine) defines
  generate "keysymdef.py" "X11/keysymdef.h" (map ("-DXK_" ++) keysymSections) $
    maybe False (`notElem` keysymSections) . stripPrefix "XK_"
  generate "xf86keysym.py" "X11/XF86keysym.h" [] $ isPrefixOf "XF86XK_"
  where
    parseDefine line = case words line of
      ("#define" : name : vals)
        | validName name -> Just (name, unwords vals)
      _ -> Nothing

    validName name = not (null name) && all (\c -> isAlphaNum c || c == '_') name

    formatDefine (name, val) = name ++ " = " ++ renderValue val

    renderValue val = case val of
      '0' : 'x' : digits
        | not (null digits) && all isHexDigit digits -> val
      _ -> case stripPrefix "_EVDEVK(0x" val >>= (fmap reverse . stripPrefix ")" . reverse) of
        Just digits
          | not (null digits) && all isHexDigit digits ->
              "0x10081000 + 0x" ++ digits
        _ -> error $ "invalid keysym value: " ++ val

data Xcffibgen = Xcffibgen { input :: String
                           , output :: String
                           }

options :: Parser Xcffibgen
options = Xcffibgen
    <$> strOption
        ( long "input"
       <> metavar "DIR"
       <> help "Input directory containing xcb xml files.")
    <*> strOption
        ( long "output"
       <> metavar "DIR"
       <> help "Output directory for generated python.")

run :: Xcffibgen -> IO ()
run (Xcffibgen inp out) = do
  headers <- parseXHeaders inp
  createDirectoryIfMissing True out
  sequence_ $ map processFile $ xform headers
  generateKeysyms out
  where
    processFile (fname, suite) = do
      putStrLn fname
      let fname' = out </> fname ++ ".py"
          contents = renderPy suite
      writeFile fname' contents

main :: IO ()
main = execParser opts >>= run
  where
    opts = info (helper <*> options)
      ( fullDesc
     <> progDesc "Generate XCB bindings for python."
     <> header "xcffib - the cffi-based XCB generator")