summaryrefslogtreecommitdiff
path: root/scripts/template_verifier.py
blob: 980ceb8df811a78b97435f7e75c74c4153eb9683 (plain)
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
#!/usr/bin/python
# @file template_verifier.py
# @brief Message template compatibility verifier.
#
# Copyright (c) 2007-$CurrentYear$, Linden Research, Inc.
# $License$

"""template_verifier is a script which will compare the
current repository message template with the "master" message template, accessible
via http://secondlife.com/app/message_template/master_message_template.msg
If [FILE] is specified, it will be checked against the master template.
If [FILE] [FILE] is specified, two local files will be checked against
each other.
"""

from os.path import realpath, dirname, join, exists
setup_path = join(dirname(realpath(__file__)), "setup-path.py")
if exists(setup_path):
    execfile(setup_path)
import optparse
import os
import sys
import urllib

from indra.ipc import compatibility
from indra.ipc import llmessage

def die(msg):
    print >>sys.stderr, msg
    sys.exit(1)

MESSAGE_TEMPLATE = 'message_template.msg'

PRODUCTION_ACCEPTABLE = (compatibility.Same, compatibility.Newer)
DEVELOPMENT_ACCEPTABLE = (
    compatibility.Same, compatibility.Newer,
    compatibility.Older, compatibility.Mixed)	

def getstatusall(command):
    """ Like commands.getstatusoutput, but returns stdout and 
    stderr separately(to get around "killed by signal 15" getting 
    included as part of the file).  Also, works on Windows."""
    (input, out, err) = os.popen3(command, 't')
    input.close() # send no input to the command
    output = out.read()
    error = err.read()
    out.close()
    status = err.close() # the status comes from the *last* pipe you close
    return status, output, error

def getstatusoutput(command):
    status, output, error = getstatusall(command)
    return status, output

def compare(base, current, mode):
    """Compare the current template against the base template using the given
    'mode' strictness:

    development: Allows Same, Newer, Older, and Mixed
    production: Allows only Same or Newer

    Print out information about whether the current template is compatible
    with the base template.

    Returns a tuple of (bool, Compatibility)
    Return True if they are compatible in this mode, False if not.
    """
    base = llmessage.parseTemplateString(base)
    current = llmessage.parseTemplateString(current)

    compat = current.compatibleWithBase(base)
    if mode == 'production':
        acceptable = PRODUCTION_ACCEPTABLE
    else:
        acceptable = DEVELOPMENT_ACCEPTABLE

    if type(compat) in acceptable:
        return True, compat
    return False, compat

def local_template_filename():
    """Returns the message template's default location relative to template_verifier.py:
    ./messages/message_template.msg."""
    d = os.path.dirname(os.path.realpath(__file__))
    return os.path.join(d, 'messages', MESSAGE_TEMPLATE)

def run(sysargs):
    parser = optparse.OptionParser(
        usage="usage: %prog [FILE] [FILE]",
        description=__doc__)
    parser.add_option(
        '-m', '--mode', type='string', dest='mode',
        default='development',
        help="""[development|production] The strictness mode to use
while checking the template; see the wiki page for details about
what is allowed and disallowed by each mode:
http://wiki.secondlife.com/wiki/Template_verifier.py
""")
    parser.add_option(
        '-u', '--master_url', type='string', dest='master_url',
        default='http://secondlife.com/app/message_template/master_message_template.msg',
        help="""The url of the master message template.""")

    options, args = parser.parse_args(sysargs)

    # both current and master supplied in positional params
    if len(args) == 2:
        master_filename, current_filename = args
        print "base:", master_filename
        print "current:", current_filename
        master = file(master_filename).read()
        current = file(current_filename).read()
    # only current supplied in positional param
    elif len(args) == 1:
        master = None
        current_filename = args[0]
        print "base: <master template from repository>"
        print "current:", current_filename
        current = file(current_filename).read()
    # nothing specified, use defaults for everything
    elif len(args) == 0:
        master = None
        current = None
    else:
        die("Too many arguments")

    # fetch the master from the url (default or supplied)
    if master is None:
        master = urllib.urlopen(options.master_url).read()

    # fetch the template for this build
    if current is None:
        current_filename = local_template_filename()
        print "base: <master template from repository>"
        print "current:", current_filename
        current = file(current_filename).read()

    acceptable, compat = compare(
        master, current, options.mode)

    def explain(header, compat):
        print header
        # indent compatibility explanation
        print '\n\t'.join(compat.explain().split('\n'))

    if acceptable:
        explain("--- PASS ---", compat)
    else:
        explain("*** FAIL ***", compat)
        return 1

if __name__ == '__main__':
    sys.exit(run(sys.argv[1:]))