This file is indexed.

/usr/bin/package-tracker is in open-infrastructure-package-tracker 20170515-3.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
#!/usr/bin/env python3

# package-tracker - Compare and track package versions in debian repositories
# Copyright (C) 2017 Andreas Kreuzer <andreas.kreuzer@open-infrastructure.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import os
import os.path
import argparse
import apt
import apt_pkg
import re
import logging
import time

software = u"package-tracker"
program = sys.argv[0]
install_path = "/"
apt_root_path = "var/lib/" + software
config_path = "etc/" + software
log_file = "var/log/" + software + "/" + software + ".log"
template_path = "usr/share/" + software + "/templates"

# --------------------------------
#  Argument handling with argparse
#
def check_args_source(check_arg):
    """
    Check validity of sources file arguments.

    This is the handler for argparse to verfiy the argument passed to the
    program.

    Parameters
    ----------
    check_arg : string
        Given argument from comand line

    Returns
    -------
    string
        Full path to file or given argument if invalid

    """

    file_path = os.path.join(config_path, check_arg)
    if (os.path.exists(os.path.join(install_path, file_path))):
        return file_path
    else:
        msg = file_path + " does not exist"
        raise argparse.ArgumentTypeError(msg)

    return check_arg

parser = argparse.ArgumentParser(
    description="Compare and track package versions in debian repositories"
)
parser.add_argument(
    "READ",
    help="apt sources file to read",
    type=check_args_source
)
parser.add_argument(
    "COMPARE",
    help="apt sources file to compare against",
    type=check_args_source
)
parser.add_argument(
    "-t", "--filter-type",
    help="displays only given action (default: %(default)s)",
    choices=["all", "lower", "obsolete", "ok"],
    default="all"
)
parser.add_argument(
    "-o", "--output-type",
    help="output format type (default: %(default)s)",
    choices=["stdout", "html"],
    default="stdout"
)
parser.add_argument(
    "-f", "--file-path",
    help="file path to output report",
    default="report.html"
)
parser.add_argument(
    "-i", "--ignore-tag",
    help="remove given tag from the end of version string"
)
parser.add_argument(
    "-v", "--verbose",
    help="print summary informations",
    action="store_true"
)
parser.add_argument(
    "-d", "--debug",
    help="log debug information",
    action="store_true"
)
args = parser.parse_args()


# --------------------------------
# Function definitions
#
def exit_critical(message):
    logging.error(message)
    print("Exiting: ", message)
    sys.exit(1)

def bit_isset(number, mask):
    """ Testing a number against a binary mask """
    return ((number & mask) == mask)

def relink_sources_file(filename):
    """
    Creates a symlink of the desired file to sources.list

    Parameters
    ----------
    filename : string
        relative path to sources file
    """
    global apt_root_path, config_path, install_path

    symlink_dir = os.path.join(
                        install_path,
                        apt_root_path,
                        "etc/apt/sources.list"
                  )

    if (os.path.exists(symlink_dir)):
        os.remove(symlink_dir)

    os.symlink(
        os.path.join(install_path, filename),
        symlink_dir
    )

PT_VERSION_COMPARE_ERROR = 0
PT_VERSION_EQUAL = 1
PT_VERSION_LOWER = 2
PT_VERSION_HIGHER = 4
PT_VERSION_IGNORED_TAG = 8
PT_VERSION_TAG_NORMAL = 16
PT_VERSION_TAG_LOWER = 32
PT_VERSION_TAG_HIGHER = 64
PT_VERSION_OBSOLETE = 128
PT_VERSION_BINNMU = 256

