"""
Notepad++ Python Script script to add setters and getters to C++ classes
Copyright (C) 2013 George Snyder
This script is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version. The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.
"""
from Npp import notepad,console,editor
import re,pprint
# only deal with C++ files
if (re.match(".*\\.cpp$", notepad.getCurrentFilename())):
# parse the editor text into statements, blocks (including labeled sections)
blocks = []
structure = blocks
blockPath = [{'children':blocks, 'blockType':'root', 'text': '', 'start':0,'end':editor.getLength()}]
statementStart = 0
depth = 0
inComment = False
inQuote = False
quoteChar = ""
quoteStrings = ["'", '"']
for i in range(0, editor.getLength()):
line = editor.getLine(editor.lineFromPosition(i))
c = "%c" % editor.getCharAt(i)
if re.match('\\s*(//.*)?$', line): # line is a comment, skip it
i = editor.getLineEndPosition(editor.lineFromPosition(i))
elif c == '/' and editor.getCharAt(i+1) == '*':
inComment = True
elif inComment and editor.getTextRange(i, i+2) == "*/":
inComment = False
elif inComment == True:
continue
elif inQuote:
if c in quoteStrings:
inQuote = False
elif c in quoteStrings:
inQuote = True
quoteChar = c
elif c == ';':
tmp = {'blockType': 'statement', 'text': editor.getTextRange(statementStart, i).strip(), 'start': statementStart, 'end': i}
blocks.append(tmp)
statementStart = i+1
elif c == '{':
depth += 1
tmp = {'blockType': 'regular', 'text': editor.getTextRange(statementStart, i).strip(), 'children': [], 'start': statementStart, 'end': -1}
blocks.append(tmp)
blockPath.append(tmp)
blocks = tmp['children']
statementStart = i+1
elif c == '}':
if blockPath[len(blockPath)-1]['blockType'] == 'label':
terminated = 2
else:
terminated = 1
depth -= terminated
if depth < 0:
console.write("Invalid C++, too many closing curly braces at line %d\n" % editor.lineFromPosition(i))
break
for j in range(0, terminated):
blockPath.pop()['end'] = i
blocks = blockPath[len(blockPath)-1]['children']
statementStart = i+1
elif c == ':':
if blockPath[len(blockPath)-1]['blockType'] != 'label':
depth += 1
else:
blockPath.pop()['end'] = statementStart
blocks = blockPath[len(blockPath)-1]['children']
tmp = {'blockType': 'label', 'text': re.match('.*(?:\A|\W)([a-z]\w*):', line).group(1), 'children': [], 'start': statementStart, 'end': -1}
blocks.append(tmp)
blockPath.append(tmp)
blocks = tmp['children']
statementStart = i+1
if depth > 0:
console.write("Invalid C++, too many opening curly braces.\n")
# find the 'class' block that contains our cursor
currentPosition = editor.getCurrentPos()
classBlock = None
for block in structure:
if block['text'].startswith('class') and block['start'] < currentPosition and block['end'] > currentPosition:
classBlock = block
break
if classBlock == None:
console.write("You (position %d) don't appear to be inside a class, we don't know which class needs setters!\n" % currentPosition)
else:
# find the 'private' and 'public' 'block's in that block
privateBlock = None
publicBlock = None
for block in classBlock['children']:
if block['blockType'] == 'label':
if block['text'] == 'private':
privateBlock = block
if block['text'] == 'public':
publicBlock = block
# create a list of all private variable names
privateVars = []
if privateBlock != None:
# find indentation
indentation = re.match('.*[\n\r]*?([\t ]*)\w.*', editor.getTextRange(privateBlock['start'], privateBlock['end']), flags=re.MULTILINE).group(1)
indentBase = re.match('.*[\n\r]*?([\t ]*)\w.*', editor.getTextRange(privateBlock['children'][0]['start'], privateBlock['children'][0]['end']), flags=re.MULTILINE).group(1)
for i in range(0, len(privateBlock['children'])):
if privateBlock['children'][i]['blockType'] == 'statement':
m = re.match('\s*(?P<dataType>\w+)\s+(?P<varName>\w+)\s*', privateBlock['children'][i]['text'])
if m:
privateVars.append([m.group('dataType'), m.group('varName')])
else:
console.write("Could not find the private label")
# make a list of already created setters and getters
changes = {}
existingMethods = []
if publicBlock != None:
for i in range(0, len(publicBlock['children'])):
if publicBlock['children'][i]['blockType'] == 'regular':
m = re.match('\s*(?P<datatype>\w+)\s+(?P<methodName>(?P<type>set|get)(?P<varName>\w+))\(.*', publicBlock['children'][i]['text'])
if m:
existingMethods.append(m.group('datatype') + m.group('methodName'))
else:
console.write("Could not find the public label")
# prepend setters and getters that aren't already created
if publicBlock != None:
insertPoint = publicBlock['children'][0]['start']
changes[insertPoint] = ""
else:
insertPoint = privateBlock['end']
if len(privateVars) == 0:
changes[insertPoint] = "\r\npublic:" # add public label if it doesn't exist
for var in privateVars:
formatVars = {'base': indentBase, 'indent': indentation, 'capName': var[1].capitalize(), 'dType': var[0], 'vName': var[1]}
if not ("voidset%s" % (var[1].capitalize()) in existingMethods):
changes[insertPoint] += "\r\n%(base)svoid set%(capName)s(%(dType)s %(vName)s) {\r\n%(base)s%(indent)sthis->%(vName)s = %(vName)s;\r\n%(base)s}" % formatVars
console.write("Adding setter 'void set%(capName)s(%(dType)s)'\n" % formatVars)
for var in privateVars:
formatVars = {'base': indentBase, 'indent': indentation, 'capName': var[1].capitalize(), 'dType': var[0], 'vName': var[1]}
if not ("%sget%s" % (var[0], var[1].capitalize()) in existingMethods):
changes[insertPoint] += "\r\n%(base)s%(dType)s get%(capName)s() {\r\n%(base)s%(indent)sreturn %(vName)s\r\n%(base)s}" % formatVars
console.write("Adding getter '%(dType)s get%(capName)s()'\n" % formatVars)
# apply changes
changeOrder = changes.keys()
changeOrder.sort(reverse=True)
for key in changeOrder:
editor.insertText(key, changes[key])
else:
console.write("Currently only C++ is supported for adding setters and getters. Might work on other languages if you disable this check. Feel free to modify the script.\n")