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

""" Handles all write accesses to the Ontologies and kif files. The 

SyntaxController's main purpose is to act as an intermediary between the user 

and the Ontology. 

 

This module contains: 

 

- SyntaxController: The interface to the parser/serializer. 

 

""" 

 

import logging 

 

from io import StringIO, BytesIO 

from os import listdir, fdopen, remove 

from os.path import basename, isdir, join 

from pkg_resources import resource_filename 

from tempfile import mkstemp 

from subprocess import Popen, PIPE, DEVNULL 

 

import pysumo 

from .logger import actionlog 

from . import parser 

 

def get_ontologies(lpath=None): 

    """ Returns a set of all ontologies provided by pysumo as well as local ontologies. """ 

    ret = set() 

    pdata = resource_filename('pysumo', 'data') 

    for f in listdir(resource_filename('pysumo', 'data')): 

        if f.endswith(".kif"): 

            ret.add(Ontology(join(pdata, f), lpath=lpath)) 

    if isdir(pysumo.CONFIG_PATH): 

        for f in listdir(pysumo.CONFIG_PATH): 

            if f.endswith(".kif"): 

                ret.add(Ontology(join(pysumo.CONFIG_PATH, f), lpath=lpath)) 

    return ret 

 

class SyntaxController: 

    """ The high-level class containing the interface to all parsing/serialization operations. 

    All operations that can modify the Ontology or kif-file are passed through the SyntaxController. 

    The SyntaxController acts as a moderator between user-side widgets and the low-level API 

    ensuring that all requests are correct, that higher objects never gain direct access to 

    internal objects and that all changes to the Ontology are atomic. 

 

    Methods: 

 

    - parse_partial: Checks a code block for syntax errors. 

    - parse_patch: Checks a code for correctness and adds it to the Ontology. 

    - add_ontology: Adds an Ontology to the in-memory Ontology. 

    - remove_ontology: Removes an Ontology from the in-memory Ontology. 

    - undo: Undoes the most recent change to the ontology. 

    - redo: Redoes the most recent change to the ontology. 

 

    """ 

 

    def __init__(self, index): 

        """ Initializes the SyntaxController object. """ 

        self.index = index 

        self.log = logging.getLogger('.' + __name__) 

 

    def parse_partial(self, code_block, ontology=None): 

        """ Tells self.parser to check code_block for syntactical correctness. 

 

        Arguments: 

 

        - code_block: the partial code block that will be checked 

 

        Raises: 

 

        - ParseError 

 

        """ 

        f = StringIO(code_block) 

        ast = parser.kifparse(f, ontology) 

        f.close() 

        return ast 

 

    def parse_patch(self, ontology, patch): 

        """ Apply a patch to the last version of the ontology and parse this new version 

 

        Arguments: 

 

        - ontology: the ontlogy which is patched 

        - patch: the patch to add to the ontology 

 

        Raises: 

 

        - ParseError 

 

        """ 

        (tempfile, tempfilepath) = mkstemp(text=True) 

        o = self.index.get_ontology_file(ontology) 

        tempfile = fdopen(tempfile, 'wt') 

        for l in o: 

            print(l, end='', file=tempfile) 

        tempfile.close() 

        p = Popen(["patch", "-u", tempfilepath], stdin=PIPE, stdout=DEVNULL) 

        p.communicate(patch.encode()) 

        with open(tempfilepath) as f: 

            pos = f.tell() 

            num = ontology.action_log.queue_log(BytesIO(f.read().encode())) 

            f.seek(pos) 

            newast = parser.kifparse(f, ontology, ast=self.index.root) 

        try: 

            self.remove_ontology(ontology) 

            newast = parser.astmerge((self.index.root, newast)) 

        except AttributeError: 

            pass 

        newast.ontology = None 

        self.index.update_index(newast) 

        self.index.ontologies.add(ontology) 

        ontology.action_log.ok_log_item(num) 

        remove(tempfilepath) 

 

    def add_ontology(self, ontology, newversion=None): 

        """ Adds ontology to the current in-memory Ontology. 

 

        Arguments: 

 

        - ontology: the ontology that will be added 

        - newversion: a string witch represent the new verison of the ontology 

 

        Raises: 

 

        - ParseError 

 

        """ 

 

        if newversion == None: 

            with open(ontology.path, errors='replace') as f: 

                pos = f.tell() 

                num = ontology.action_log.queue_log(BytesIO(f.read().encode())) 

                f.seek(pos) 

                newast = parser.kifparse(f, ontology, ast=self.index.root) 

        else: 

            num = ontology.action_log.queue_log(BytesIO(newversion.encode())) 

            f = StringIO(newversion) 

            newast = parser.kifparse(StringIO(newversion), ontology, ast=self.index.root) 

        try: 

            self.remove_ontology(ontology) 

            newast = parser.astmerge((self.index.root, newast)) 

        except AttributeError: 

            pass 

        newast.ontology = None 

        self.index.update_index(newast) 

        self.index.ontologies.add(ontology) 

        ontology.action_log.ok_log_item(num) 

 

    def remove_ontology(self, ontology): 

        """ Removes ontology from the current in-memory Ontology. 

 

        Arguments: 

 

        - ontology: the ontology that will be removed 

 

        Raises: 

 

        - NoSuchOntologyError 

 

        """ 

        offset = 0 

        for n, c in enumerate(list(self.index.root.children)): 

            if c.ontology == ontology: 

                self.index.root.children.pop(n - offset) 

                offset = offset + 1 

        self.index.update_index(self.index.root) 

        self.index.ontologies.discard(ontology) 

 

    def undo(self, ontology): 

        """ Undoes the last action in ontology """ 

        self.log.info('Undoing change to %s' % str(ontology)) 

        kif = ontology.action_log.undo().getvalue().decode('utf8') 

        self._update_asts(ontology, kif) 

 

    def redo(self, ontology): 

        """ Redoes the last action in ontology """ 

        self.log.info('Redoing change to %s' % str(ontology)) 

        kif = ontology.action_log.redo().getvalue().decode('utf8') 

        self._update_asts(ontology, kif) 

 

    def _update_asts(self, ontology, kif): 

        ast = self.parse_partial(kif, ontology) 

        self.remove_ontology(ontology) 

        newast = parser.astmerge((self.index.root, ast)) 

        self.index.update_index(newast) 

 

