GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 4c2037...997ab3 )
by dup
01:19
created

FeedCache.read_cache_feed()   A

Complexity

Conditions 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
dl 0
loc 10
rs 9.4285
c 1
b 0
f 0
1
#!/usr/bin/env python
2
# -*- coding: utf8 -*-
3
#
4
#  versions.py : checks release and versions of programs through RSS
5
#                or Atom feeds and tells you
6
#
7
#  (C) Copyright 2016 Olivier Delhomme
8
#  e-mail : [email protected]
9
#
10
#  This program is free software; you can redistribute it and/or modify
11
#  it under the terms of the GNU General Public License as published by
12
#  the Free Software Foundation; either version 2, or (at your option)
13
#  any later version.
14
#
15
#  This program is distributed in the hope that it will be useful,
16
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
#  GNU General Public License for more details.
19
#
20
#  You should have received a copy of the GNU General Public License
21
#  along with this program; if not, write to the Free Software Foundation,
22
#  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
#
24
import feedparser
25
import yaml
26
import argparse
27
import os
28
import errno
29
30
__author__ = "Olivier Delhomme <[email protected]>"
31
__date__ = "01.06.2016"
32
__version__ = "0.0.1"
33
34
"""
35
This program checks projects versions throught RSS and Atom feeds and
36
should only print those with new release version.
37
38
It implements checking for projects in github.com and freshcode.club.
39
Projects must be added to a YAML file (named by default
40
~/.config/versions/versions.yaml). One can use --file=FILENAME option
41
to specify an alternative YAML file.
42
github projects must be listed under a "github.com:" section and
43
freshcode ones must be listed under a "freshcode.club:" section.
44
45
Versions uses and produces text files. Those files are cache files
46
written into ~/.local/versions directory. "*.cache" are cache files
47
containing the project list and their associated version (the latest).
48
"*.feed" are information feed cache files containing on only one line
49
the latest parsed post of the feed.
50
"""
51
52
53
class Conf:
54
    """
55
    Class to store configuration of the program
56
    """
57
58
    config_dir = ''
59
    local_dir = ''
60
    config_filename = ''
61
    description = {}
62
    options = None
63
64
    def __init__(self):
65
        """
66
        Inits the class
67
        """
68
        self.config_dir = os.path.expanduser("~/.config/versions")
69
        self.local_dir = os.path.expanduser("~/.local/versions")
70
        config_filename = '' # At this stage we do not know if a filename has been set on the command line
71
        description = {}
72
        options = None
73
74
        # Make sure that the directories exists
75
        make_directories(self.config_dir)
76
        make_directories(self.local_dir)
77
78
        self.get_command_line_arguments()
79
80
    # End of init() function
81
82
83
    def load_yaml_from_config_file(self, filename):
84
        """
85
        Loads definitions from the YAML config file filename
86
        """
87
88
        config_file = open(filename, 'r')
89
90
        self.description = yaml.safe_load(config_file)
91
92
        config_file.close()
93
94
    # End of load_yaml_from_config_file() function
95
96
97
    def get_command_line_arguments(self):
98
        """
99
        Defines and gets all the arguments for the command line using
100
        argparse module. This function is called in the __init__ function
101
        of this class.
102
        """
103
104
        parser = argparse.ArgumentParser(description='This program checks release and versions of programs through RSS or Atom feeds', version='versions - 0.0.1')
105
106
        parser.add_argument('--file', action='store', dest='filename', help='Configuration file with projects to check', default='versions.yaml')
107
108
        self.options = parser.parse_args()
109
        self.config_filename = os.path.join(self.config_dir, self.options.filename)
110
111
    # End of get_command_line_arguments() function
112
# End of Conf class
113
114
115
class FileCache:
116
    """
117
    This class should help in managing cache files
118
    """
119
120
    cache_filename = ''
121
    cache_dict = {}  # Dictionnary of projects and their associated version
122
123
    def __init__(self, local_dir, filename):
124
        """
125
        Inits the class. 'local_dir' must be a directory where we want to
126
        store the cache file named 'filename'
127
        """