def compare_debug(compare_result):
    """ Create a human readable output of the compare result """
    global PT_VERSION_COMPARE_ERROR, PT_VERSION_EQUAL, PT_VERSION_LOWER
    global PT_VERSION_HIGHER, PT_VERSION_IGNORED_TAG
    global PT_VERSION_TAG_NORMAL, PT_VERSION_TAG_LOWER
    global PT_VERSION_TAG_HIGHER

    log_string = "Compare result:"
    if(compare_result == PT_VERSION_COMPARE_ERROR):
        logging.debug(log_string + "PT_VERSION_COMPARE_ERROR");
        return
    if((compare_result & PT_VERSION_EQUAL)
            == PT_VERSION_EQUAL):
        log_string += " PT_VERSION_EQUAL"
    if((compare_result & PT_VERSION_LOWER)
            == PT_VERSION_LOWER):
        log_string += " PT_VERSION_LOWER"
    if((compare_result & PT_VERSION_HIGHER)
            == PT_VERSION_HIGHER):
        log_string += " PT_VERSION_HIGHER"
    if((compare_result & PT_VERSION_IGNORED_TAG)
            == PT_VERSION_IGNORED_TAG):
        log_string += " PT_VERSION_IGNORED_TAG"
    if((compare_result & PT_VERSION_TAG_NORMAL)
            == PT_VERSION_TAG_NORMAL):
        log_string += " PT_VERSION_TAG_NORMAL"
    if((compare_result & PT_VERSION_TAG_LOWER)
            == PT_VERSION_TAG_LOWER):
        log_string += " PT_VERSION_TAG_LOWER"
    if((compare_result & PT_VERSION_TAG_HIGHER)
            == PT_VERSION_TAG_HIGHER):
        log_string += " PT_VERSION_TAG_HIGHER"
    if((compare_result & PT_VERSION_OBSOLETE)
            == PT_VERSION_OBSOLETE):
        log_string += " PT_VERSION_OBSOLETE"
    if((compare_result & PT_VERSION_BINNMU)
            == PT_VERSION_BINNMU):
        log_string += " PT_VERSION_BINNMU"

    logging.debug(log_string)

update_counter = 0
equal_counter = 0
obsolete_counter = 0
def compare_versions(from_version, to_version):
    """
    Compares two given versions with apt_pkg module

    Parameters
    ----------
    from_version : string
        Version string to compare from
    to_version : string
        Version string to compare against

    Returns
    -------
    A binary representation of the compare result. Used to match against binary
    masks.
    """
    global update_counter, equal_counter
    compare_result = 0

    if (args.ignore_tag != None):
        version_re = re.search(
                r'(?P<debrev>-[0-9]+)?(?P<tag>(?P<tagmod>[~+]?)' + args.ignore_tag + r'(?P<tagrev>[0-9]+))$',
                from_version
        )
        if (version_re):
            # We have found a tag from a debian derrivative
            compare_result |= PT_VERSION_IGNORED_TAG

            if (version_re.group('tagmod') == '~'):
                compare_result |= PT_VERSION_TAG_LOWER
            elif (version_re.group('tagmod') == '+'):
                compare_result |= PT_VERSION_TAG_HIGHER
            else:
                compare_result |= PT_VERSION_TAG_NORMAL

            # Trim the version string
            trimpos = version_re.start('tagmod')
            if ((version_re.group('debrev') == '-0')
                    and bit_isset(compare_result, PT_VERSION_TAG_HIGHER)):
                trimpos = version_re.start(0)
            version = from_version[:trimpos]
    else:
        version = from_version

    # Search for a binNMU package version
    if(re.search(r'\+b[0-9]+$', to_version)):
        compare_result |= PT_VERSION_BINNMU

    result = apt_pkg.version_compare(version, to_version)
    if (result > 0):
        compare_result |= PT_VERSION_HIGHER
        equal_counter += 1
    elif (result < 0):
        compare_result |= PT_VERSION_LOWER
        update_counter += 1
    else:
        compare_result |= PT_VERSION_EQUAL
        equal_counter += 1

    return compare_result

def print_package_message(package, compare_result):
    """
    Print information for each version differences

    Parameters
    ----------
    package : string
        Package name (without architecture)
    compare_result : binary
        Result of compared versions from compare_versions()
    """
    global query_cache
    global compare_cache
    global args

    tmp = None

    if ((args.filter_type == "lower") or (args.filter_type == "all")
            and bit_isset(compare_result, PT_VERSION_LOWER)):
        tmp = package + " is lower in version"
        if (bit_isset(compare_result, PT_VERSION_BINNMU)):
            tmp += " [NMU]"
        tmp += "\n"

    elif ((args.filter_type == "ok" or args.filter_type == "all")
            and (bit_isset(compare_result, PT_VERSION_EQUAL)
                or bit_isset(compare_result, PT_VERSION_HIGHER))
            ):
        tmp = package + "\n"

    elif ((args.filter_type == "obsolete") or (args.filter_type == "all")
            and bit_isset(compare_result, PT_VERSION_OBSOLETE)):
        tmp = package + " is obsolete\n"

    if (tmp):
        tmp += "\tparent version: " + compare_cache[package].versions[0].source_version + "\n"
        tmp += "\tcurrent version: " + query_cache[package].versions[0].source_version
        print()
        print(tmp)


# --------------------------------
# Templated output
#
template_rawdata = ''
template_data = {}
template_data['main'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['security'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['updates'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['extras'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['backports'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['backports-extras'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }

def load_template():
    """ Initiates templated output and loads the template """
    global args
    global install_path, template_path
    global template_rawdata, template_data

    #FIXME: using command parameter to switch to another template
    file_path = os.path.join(install_path, template_path)
    file_name = ''
    if (args.output_type == "html"):
        file_name = "report.html.in"
    f = open(file_path + "/" + file_name, 'r')
    if (not f):
        exit_critical("Cannot read html template: ", file_path + "/" + file_name)
    template_rawdata = f.read()
    f.close()

    m = re.search(r'{%PT_CONTENT_SPAN_MAIN%}(.+?){%/PT_CONTENT_SPAN_MAIN%}',
            template_rawdata, re.DOTALL)
    #FIXME: checks for other template content place holder below
    if (not m):
        exit_critical("Template error: PT_CONTENT_SPAN_MAIN not found.")
    template_data['main']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_MAIN%}(.+?){%/PT_CONTENT_ELEMENTS_MAIN%}',
            template_rawdata, re.DOTALL)
    template_data['main']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_SECURITY%}(.+?){%/PT_CONTENT_SPAN_SECURITY%}',
            template_rawdata, re.DOTALL)
    template_data['security']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_SECURITY%}(.+?){%/PT_CONTENT_ELEMENTS_SECURITY%}',
            template_rawdata, re.DOTALL)
    template_data['security']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_UPDATES%}(.+?){%/PT_CONTENT_SPAN_UPDATES%}',
            template_rawdata, re.DOTALL)
    template_data['updates']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_UPDATES%}(.+?){%/PT_CONTENT_ELEMENTS_UPDATES%}',
            template_rawdata, re.DOTALL)
    template_data['updates']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_EXTRAS%}(.+?){%/PT_CONTENT_SPAN_EXTRAS%}',
            template_rawdata, re.DOTALL)
    template_data['extras']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_EXTRAS%}(.+?){%/PT_CONTENT_ELEMENTS_EXTRAS%}',
            template_rawdata, re.DOTALL)
    template_data['extras']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_BACKPORTS%}(.+?){%/PT_CONTENT_SPAN_BACKPORTS%}',
            template_rawdata, re.DOTALL)
    template_data['backports']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_BACKPORTS%}(.+?){%/PT_CONTENT_ELEMENTS_BACKPORTS%}',
            template_rawdata, re.DOTALL)
    template_data['backports']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_BACKPORTSEXTRAS%}(.+?){%/PT_CONTENT_SPAN_BACKPORTSEXTRAS%}',
            template_rawdata, re.DOTALL)
    template_data['backports-extras']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_BACKPORTSEXTRAS%}(.+?){%/PT_CONTENT_ELEMENTS_BACKPORTSEXTRAS%}',
            template_rawdata, re.DOTALL)
    template_data['backports-extras']['rawelement'] = m.group(1)

