Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

# -*- coding: UTF-8 -*- 

# Copyright 2009-2015 Luc Saffre 

# License: BSD (see file COPYING for details) 

 

"""This defines the :class:`ConfigDirCache` which Lino instantiates 

and installs as :attr:`SITE.confdirs 

<lino.core.site.Site.confdirs>`. 

 

It creates a list `config_dirs` of all configuration directories by 

looping through :attr:`lino.core.site.Site.installed_plugins` and taking those 

whose source directory has a :xfile:`config` subdir. 

 

The mechanism in this module emulates the behaviour of Django's and 

Jinja's template loaders. 

 

We cannot use the Jinja loader because Jinja's `get_template` method 

returns a `Template`, and Jinja templates don't know their filename. 

One possibility might be to write a special Jinja Template class... 

 

""" 

 

from __future__ import unicode_literals 

from past.builtins import cmp 

from builtins import object 

 

import logging 

logger = logging.getLogger(__name__) 

 

import os 

from os.path import join, abspath, dirname, isdir 

import sys 

import codecs 

from fnmatch import fnmatch 

 

fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() 

 

from lino.utils import iif 

 

SUBDIR_NAME = 'config'  # we might change this to "templates" 

 

 

class ConfigDir(object): 

 

    """A directory that may contain configuration files. 

 

    """ 

 

    def __init__(self, name, writeable): 

        self.name = abspath(name) 

        self.writeable = writeable 

 

    def __repr__(self): 

        return "ConfigDir %s" % self.name + iif( 

            self.writeable, " (writeable)", "") 

 

 