128
129
        self.cache_filename = os.path.join(local_dir, filename)
130
        self.cache_dict = {}
131
132
    # End of __init__() function
133
134
135
    def read_cache_file(self):
136
        """
137
        Reads the cache file and puts it into a dictionnary of project with
138
        their associated version
139
        """
140
141
        if os.path.isfile(self.cache_filename):
142
143
            cache_file = open(self.cache_filename, 'r')
144
145
            for line in cache_file:
146
147
                line = line.strip()
148
149
                if line.count(' ') > 0:
150
                    (project, version) = line.split(' ', 1)
151
152
                elif line != '':
153
                    project = line
154
                    version = ''
155
156
                self.cache_dict[project] = version
157
158
            cache_file.close()
159
160
    # End of read_cache_file() function
161
162
163
    def write_cache_file(self):
164
        """
165
        Owerwrites dictionnary cache to the cache file
166
        """
167
168
        cache_file = open(self.cache_filename, 'w')
169
        cache_file.truncate(0)
170
        cache_file.flush()
171
172
        for (project, version) in self.cache_dict.iteritems():
173
            cache_file.write('%s %s\n' % (project, version))
174
175
        cache_file.close()
176
177
    # End of write_cache_file() function
178
179
180
    def update_cache_dict(self, project, version):
181
        """
182
        Updates cache dictionnary if needed
183
        """
184
185
        try:
186
            version_cache = self.cache_dict[project]
187
188
            if version != version_cache:
189
                print('%s %s' % (project, version))
190
                self.cache_dict[project] = version
191
192
        except KeyError:
193
            print('%s %s' % (project, version))
194
            self.cache_dict[project] = version
195
196
    # End of update_cache_dict() function
197
# End of FileCache class
198
199
200
class FeedCache:
201
202
    cache_filename = ''
203
    year = 2016
204
    month = 05
205
    day = 01
206
    hour = 00
207
    minute = 00
208
209
    def __init__(self, local_dir, filename):
210
        """
211
        Inits the class. 'local_dir' must be a directory where we want to
212
        store the cache file named 'filename'
213
        """
214
215
        self.cache_filename = os.path.join(local_dir, filename)
216
        self.year = 2016
217
        self.month = 5
218
        self.day = 1
219
        self.hour = 0
220
        self.minute = 0
221
222
    # End of __init__() function
223
224
225
    def read_cache_feed(self):
226
        """
227
        Reads the cache file which should only contain one date on the
228
        first line
229
        """
230
231
        if os.path.isfile(self.cache_filename):
232
            cache_file = open(self.cache_filename, 'r')
233
            (self.year, self.month, self.day, self.hour, self.minute) = cache_file.readline().strip().split(' ', 4)
234
            cache_file.close()
235
236
    # End of read_cache_feed() function
237
238
239
    def write_cache_feed(self):
240
        """
241
        Overwrites the cache file with values stored in this class
242
        """
243
        cache_file = open(self.cache_filename, 'w')
244
        cache_file.truncate(0)
245
        cache_file.flush()
246
247
        cache_file.write('%s %s %s %s %s' % (self.year, self.month, self.day, self.hour, self.minute))
248
249
        cache_file.close()
250
251
    # End of write_cache_feed() function
252
253
254
    def update_cache_feed(self, date):
255
        """
256
        Updates the values stored in the class with the date which should
257
        be a time.struct_time
258
        """
259
260
        self.year = date.tm_year
261
        self.month = date.tm_mon
262
        self.day = date.tm_mday
263
        self.hour = date.tm_hour
264
        self.minute = date.tm_min
265
266
    # End of update_cache_feed() function
267
268
269
    def is_newer(self, date):
270
        """
271
        Tells wether "date" is newer than the one in the cache (returns True
272
        or not (returns False)
273
        """
274
275
        #print("%d %d" % (date.tm_year, self.year))
276
277
        if date.tm_year > self.year:
278
            return True
279
        elif date.tm_year == self.year:
280
            if date.tm_mon > self.month:
281
                return True
282
            elif date.tm_mon == self.month:
283
                if date.tm_mday > self.day:
284
                    return True
285
                elif date.tm_mday == self.day:
286
                    if date.tm_hour > self.hour:
287
                        return True
288
                    elif date.tm_hour == self.hour:
289
                        if date.tm_min > self.minute:
290
                            return True
291
                        else:
292
                            return False
293
                    else:
294
                        return False
295
                else:
296
                    return False
297
            else:
298
                return False
299
        else:
300
            return False
301
302
    # End of is_newer() function
303
304
# End of FeedCache class
305
306
307
def make_directories(path):
308
    """
309
    Makes all directories in path if possible. It is not an error if
310
    path already exists
311
    """
312
313
    try:
314
        os.makedirs(path)
315
316
    except OSError as exc:
317
318
        if exc.errno != errno.EEXIST or os.path.isdir(path) != True:
319
            raise
320
321
# End of make_directories() function
322
323
324
def get_latest_github_release(program):
325
    """
326
    Gets the latest release of a program on github. program must be a
327
    string of the form user/repository
328
    """
329
330
    version = ''
331
    url = 'https://github.com/' + program + '/releases.atom'
332
    feed = feedparser.parse(url)
333
334
    if len(feed.entries) > 0:
335
        version = feed.entries[0].title
336
337
    return version
338
339
# End of get_latest_github_release() function
340
341
342
def check_versions_for_github_projects(github_project_list, local_dir):
343
    """
344
    Checks project's versions on github if any are defined in the yaml
345
    file under the github.com tag.
346
    """
347
348
    github_cache = FileCache(local_dir, 'github.cache')
349
    github_cache.read_cache_file()
350
351
    for project in github_project_list:
352
353
        version = get_latest_github_release(project)
354
        github_cache.update_cache_dict(project, version)
355
356
    github_cache.write_cache_file()
357
358
# End of check_versions_for_github_projects() function
359
360
361
def check_versions_for_freshcode(freshcode_project_list, local_dir):
362
    """
363
    Checks projects with freshcode web site's RSS
364
    """
365
366
367
    freshcode_cache = FileCache(local_dir, 'freshcode.cache')
368
    freshcode_cache.read_cache_file()
369
370
    url = 'http://freshcode.club/projects.rss'
371
    feed = feedparser.parse(url)
372
373
    feed_info = FeedCache(local_dir, 'freshcode.feed')
374
    feed_info.read_cache_feed()
375
376
    if len(feed.entries) > 0:
377
378
        feed_list = []
379
380
        # inserting into a list in reverse order to keep the most recent
381
        # version in case of multiple release of the same project in the
382
        # feeds
383
        for f in feed.entries:
384
            if feed_info.is_newer(f.published_parsed):
385
                feed_list.insert(0, f)
386
387
        feed_info.update_cache_feed(feed.entries[0].published_parsed)
388
389
        for entry in feed_list:
390
            (project, version) = entry.title.strip().split(' ', 1)
391
392
            if project in freshcode_project_list:
393
                freshcode_cache.update_cache_dict(project, version)
394
395
        freshcode_cache.write_cache_file()
396
397
    feed_info.write_cache_feed()
398
399
# End of check_versions_for_freshcode() function
400
401
402
def main():
403
    """
404
    This is the where the program begins
405
    """
406
407
    versions_conf = Conf()  # Configuration options
408
409
    if os.path.isfile(versions_conf.config_filename):
410
411
        versions_conf.load_yaml_from_config_file(versions_conf.config_filename)
412
413
        # Checks projects from github
414
        check_versions_for_github_projects(versions_conf.description['github.com'], versions_conf.local_dir)
415
416
        # Checks projects from freshcode.club
417
        check_versions_for_freshcode(versions_conf.description['freshcode.club'], versions_conf.local_dir)
418
419
    else:
420
        print('Error: file %s does not exist' % config_filename)
421
422
# End of main() function
423
424
425
if __name__=="__main__" :
426
    main()
427