#FIXME: Parameters for all attributes to output into the table. As such
#       lookup via cache will not be nessary and templating engine can
#       be moved to a module.
def add_template_element(suite, package, compare_result):
    """
    Build result data for template engine

    Parameters
    ----------
    suite : string
        Archive suite name
    package : string
        Package name (without architecture)
    compare_result : binary
        Result of compared versions from compare_versions()
    """
    global template_data

    action = ""
    if ((bit_isset(compare_result, PT_VERSION_EQUAL)
        or bit_isset(compare_result, PT_VERSION_HIGHER))
            and (args.filter_type == "ok" or args.filter_type == "all")):
        action = "ok"
    elif (bit_isset(compare_result, PT_VERSION_LOWER)
            and (args.filter_type == "lower" or args.filter_type == "all")):
        action = "lower"
        if (bit_isset(compare_result, PT_VERSION_BINNMU)):
            action += " [NMU]"
    elif (bit_isset(compare_result, PT_VERSION_OBSOLETE)
            and (args.filter_type == "obsolete" or args.filter_type == "all")):
        action = "obsolete"
    else:
        return

    template_data[suite]['counter'] += 1

    tmp = template_data[suite]['rawelement'].replace('{%PT_ITEM_INDEX/%}',
            str(template_data[suite]['counter']))
    tmp = tmp.replace('{%PT_SOURCE_PACKAGE/%}',
            query_cache[package].versions[0].source_name)
    tmp = tmp.replace('{%PT_PARENT_VERSION/%}',
            compare_cache[package].versions[0].source_version)
    tmp = tmp.replace('{%PT_CURRENT_VERSION/%}',
            query_cache[package].versions[0].source_version)
    tmp = tmp.replace('{%PT_STATUS/%}',
            action)

    template_data[suite]['elements'] += tmp

def output_template():
    """ Outputs a file with the generated results """
    global args
    global template_rawdata, template_data
    global install_path, config_path


    #FIXME: usage of apt functions to query archives in use
    f = open(os.path.join(install_path, args.READ), 'r')
    configured_archives_query = f.read()
    f.close()
    configured_archives_query = re.sub(r"^\W?#.+?$\n?", "",
                        configured_archives_query, flags=re.MULTILINE)
    configured_archives_query = re.sub(r"^\W?$\n?", "",
                        configured_archives_query, flags=re.MULTILINE)

    f = open(os.path.join(install_path, args.COMPARE), 'r')
    configured_archives_compare = f.read()
    f.close()
    configured_archives_compare = re.sub(r"^\W?#.+?$\n?", "",
                        configured_archives_compare, flags=re.MULTILINE)
    configured_archives_compare = re.sub(r"^\W?$\n?", "",
                        configured_archives_compare, flags=re.MULTILINE)

    title = "Package-Tracker Report"
    intro = "<h1>" + title + "</h1>\n"
    intro += "<p>This Report was generated at: " + time.ctime() + "</p>\n"
    intro += "<p>"
    if ((args.filter_type == "lower") or (args.filter_type == "all")):
        intro += "Total of source packages wich are outdated: "
        intro += str(update_counter) + "<br/>\n"
    if ((args.filter_type == "obsolete") or (args.filter_type == "all")):
        intro += "Total of source packages wich are obsolete: "
        intro += str(obsolete_counter) + "<br/>\n"
    if ((args.filter_type == "ok") or (args.filter_type == "all")):
        intro += "Total of source packages with no action requested: "
        intro += str(equal_counter) + "<br/>\n"
    intro += "</p>"

    summary = "Archives were read from:\n<pre>\n"
    summary += configured_archives_query + "</pre>\n"
    summary += "Archives wich have been compared against:\n<pre>\n"
    summary += configured_archives_compare + "</pre>\n"

    if (template_data['main']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_MAIN%}(.+?){%/PT_CONTENT_SPAN_MAIN%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_MAIN%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_MAIN%}(.+?){%/PT_CONTENT_ELEMENTS_MAIN%}'
    template_rawdata = re.sub(re_element, template_data['main']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['security']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_SECURITY%}(.+?){%/PT_CONTENT_SPAN_SECURITY%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_SECURITY%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_SECURITY%}(.+?){%/PT_CONTENT_ELEMENTS_SECURITY%}'
    template_rawdata = re.sub(re_element, template_data['security']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['updates']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_UPDATES%}(.+?){%/PT_CONTENT_SPAN_UPDATES%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_UPDATES%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_UPDATES%}(.+?){%/PT_CONTENT_ELEMENTS_UPDATES%}'
    template_rawdata = re.sub(re_element, template_data['updates']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['extras']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_EXTRAS%}(.+?){%/PT_CONTENT_SPAN_EXTRAS%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_EXTRAS%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_EXTRAS%}(.+?){%/PT_CONTENT_ELEMENTS_EXTRAS%}'
    template_rawdata = re.sub(re_element, template_data['extras']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['backports']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_BACKPORTS%}(.+?){%/PT_CONTENT_SPAN_BACKPORTS%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_BACKPORTS%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_BACKPORTS%}(.+?){%/PT_CONTENT_ELEMENTS_BACKPORTS%}'
    template_rawdata = re.sub(re_element, template_data['backports']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['backports-extras']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_BACKPORTSEXTRAS%}(.+?){%/PT_CONTENT_SPAN_BACKPORTSEXTRAS%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_BACKPORTSEXTRAS%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_BACKPORTSEXTRAS%}(.+?){%/PT_CONTENT_ELEMENTS_BACKPORTSEXTRAS%}'
    template_rawdata = re.sub(re_element, template_data['backports-extras']['elements'],
                            template_rawdata, flags=re.DOTALL)

    #FIXME: Cleanup of unmatched tags from template


    # replace single items
    template_rawdata = template_rawdata.replace('{%PT_TITLE/%}', title)
    template_rawdata = template_rawdata.replace('{%PT_INTRO/%}', intro)
    template_rawdata = template_rawdata.replace('{%PT_SUMMARY/%}', summary)

    file_name = args.file_path
    f = open(file_name, "w")
    f.write(template_rawdata)
    f.close()

