Last active
December 4, 2015 18:46
-
-
Save rtweeks/11346958 to your computer and use it in GitHub Desktop.
Reformat Visual Basic 6 (VB6) code to use consistent indentation and line widths.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# The MIT License (MIT) | |
# | |
# Copyright (c) 2014 Richard T. Weeks | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
# Reformat Visual Basic 6 or VBScript code to use consistent indentation and | |
# line widths. Main class is VB::Sanitizer. | |
require 'optparse' | |
require 'ostruct' | |
require 'stringio' | |
module VB | |
TOKEN_MATCHER = /^( | |
\s+ | # Whitespace | |
<% | %> | # ASP processing tag | |
[-+*\/\\^&=,.<>()_]+ | # Operator | |
"([^"]|"")*" | # String literal | |
\#[^\#]*\# | # Date-time literal | |
'.* | # Comment | |
(\d+(.\d*)?|.\d+)(E\d+)?[D@R\#I%L&SF!]? | # Number | |
[A-Z_][A-Z0-9_]* # Identifier | |
)/ixo | |
def self.parse_options(argv) | |
options = OpenStruct.new | |
optparser = OptionParser.new do |flags| | |
flags.banner = [ | |
"Usage: #{File.basename($0)} [<options>]" | |
].join("\n") | |
flags.separator '' | |
options.indent = ' ' | |
flags.on('-sN', '--indent-size=N', "Set indent size (spaces)") do |n| | |
options.indent = ' ' * n.to_i | |
end | |
flags.on('--tab-indent', "Use a tab for each level of indentation") do | |
options.indent = "\t" | |
end | |
options.dos_newlines = false | |
flags.on('-d', '--dos-newlines', "Use DOS-style CRLF newlines") do | |
options.dos_newlines = true | |
end | |
flags.on('--inplace', "Write output back to input file") do | |
options.inplace = true | |
end | |
end | |
argv = optparser.parse(argv) | |
return argv, options | |
end | |
def self.tokenize(line) | |
remainder = line | |
tokens = [] | |
until remainder.empty? | |
if remainder =~ TOKEN_MATCHER | |
tokens << $& | |
remainder = remainder[$&.length..-1] | |
else | |
tokens << remainder | |
break | |
end | |
end | |
return tokens | |
rescue | |
$stderr.puts "Error while tokenizing line: " + line | |
$stderr.puts " (unparsed segment: #{remainder})" | |
raise | |
end | |
def self.statement_block_role(tokens) | |
tokens = tokens.dup | |
# Ignore whitespace | |
tokens.reject! {|t| t =~ /^\s+$/} | |
# Ignore trailing comments | |
if /^'/ === tokens[-1] | |
tokens.pop | |
end | |
tokens.map! {|t| t.downcase} | |
return :start if %w[begin while do for with class].include? tokens[0] | |
if tokens[0] == 'if' | |
last_token = tokens[-1] | |
last_token = tokens[-2] if last_token.start_with? "'" | |
return :start if last_token == 'then' | |
return :statement | |
end | |
return :start if tokens[0..1] == ['select', 'case'] | |
if tokens[0..2].any? {|t| %w[function sub property].include? t} | |
i = 0 | |
i += 1 if %w[private public friend].include? tokens[i] | |
i += 1 if 'static' == tokens[i] | |
return :start if %w[function sub property].include? tokens[i] | |
end | |
return :separator if %w[elseif else case].include? tokens[0] | |
return :end if %w[end wend next].include? tokens[0] | |
return :statement | |
end | |
class Sanitizer | |
def initialize(**options) | |
@indent = options[:indent] || ' ' | |
@gutter = options[:gutter] || 90 | |
end | |
def sanitize(lines, &blk) | |
return enum_for(:sanitize, lines) unless block_given? | |
indent_level = 0 | |
previous_line_empty = false | |
continued_tokens = [] | |
lines.each do |l| | |
tokens, continued_tokens = continued_tokens + VB.tokenize(l.chomp), [] | |
# Drop any leading whitespace (we'll handle all indentation) | |
if /^\s*$/ === tokens[0] | |
tokens.shift | |
end | |
if tokens.empty? | |
next if previous_line_empty | |
previous_line_empty = true | |
else | |
previous_line_empty = false | |
end | |
if tokens[-1] == '_' | |
continued_tokens = tokens[0..-2] | |
next | |
end | |
case VB.statement_block_role(tokens) | |
when :start | |
output(tokens, indent_level, &blk) | |
indent_level += 1 | |
when :separator | |
indent_level -= 1 | |
output(tokens, indent_level, &blk) | |
indent_level += 1 | |
when :end | |
indent_level -= 1 | |
output(tokens, indent_level, &blk) | |
else | |
output(tokens, indent_level, &blk) | |
end | |
end | |
end | |
def output(tokens, indent_level) | |
line = (@indent * indent_level) | |
split_point = nil | |
tokens.each do |tok| | |
if split_point.nil? or /^('|\s+$)/ === tok or (line.length + tok.length <= @gutter) | |
line += tok | |
else | |
yield(line[0...split_point] + " _") | |
line = (@indent * (indent_level + 2)) + line[split_point..-1].lstrip + tok | |
split_point = nil | |
end | |
if tok =~ /^(and|or|&|\+)$/io | |
# Mark this spot for line split | |
split_point = line.length | |
end | |
end | |
yield(line) if tokens.empty? or not line.strip.empty? | |
end | |
end | |
end | |
if $0 == __FILE__ | |
argv, options = VB::parse_options(ARGV) | |
tool = VB::Sanitizer.new(indent: options.indent) | |
newline = options.dos_newlines ? "\r\n" : "\n" | |
out = options.inplace ? StringIO.new : $stdout | |
open argv[0], 'r' do |input| | |
tool.sanitize input.each_line do |line| | |
out.print(line + newline) | |
end | |
end | |
if options.inplace | |
open argv[0], 'w' do |outfile| | |
outfile.write(out.string) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment