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 ( 997ab3...704545 )
by dup
01:17
created

FeedCache.calculate_minutes()   A

Complexity

Conditions 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 13
rs 9.4285
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 = 1
206
    hour = 0
207
    minute = 0
208
    date_minutes = 0
209
210
211
    def __init__(self, local_dir, filename):
212
        """
213
        Inits the class. 'local_dir' must be a directory where we want to
214
        store the cache file named 'filename'
215
        """
216
217
        self.cache_filename = os.path.join(local_dir, filename)
218
        self.year = 2016
219
        self.month = 5
220
        self.day = 1
221
        self.hour = 0
222
        self.minute = 0
223
        self.date_minutes = self.calculate_minutes(self.year, self.month, self.day, self.hour, self.minute)
224
225
    # End of __init__() function
226
227
228
    def read_cache_feed(self):
229
        """
230
        Reads the cache file which should only contain one date on the
231
        first line
232
        """
233
234
        if os.path.isfile(self.cache_filename):
235
            cache_file = open(self.cache_filename, 'r')
236
            (self.year, self.month, self.day, self.hour, self.minute) = cache_file.readline().strip().split(' ', 4)
237
            self.calculate_minutes(self.year, self.month, self.day, self.hour, self.minute)
238
            cache_file.close()
239
240
    # End of read_cache_feed() function
241
242
243
    def write_cache_feed(self):
244
        """
245
        Overwrites the cache file with values stored in this class
246
        """
247
        cache_file = open(self.cache_filename, 'w')
248
        cache_file.truncate(0)
249
        cache_file.flush()
250
251
        cache_file.write('%s %s %s %s %s' % (self.year, self.month, self.day, self.hour, self.minute))
252
253
        cache_file.close()
254
255
    # End of write_cache_feed() function
256
257
258
    def update_cache_feed(self, date):
259
        """
260
        Updates the values stored in the class with the date which should
261
        be a time.struct_time
262
        """
263
264
        self.year = date.tm_year
265
        self.month = date.tm_mon
266
        self.day = date.tm_mday
267
        self.hour = date.tm_hour
268
        self.minute = date.tm_min
269
        self.date_minutes = self.calculate_minutes_from_date(date)
270
271
    # End of update_cache_feed() function
272
273
274
    def calculate_minutes(self, year, mon, day, hour, mins):
275
        """
276
        Calculate a number of minutes with all parameters and returns
277
        this.
278
        """
279
280
        minutes = (year * 365 * 24 * 60) + \
281
                  (mon * 30 * 24 * 60) + \
282
                  (day * 24 * 60) + \
283
                  (hour * 60) + \
284
                  (mins)
285
286
        return minutes
287
288
    # End of calculate_minutes() function
289
290
291
    def calculate_minutes_from_date(self, date):
292
        """
293
        Transforms a date in a number of minutes to ease comparisons
294
        and returns this number of minutes
295
        """
296
297
        return self.calculate_minutes(date.tm_year, date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min)
298
299
    # End of calculate_minutes() function
300
301
302
    def is_newer(self, date):
303
        """
304
        Tells wether "date" is newer than the one in the cache (returns True
305
        or not (returns False)
306
        """
307
308
        minutes = self.calculate_minutes_from_date(date)
309
310
        if minutes > self.date_minutes:
311
            return True
312
        else:
313
            return False
314
315
    # End of is_newer() function
316
# End of FeedCache class
317
318
319
def make_directories(path):
320
    """
321
    Makes all directories in path if possible. It is not an error if
322
    path already exists
323
    """
324
325
    try:
326
        os.makedirs(path)
327
328
    except OSError as exc:
329
330
        if exc.errno != errno.EEXIST or os.path.isdir(path) != True:
331
            raise
332
333
# End of make_directories() function
334
335
336
def get_latest_github_release(program):
337
    """
338
    Gets the latest release of a program on github. program must be a
339
    string of the form user/repository
340
    """
341
342
    version = ''
343
    url = 'https://github.com/' + program + '/releases.atom'
344
    feed = feedparser.parse(url)
345
346
    if len(feed.entries) > 0:
347
        version = feed.entries[0].title
348
349
    return version
350
351
# End of get_latest_github_release() function
352
353
354
def check_versions_for_github_projects(github_project_list, local_dir):
355
    """
356
    Checks project's versions on github if any are defined in the yaml
357
    file under the github.com tag.
358
    """
359
360
    github_cache = FileCache(local_dir, 'github.cache')
361
    github_cache.read_cache_file()
362
363
    for project in github_project_list:
364
365
        version = get_latest_github_release(project)
366
        github_cache.update_cache_dict(project, version)
367
368
    github_cache.write_cache_file()
369
370
# End of check_versions_for_github_projects() function
371
372
373
def check_versions_for_freshcode(freshcode_project_list, local_dir):
374
    """
375
    Checks projects with freshcode web site's RSS
376
    """
377
378
379
    freshcode_cache = FileCache(local_dir, 'freshcode.cache')
380
    freshcode_cache.read_cache_file()
381
382
    url = 'http://freshcode.club/projects.rss'
383
    feed = feedparser.parse(url)
384
385
    feed_info = FeedCache(local_dir, 'freshcode.feed')
386
    feed_info.read_cache_feed()
387
388
    if len(feed.entries) > 0:
389
390
        feed_list = []
391
392
        # inserting into a list in reverse order to keep the most recent
393
        # version in case of multiple release of the same project in the
394
        # feeds
395
        for f in feed.entries:
396
            if feed_info.is_newer(f.published_parsed):
397
                feed_list.insert(0, f)
398
399
        feed_info.update_cache_feed(feed.entries[0].published_parsed)
400
401
        for entry in feed_list:
402
            (project, version) = entry.title.strip().split(' ', 1)
403
404
            if project in freshcode_project_list:
405
                freshcode_cache.update_cache_dict(project, version)
406
407
        freshcode_cache.write_cache_file()
408
409
    feed_info.write_cache_feed()
410
411
# End of check_versions_for_freshcode() function
412
413
414
def main():
415
    """
416
    This is the where the program begins
417
    """
418
419
    versions_conf = Conf()  # Configuration options
420
421
    if os.path.isfile(versions_conf.config_filename):
422
423
        versions_conf.load_yaml_from_config_file(versions_conf.config_filename)
424
425
        # Checks projects from github
426
        check_versions_for_github_projects(versions_conf.description['github.com'], versions_conf.local_dir)
427
428
        # Checks projects from freshcode.club
429
        check_versions_for_freshcode(versions_conf.description['freshcode.club'], versions_conf.local_dir)
430
431
    else:
432
        print('Error: file %s does not exist' % config_filename)
433
434
# End of main() function
435
436
437
if __name__=="__main__" :
438
    main()
439