class ConfigDirCache(object): 

    _init = False 

 

    def __init__(self, site): 

        if self._init: 

            raise Exception("Oops, ConfigDirCache was already instantiated!") 

        self._init = True 

        self.site = site 

        config_dirs = [] 

 

        for pth in site.get_settings_subdirs(SUBDIR_NAME): 

            config_dirs.append(ConfigDir(pth.decode(fs_encoding), False)) 

 

        def add_config_dir(name, mod): 

            pth = join(dirname(mod.__file__), SUBDIR_NAME) 

            if isdir(pth): 

                # logger.info("add_config_dir %s %s", name, pth) 

                # config_dirs.append(ConfigDir(pth.decode(fs_encoding), False)) 

                config_dirs.append(ConfigDir(pth, False)) 

 

        site.for_each_app(add_config_dir) 

 

        self.LOCAL_CONFIG_DIR = None 

 

        if site.is_local_project_dir: 

            p = join(site.project_dir, SUBDIR_NAME) 

            if isdir(p): 

                self.LOCAL_CONFIG_DIR = ConfigDir(p, True) 

                config_dirs.append(self.LOCAL_CONFIG_DIR) 

        #         print "20140625 Local config directory %s." % p 

        #     else: 

        #         print "20140625 No local config directory.", p 

        # else: 

        #     print "20140625 Not a local project directory." 

 

        config_dirs.reverse() 

        self.config_dirs = tuple(config_dirs) 

 

        # logger.info('config_dirs:\n%s', '\n'.join([ 

        #     repr(cd) for cd in config_dirs])) 

 

    def find_config_file(self, fn, *groups): 

        """ 

        Return the full path of the first occurence within the 

        :class:`lino.utils.config.ConfigDirCache` of a file named 

        `filename` 

 

        """ 

        if os.path.isabs(fn): 

            return fn 

        if len(groups) == 0: 

            groups = [''] 

        for group in groups: 

            if group: 

                prefix = join(*(group.split('/'))) 

            else: 

                prefix = '' 

            for cd in self.config_dirs: 

                ffn = join(cd.name, prefix, fn) 

                if os.path.exists(ffn): 

                    return ffn 

 

    def find_config_files(self, pattern, *groups): 

        """ 

        Returns a dict of `filename` -> `config_dir` entries for each config 

        file on this site that matches the pattern.  Loops through 

        `config_dirs` and collects matching files.  When a filename is 

        provided by more than one app, then the latest app gets it. 

 

        `groups` is a tuple of strings, e.g. '', 'foo', 'foo/bar', ... 

 

        """ 

        files = {} 

        for group in groups: 

            if group: 

                prefix = os.path.sep + join(*(group.split('/'))) 

            else: 

                prefix = '' 

            for cd in self.config_dirs: 

                pth = cd.name + prefix 

                if isdir(pth): 

                    for fn in os.listdir(pth): 

                        if fnmatch(fn, pattern): 

                            files.setdefault(fn, cd) 

        return files 

 

    def find_template_config_files(self, template_ext, *groups): 

        """ 

        Like :func:`find_config_files`, but ignore babel variants: 

        e.g. ignore "foo_fr.html" if "foo.html" exists. 

        Note: but don't ignore "my_template.html" 

        """ 

        files = self.find_config_files('*' + template_ext, *groups) 

        l = [] 

        template_ext 

        for name in list(files.keys()): 

            basename = name[:-len(template_ext)] 

            chunks = basename.split('_') 

            if len(chunks) > 1: 

                basename = '_'.join(chunks[:-1]) 

                if basename + template_ext in files: 

                    continue 

            l.append(name) 

        l.sort() 

        if not l: 

            logger.warning( 

                "find_template_config_files() : no matches for (%r, %r)", 

                '*' + template_ext, groups) 

        return l 

 

    def load_config_files(self, loader, pattern, *groups): 

        """ 

        Currently not used. 

        Naming conventions for :xfile:`*.dtl` files are: 

 

        - the first detail is called appname.Model.dtl 

        - If there are more Details, then they are called 

          appname.Model.2.dtl, appname.Model.3.dtl etc. 

 

        The `sort()` below must remove the filename extension (".dtl") 

        because otherwise the frist Detail would come last. 

        """ 

        files = list(self.find_config_files(pattern, *groups).items()) 

 

        def fcmp(a, b): 

            return cmp(a[0][:-4], b[0][:-4]) 

        files.sort(fcmp) 

        for group in groups: 

            prefix = group.replace("/", os.sep) 

            for filename, cd in files: 

                filename = join(prefix, filename) 

                ffn = join(cd.name, filename) 

                logger.debug("Loading %s...", ffn) 

                s = codecs.open(ffn, encoding='utf-8').read() 

                loader(s, cd, filename) 

 

 

IGNORE_TIMES = False 

MODIFY_WINDOW = 2 

 

 

def must_make(src, target): 

    "returns True if src is newer than target" 

    try: 

        src_st = os.stat(src) 

        src_mt = src_st.st_mtime 

    except OSError: 

        # self.error("os.stat() failed: ",e) 

        return False 

 

    try: 

        target_st = os.stat(target) 

        target_mt = target_st.st_mtime 

    except OSError: 

        # self.error("os.stat() failed: %s", e) 

        return True 

 

    if src_mt - target_mt > MODIFY_WINDOW: 

        return True 

    return False 

 

 

def make_dummy_messages_file(src_fn, messages): 

    """ 

    Write a dummy `.py` source file containing 

    translatable messages that getmessages will find. 

    """ 

    raise Exception("Never used") 

    target_fn = src_fn + '.py' 

    if not must_make(src_fn, target_fn): 

        logger.debug("%s is up-to-date.", target_fn) 

        return 

    try: 

        f = file(target_fn, 'w') 

    except IOError as e: 

        logger.warning("Could not write file %s : %s", target_fn, e) 

        return 

    f.write("# this file is generated by Lino\n") 

    f.write("from django.utils.translation import ugettext\n") 

    for m in messages: 

        f.write("ugettext(%r)\n" % m) 

    f.close() 

    logger.info("Wrote %d dummy messages to %s.", len(messages), target_fn)