-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontrol_freak.rb
executable file
·184 lines (159 loc) · 4.73 KB
/
control_freak.rb
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
require 'optparse'
# encoding: utf-8
class OptParse
Version = '0.2.6'
class Options
# Create options
attr_accessor :quiet, :output, :inplace, :dryrun
# Initialize options
def initialize
self.quiet = false
self.output = nil
self.inplace = nil
self.dryrun = nil
end
def define_options(parser)
# Help text
parser.banner = "Usage: ruby control_freak.rb [input file] [options]"
# Other options
quiet_mode_option(parser)
output_file_option(parser)
inplace_mode_option(parser)
dry_run_mode_option(parser)
parser.on("-h", "--help", "Show this help text") do
puts parser
exit
end
parser.on("-v", "--version", "Show version") do
puts Version
exit
end
end
def quiet_mode_option(parser)
parser.on("-q", "--quiet", "Suppresses removed character output to the terminal") do
self.quiet = true
end
end
def output_file_option(parser)
parser.on("-o", "--output OUTPUT", String, "The full or relative path of the output file") do |out|
self.output = out
end
end
def inplace_mode_option(parser)
parser.on("-i", "--in-place", "Writes the changes to the file, in place") do
self.inplace = true
end
end
def dry_run_mode_option(parser)
parser.on("-d", "--dry-run", "Run a dry run of the characters to be deleted") do
self.dryrun = true
end
end
end
# Create a structure that describes the options
def parse(args)
@options = Options.new
@args = OptionParser.new do |parser|
@options.define_options(parser)
begin
parser.parse!(args)
rescue OptionParser::InvalidOption => e
puts e.message
exit
end
end
@options
rescue OptionParser::MissingArgument => e
puts e.message
exit
end
attr_reader :parser, :options
end
class Cleaner
attr_accessor :options, :clean_file
def initialize(options)
@input = ARGV[0]
@quiet = options.quiet
@output = options.output
@inplace = options.inplace
@dryrun = options.dryrun
self.clean_file = nil
end
def input_file
File.open("#{@input}", "rt:utf-8")
rescue Errno::ENOENT => e
puts e.message
exit
end
def strip_control_characters
str = ''
# Iterates through lines of the input file
input_file.each_line.with_index(1) do |line, line_index|
# Finds escaped Unicode codepoints and interprets them as a single character
line = line.gsub(/\\u([0-9A-F]{4})/i){$1.hex.chr(Encoding::UTF_8)}
# Iterates through characters
line.each_char.with_index(1) do |char, char_index|
# Finds ASCII characters 0-31 and 127 (ordinal), except:
# 9 (horizontal tab, \t)
# 10 (line feed, \n)
# 13 (carriage return, \r)
if char.ascii_only? and (char.ord < 32 or char.ord == 127 and char.ord != 9 and char.ord != 10 and char.ord != 13)
# Puts line and character number to STDOUT if removed,
# unless -q flag is present
$stdout.puts "Line #{line_index}, char #{char_index}: " "\\u%.4x" % char.ord unless @quiet == true
else
str << char
end
end
end
str
# Exits on invalid encoding
rescue ArgumentError => e
puts e.message
exit
end
def write_to_new_file
File.open(@output, "wt:utf-8"){ |f| f.write(strip_control_characters) }
end
def write_in_place
cleaned_file = strip_control_characters
File.open(@input, "w+t:utf-8"){ |f| f.write(cleaned_file) }
end
def write_to_dry_run
strip_control_characters
end
end
# Create the option hash for the given argument
op = OptParse.new
options = op.parse(ARGV)
clean = Cleaner.new(options)
# Exits if no output option given
if !(options.output || options.inplace || options.dryrun)
m = "You must include one of the following options: output file (-o), in-place (-i), dry run (-d)"
# Exits if input and output files are the same
elsif ARGV[0] == options.output
m = "The input file and output file should be different. Are you looking for in-place mode (-i)?"
# Exits if multiple output options given
elsif [options.output, options.inplace, options.dryrun].compact.length > 1
m = "You cannot include more than one of the following options: output file (-o), in-place (-i), dry run (-d)"
# Exits if dry run and quiet options given
elsif options.quiet && options.dryrun
m = "Think about what you're doing."
end
# Error handler
if m
begin
raise OptionParser::InvalidOption
rescue OptionParser::InvalidOption => e
puts e.message + m
exit
end
end
# Checks output option
if options.dryrun
clean.write_to_dry_run
elsif options.output
clean.write_to_new_file
elsif options.inplace
clean.write_in_place
end