#!/usr/bin/env python
# coding=utf8

"""
Convert selected @objc attributes in a source file into access notes, removing
the originals in the process.
"""

from __future__ import print_function

import io
import re
import sys

#
# Entry point
#


def main():
    if len(sys.argv) != 4:
        print('Too few args to ' + sys.argv[0])
        print('Usage: access-note-gen.py <input-file> <output-source-file> ' +
              '<output-access-notes-file>')
        sys.exit(1)

    with io.open(sys.argv[1], mode='r', encoding='utf8') as input_file, \
         io.open(sys.argv[2], mode='w', encoding='utf8') as output_file, \
         io.open(sys.argv[3], mode='w', encoding='utf8') as access_notes_file:

        # Add header to access notes file
        access_notes_file.write(u"""\
Reason: 'fancy tests'
Notes:""")

        # Loop over input lines, transforming them into output lines, writing access
        # notes as a side effect.
        for input_line in input_file:
            # Look for access-note-move comments.
            input_line = access_note_move_re.sub(replacer(move_at_objc_to_access_note,
                                                          access_notes_file),
                                                 input_line, count=1)

            # Look for access-note-adjust comments.
            input_line = adjust_comment_re.sub(replacer(adjust_comments),
                                               input_line, count=1)

            output_file.write(input_line)


#
# Offsets
#

"""Matches an @±N offset."""
offset_re_fragment = r'[ \t]*(?:@([+-]\d+))?[ \t]*'


def offsetify(*offsets):
    """Sum line offsets matched by offset_re_fragment and convert them to strings
       like @+3 or @-2."""

    offset = sum([int(o) for o in offsets if o is not None])
    if offset < 0:
        return u"@-" + str(-offset)
    elif offset > 0:
        return u"@+" + str(offset)
    else:
        return u""


#
# Adjusting comments
#

"""Matches expected-warning/note/remark and its offset."""
expected_other_diag_re = re.compile(r'expected-(warning|note|remark)' +
                                    offset_re_fragment)

"""Matches expected-error and its offset."""
expected_error_re = re.compile(r'expected-error' + offset_re_fragment)

"""Matches the string 'marked @objc'."""
marked_objc_re = re.compile(r'marked @objc')

"""Matches any non-none fix-it expectation."""
fixit_re = re.compile(r'{{\d+-\d+=[^}]*}}')


def adjust_comments(offset, comment_str):
    """Replace expected-errors with expected-remarks, and make other adjustments
       to diagnostics so that they reflect access notes."""

    adjusted = expected_other_diag_re.sub(lambda m: u"expected-" + m.group(1) +
                                                    offsetify(offset, m.group(2)),
                                          comment_str)
    adjusted = expected_error_re.sub(lambda m: u"expected-remark" +
                                               offsetify(offset, m.group(1)),
                                     adjusted)
    adjusted = marked_objc_re.sub(u"marked @objc by an access note", adjusted)
    adjusted = fixit_re.sub(u"{{none}}", adjusted)

    return u"// [expectations adjusted] " + adjusted


#
# Writing attrs to access notes
#

def move_at_objc_to_access_note(access_notes_file, arg, offset, access_note_name):
    """Write an @objc attribute into an access notes file, then return the
       string that will replace the attribute and trailing comment."""

    access_notes_file.write(u"""
- Name: '{}'
  ObjC: true""".format(access_note_name))
    if arg:
        access_notes_file.write(u"""
  ObjCName: '{}'""".format(arg))

    # Default to shifting expected diagnostics down 1 line.
    if offset is None:
        offset = 1

    return u"// access-note-adjust" + offsetify(offset) + u" [attr moved] " + \
           u"expected-remark{{access note for fancy tests adds attribute 'objc' to " + \
           u"this }} expected-note{{add attribute explicitly to silence this warning}}"


#
# Matching lines
#

"""Matches '@objc(foo) // access-note-move{{access-note-name}}'
   or '@objc // access-note-move{{access-note-name}}'"""
access_note_move_re = re.compile(r'@objc(?:\(([\w:]+)\))?[ \t]*' +
                                 r'//[ \t]*access-note-move' +
                                 offset_re_fragment +
                                 r'\{\{([^}]*)\}\}')

"""Matches // access-note-adjust <comment>"""
adjust_comment_re = re.compile(r'//[ \t]*access-note-adjust' + offset_re_fragment +
                               r'(.*)')


def replacer(fn, *args):
    """Returns a lambda which calls fn with args, followed by the groups from
       the match passed to the lambda."""

    return lambda m: fn(*(args + m.groups()))


main()

#!/usr/bin/env python
# coding=utf8

"""
Convert selected @objc attributes in a source file into access notes, removing
the originals in the process.
"""

from __future__ import print_function

import io
import re
import sys

#
# Entry point
#


def main():
    if len(sys.argv) != 4:
        print('Too few args to ' + sys.argv[0])
        print('Usage: access-note-gen.py <input-file> <output-source-file> ' +
              '<output-access-notes-file>')
        sys.exit(1)

    with io.open(sys.argv[1], mode='r', encoding='utf8') as input_file, \
         io.open(sys.argv[2], mode='w', encoding='utf8') as output_file, \
         io.open(sys.argv[3], mode='w', encoding='utf8') as access_notes_file:

        # Add header to access notes file
        access_notes_file.write(u"""\
Reason: 'fancy tests'
Notes:""")

        # Loop over input lines, transforming them into output lines, writing access
        # notes as a side effect.
        for input_line in input_file:
            # Look for access-note-move comments.
            input_line = access_note_move_re.sub(replacer(move_at_objc_to_access_note,
                                                          access_notes_file),
                                                 input_line, count=1)

            # Look for access-note-adjust comments.
            input_line = adjust_comment_re.sub(replacer(adjust_comments),
                                               input_line, count=1)

            output_file.write(input_line)


#
# Offsets
#

"""Matches an @±N offset."""
offset_re_fragment = r'[ \t]*(?:@([+-]\d+))?[ \t]*'


def offsetify(*offsets):
    """Sum line offsets matched by offset_re_fragment and convert them to strings
       like @+3 or @-2."""

    offset = sum([int(o) for o in offsets if o is not None])
    if offset < 0:
        return u"@-" + str(-offset)
    elif offset > 0:
        return u"@+" + str(offset)
    else:
        return u""


#
# Adjusting comments
#

"""Matches expected-warning/note/remark and its offset."""
expected_other_diag_re = re.compile(r'expected-(warning|note|remark)' +
                                    offset_re_fragment)

"""Matches expected-error and its offset."""
expected_error_re = re.compile(r'expected-error' + offset_re_fragment)

"""Matches the string 'marked @objc'."""
marked_objc_re = re.compile(r'marked @objc')

"""Matches any non-none fix-it expectation."""
fixit_re = re.compile(r'{{\d+-\d+=[^}]*}}')


def adjust_comments(offset, comment_str):
    """Replace expected-errors with expected-remarks, and make other adjustments
       to diagnostics so that they reflect access notes."""

    adjusted = expected_other_diag_re.sub(lambda m: u"expected-" + m.group(1) +
                                                    offsetify(offset, m.group(2)),
                                          comment_str)
    adjusted = expected_error_re.sub(lambda m: u"expected-remark" +
                                               offsetify(offset, m.group(1)),
                                     adjusted)
    adjusted = marked_objc_re.sub(u"marked @objc by an access note", adjusted)
    adjusted = fixit_re.sub(u"{{none}}", adjusted)

    return u"// [expectations adjusted] " + adjusted


#
# Writing attrs to access notes
#

def move_at_objc_to_access_note(access_notes_file, arg, offset, access_note_name):
    """Write an @objc attribute into an access notes file, then return the
       string that will replace the attribute and trailing comment."""

    access_notes_file.write(u"""
- Name: '{}'
  ObjC: true""".format(access_note_name))
    if arg:
        access_notes_file.write(u"""
  ObjCName: '{}'""".format(arg))

    # Default to shifting expected diagnostics down 1 line.
    if offset is None:
        offset = 1

    return u"// access-note-adjust" + offsetify(offset) + u" [attr moved] " + \
           u"expected-remark{{access note for fancy tests adds attribute 'objc' to " + \
           u"this }} expected-note{{add attribute explicitly to silence this warning}}"


#
# Matching lines
#

"""Matches '@objc(foo) // access-note-move{{access-note-name}}'
   or '@objc // access-note-move{{access-note-name}}'"""
access_note_move_re = re.compile(r'@objc(?:\(([\w:]+)\))?[ \t]*' +
                                 r'//[ \t]*access-note-move' +
                                 offset_re_fragment +
                                 r'\{\{([^}]*)\}\}')

"""Matches // access-note-adjust <comment>"""
adjust_comment_re = re.compile(r'//[ \t]*access-note-adjust' + offset_re_fragment +
                               r'(.*)')


def replacer(fn, *args):
    """Returns a lambda which calls fn with args, followed by the groups from
       the match passed to the lambda."""

    return lambda m: fn(*(args + m.groups()))


main()

#!/usr/bin/env python # coding=utf8 Convert selected @objc attributes in a source file into access notes, removing the originals in the process. # # Entry point # # Add header to access notes file \ Reason: 'fancy tests' Notes: # Loop over input lines, transforming them into output lines, writing access # notes as a side effect. # Look for access-note-move comments. # Look for access-note-adjust comments. # # Offsets # Matches an @±N offset. Sum line offsets matched by offset_re_fragment and convert them to strings        like @+3 or @-2. # # Adjusting comments # Matches expected-warning/note/remark and its offset. Matches expected-error and its offset. Matches the string 'marked @objc'. Matches any non-none fix-it expectation. Replace expected-errors with expected-remarks, and make other adjustments        to diagnostics so that they reflect access notes. # # Writing attrs to access notes # Write an @objc attribute into an access notes file, then return the        string that will replace the attribute and trailing comment. - Name: '{}'   ObjC: true ObjCName: '{}' # Default to shifting expected diagnostics down 1 line. # # Matching lines # Matches '@objc(foo) // access-note-move{{access-note-name}}'    or '@objc // access-note-move{{access-note-name}}' Matches // access-note-adjust <comment> Returns a lambda which calls fn with args, followed by the groups from        the match passed to the lambda.