I've been fiddling around with some programs, and since most of them are for fun, I haven't bothered with little things like making the output non-verbose until I, say, put it here, for review. I was idly wondering how I could make converting it easier, and naturally I thought of writing a program, because that's all I ever do. You could, of course, use a logging library and set the debug level, but that results in having lots of extra code everywhere that's never touched in production. With this, you can keep all that code, tag it, and when it's sent off to production
Here's how it works:
Read a line from standard input. If any state variables are nonzero, apply the action, decrement the variable, and move to the next line. If the line looks like a tag, set the appropriate state variable and go to the next line. If the line looks like it ends in a tag, apply the action from the tag to that line and go to the next. If it's still trying to figure out what to do, give up and output the line as-is.
debug_converter.rb
#USAGE: <input source> | ruby debug_converter.rb <comment symbol> > <output file>
# Hurr hurr I'm funny
#DEBUG[2] - comment
#print 'Enter the comment separator: '
#ARGV = gets.split
DEFAULT_ACTION = 'delete'
raise "Wrong number of args: #{ARGV.inspect}" if ARGV.length != 1
COMMENT_SYMBOL = ARGV[0]
deleting = 0
commenting = 0
uncommenting = 0
until (line = STDIN.gets).nil?
line.chomp!
if deleting != 0
deleting -= 1
elsif commenting != 0
commenting -= 1
puts line.gsub(/^(\s*)/, "\\1#{COMMENT_SYMBOL}")
elsif uncommenting != 0
uncommenting -= 1
puts line.gsub(/^(\s*)#{COMMENT_SYMBOL}/, '\1')
else
#Helps to separate it conceptually from the bit that depends on the variables
case line
when /^[ \t]*#{COMMENT_SYMBOL}\s*DEBUG(?:\[(\d+)\])?\s*(?:-\s*([a-z]+))?/i
line.strip!
count, action = (Integer($1) rescue nil), $2 # For readability purposes.
count ||= 1
action ||= DEFAULT_ACTION
raise "Invalid count: #{count}" if count == 0
case action
when 'delete' then deleting = count.to_i
when 'comment' then commenting = count.to_i
when 'uncomment' then uncommenting = count.to_i
else raise "'#{action}' is an invalid action!"
end
when /(.*)#{COMMENT_SYMBOL}\s*DEBUG(?:\s*-\s*([a-z]+))?\s*$/i
line, action = $1, $2 # For readability
action ||= DEFAULT_ACTION
case action
when 'delete' then ; #Do nothing
when 'comment' then puts line.gsub(/^(\s*)/, "\\1#{COMMENT_SYMBOL}")
when 'uncomment' then puts line.gsub(/^(\s*)#{COMMENT_SYMBOL}/, '\1')
else raise "'#{action}' is an invalid action!"
end
else
puts line
end
end
end
Now, say we call it like this:
test_file.txt | ruby debug_converter.rb # > test_file.prod.txt
If test_file.txt
is this:
NORMAL TEXT: normal text normal text with spaces normal text with tabs VERBOSE FORMS: #DEBUG[3] - comment comment no indent comment with spaces comment with tab #DEBUG[2] - delete should be deleted should also be deleted #DEBUG[2] - uncomment #should be uncommented #should also be uncommented IMPLICATIONS: #DEBUG - comment prevline implied 1 explicit comment #DEBUG - delete prevline implied 1 explicit delete #DEBUG - uncomment prevline implied 1 explicit uncomment #DEBUG prevline implied 1 implied delete INLINES: inline comment #DEBUG - comment inline comment with spaces #DEBUG - comment inline delete #DEBUG - delete inline implied delete #DEBUG #inline uncomment #DEBUG - uncomment
then test_file.prod.txt
will be this:
NORMAL TEXT: normal text normal text with spaces normal text with tabs VERBOSE FORMS: #comment no indent #comment with spaces #comment with tab should be uncommented should also be uncommented IMPLICATIONS: #prevline implied 1 explicit comment prevline implied 1 explicit uncomment INLINES: #inline comment #inline comment with spaces inline uncomment
Right now, I'm specifically looking for advice to do with:
- Performance, with regards to both speed and memory usage. I'm fairly certain that this is about as good as it's gonna get (Ω(n) and Ω(1) respectively, for n=line count), but anything to make it faster is appreciated.
- Usability. Note that this is meant to be used in, say, a Bash script, so it should be able to take input and push output from anywhere directly, without having to write to the filesystem and read it; that's why I'm taking from
STDIN
and giving toSTDOUT
instead of reading and writing to files supplied as arguments. - Extensibility. How can I make it easy to add new functions? Note that the one-line and multi-line functions, while they have the same name, act differently, so merging them isn't an option. I feel like I could turn
deleting
,commenting
, anduncommenting
into a Hash, but I can't figure out how it'd work, since each has to take different actions. Maybe I'm just tired. - Edge cases. I'm bad at figuring them out. Any help finding them all is appreciated.