class Ontology: 

    """ Contains basic information about a KIF file.  This class is used to 

    maintain separation between different Ontology-files so the user can choose 

    which are active and where each Ontology should be saved. 

 

    Variables: 

 

    - name: The name of the Ontology. 

    - path: The location of the Ontology in the filesystem. 

    - url: The URL from which the Ontology can be updated. 

    - active: Whether or not the Ontology is currently loaded. 

 

    """ 

 

    def __init__(self, path, name=None, url=None, lpath=None): 

        """ Initializes an Ontology and instantiates variables. """ 

        if name is None: 

            self.name = basename(path) 

        else: 

            self.name = name 

        self.action_log = actionlog.ActionLog(self.name, lpath) 

        with open(path, 'r+b') as f: 

            self.action_log.current = BytesIO(f.read()) 

        self.path = path 

        self.url = url 

        self.active = False 

 

    def save(self): 

        """ Saves all pending changes in self to self.path. """ 

        with open(self.path, 'w+b') as f: 

            f.write(self.action_log.current.getbuffer()) 

 

    def __eq__(self, other): 

        return self.name == other.name 

 

    def __ne__(self, other): 

        return not self.__eq__(other) 

 

    def __lt__(self, other): 

        return self.name < other.name 

 

    def __hash__(self): 

        return hash(self.name) 

 

    def __repr__(self): 

        return self.name