# --------------------------------
# Apt cache packages processing
#
compare_data = {}
query_data = {}
suites = [
            'main',
            'security',
            'updates',
            'extras',
            'backports',
            'backports-extras'
        ]

# FIXME: debug
def debug_data():
    for source_package, binary_packages in compare_data.items():
        print("source: ", source_package)
        for binary_package in binary_packages:
            print("  binary: ", binary_package)

def process_packages():
    """ Read all packages in compare cache to detect source/binary packages """
    global query_data
    global compare_data

    for package in compare_cache:
        archive = package.versions[0].origins[0].archive
        if (not archive in compare_data):
            compare_data[archive] = {}
        if (not package.versions[0].source_name in compare_data[archive]):
            compare_data[archive][package.versions[0].source_name] = []

        compare_data[archive][package.versions[0].source_name].append(package.shortname)

    for package in query_cache:
        archive = package.versions[0].origins[0].archive
        if (not archive in query_data):
            query_data[archive] = {}
        if (not package.versions[0].source_name in query_data[archive]):
            query_data[archive][package.versions[0].source_name] = []

        query_data[archive][package.versions[0].source_name].append(package.shortname)

def process_compared_data():
    """
    Iterate over all source packages and process
    the compare result data from process_packages()
    """
    global equal_conter, update_counter, obsolete_counter

    # check if we have to compare against
    # or from a testing archive
    compare_archive = "stable"
    if (not compare_archive in compare_data):
        compare_archive = "testing"
    if (not compare_archive in compare_data):
        logging.error("There is no testing or stable suite in parent distribution.")
        sys.exit(1)
    query_archive = "stable"
    if (not query_archive in query_data):
        query_archive = "testing"
    if (not query_archive in query_data):
        logging.error("There is no testing or stable suite in current distribution.")
        sys.exit(1)

    for suite in suites:
        if (suite != 'main'):
            if (suite == 'backports' or suite == 'backports-extras'):
                compare_suite = 'unstable'
            else:
                compare_suite = compare_archive + "-" + suite
            if (not compare_suite in compare_data):
                logging.info("skipping check for '" + suite + "'. It does not exist in parent archives.")
                continue
            query_suite = query_archive + "-" + suite
            if (not query_suite in query_data):
                logging.info("skipping check for '" + suite + "'. It does not exist in current archives.")
                continue
        else:
            compare_suite = compare_archive
            query_suite = query_archive

        #FIXME: stdout output
        if (args.output_type == "stdout"):
            print()
            print("comparing '" + query_suite + "' against '" + compare_suite + "' suite")

        for source_package in compare_data[compare_suite]:
            if (source_package in query_data[query_suite]):
                for binary_package in compare_data[compare_suite][source_package]:
                    if (binary_package in query_data[query_suite]):
                        result = compare_versions(
                                    query_cache[binary_package].versions[0].version,
                                    compare_cache[binary_package].versions[0].version)

                        # FIXME: testing
                        # Check if compare_result has errors
                        if (result == PT_VERSION_COMPARE_ERROR):
                            logging.error(
                                    "compare_result for " + binary_package.shortname + " has not succeeded."
                        )
                        if (args.debug):
                            logging.debug(
                                "source package: "
                                    + query_cache[source_package].shortname
                                    + " "
                                    + query_cache[source_package].versions[0].source_version
                                    + " (current) "
                                    + compare_cache[source_package].versions[0].source_version
                                    + " (parent)"
                            )
                            logging.debug(
                                "binary package: "
                                    + query_cache[binary_package].shortname
                            )
                            logging.debug(
                                "current binary version: "
                                    + query_cache[binary_package].versions[0].version
                            )
                            logging.debug(
                                "parent binary version: "
                                    + compare_cache[binary_package].versions[0].version
                            )
                            compare_debug(result)

                        if (args.output_type == "stdout"):
                            print_package_message(source_package, result)
                        else:
                            add_template_element(suite, source_package, result)

    #Check for extras and backports-extras source packages
    #wich have a package in parent archive
    for suite in [ 'extras', 'backports-extras' ]:
        #FIXME: stdout output
        if (args.output_type == "stdout"):
            print()
            print("obsolete check for '" + query_archive + "-" + suite + "'")

        if query_archive + "-" + suite in query_data:
            for source_package in query_data[query_archive + "-" + suite]:
                if (source_package in compare_cache):
                    obsolete_counter += 1

                    if (args.debug):
                        logging.debug(
                                "obsolete-check: source package: "
                               + query_cache[source_package].shortname
                               + " "
                               + query_cache[source_package].versions[0].version
                               + " (current) is obsolete"
                        )
                        logging.debug(
                                "obsolete-check: parent source version: "
                                + compare_cache[source_package].versions[0].source_version
                        )

                    if (args.output_type == "stdout"):
                        print_package_message(source_package, PT_VERSION_OBSOLETE)
                    else:
                        add_template_element(suite, source_package, PT_VERSION_OBSOLETE)


# --------------------------------
# Main:
#
def main():
    global query_cache
    global compare_cache
    global install_path
    global apt_root_path
    global args

    # Configuring logging
    if (args.verbose == True):
        loglevel=logging.DEBUG
    else:
        loglevel=logging.INFO

    logging.basicConfig(
            filename=os.path.join(install_path, log_file),
            level=loglevel,
            filemode='w',
            format="%(asctime)s: "+software+": %(levelname)s: %(message)s"
    )

    if (args.verbose == True):
        print("Reading apt archives to compare from..")

    relink_sources_file(args.READ)

    query_cache = apt.Cache(rootdir=os.path.join(install_path, apt_root_path))
    query_cache.update(None)
    query_cache.open()

    if (args.verbose == True):
        print("Reading apt archives to compare against..")

    relink_sources_file(args.COMPARE)

    compare_cache = apt.Cache(rootdir=os.path.join(install_path, apt_root_path))
    compare_cache.update(None)
    compare_cache.open()

    if (args.verbose == True):
        print()
        print(
            "Number of packages in archives to compare from: ",
            len(query_cache)
        )
        print(
            "Number of packages to compare against: ",
            len(compare_cache)
        )
        print()
        print("Comparing packages..")

    process_packages()

    if (args.verbose == True):
        print(
            "Source packages to compare: ",
            len(compare_data)
        )

    if (args.output_type == "html"):
        load_template()

    process_compared_data()

    if (args.output_type == "html"):
        output_template()

    if (args.verbose == True):
        print()
        print(
                "Total of source packages to update: ",
                update_counter
        )
        print(
                "Total of source packages wich are obsolete: ",
                obsolete_counter
        )
        print(
                "Total of source packages with no action required: ",
                equal_counter
        )


if (__name__ == "__main__"):
    main()

sys.exit(0)