Skip to content

Commit

Permalink
Initial stab case of filter_chain, a chaining filter system for text
Browse files Browse the repository at this point in the history
  • Loading branch information
raggi committed Oct 17, 2008
0 parents commit 9dabdbb
Show file tree
Hide file tree
Showing 26 changed files with 1,231 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
.DS_Store
tmp
pkg
4 changes: 4 additions & 0 deletions History.txt
@@ -0,0 +1,4 @@
== 0.1.0 / 2008-10-17

* 1 major enhancement
* Birthday!
25 changes: 25 additions & 0 deletions Manifest.txt
@@ -0,0 +1,25 @@
History.txt
Manifest.txt
README.txt
Rakefile
bin/filter_chain
lib/filter_chain.rb
lib/filter_chain/filters/all.rb
lib/filter_chain/filters/none.rb
spec/.bacon
spec/helper.rb
spec/runner
tasks/ann.rake
tasks/autospec.rake
tasks/bones.rake
tasks/gem.rake
tasks/git.rake
tasks/manifest.rake
tasks/notes.rake
tasks/post_load.rake
tasks/rdoc.rake
tasks/rubyforge.rake
tasks/setup.rb
tasks/spec.rake
tasks/svn.rake
tasks/test.rake
53 changes: 53 additions & 0 deletions README.txt
@@ -0,0 +1,53 @@
filter_chain
by James Tucker
http://raggi.org
http://github.com/raggi/filter_chain


--> EXPERIMENTAL!


== DESCRIPTION:

FIXME (describe your package)

== FEATURES/PROBLEMS:

* FIXME (list of features or problems)

== SYNOPSIS:

FIXME (code sample of usage)

== REQUIREMENTS:

* FIXME (list of requirements)

== INSTALL:

* FIXME (sudo gem install, anything else)

== LICENSE:

(The MIT License)

Copyright (c) 2008 James Tucker

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.
21 changes: 21 additions & 0 deletions Rakefile
@@ -0,0 +1,21 @@
# Look in the tasks/setup.rb file for the various options that can be
# configured in this Rakefile. The .rake files in the tasks directory
# are where the options are used.

load 'tasks/setup.rb'

ensure_in_path 'lib'
require 'filter_chain'

task :default => 'spec:run'

PROJ.name = 'filter_chain'
PROJ.authors = 'James Tucker'
PROJ.email = 'raggi@rubyforge.org'
PROJ.url = 'http://github.com/raggi/filter_chain'
# PROJ.rubyforge.name = 'filter_chain'

PROJ.exclude = %w(tmp$ bak$ ~$ CVS \.git \.hg \.svn ^pkg ^doc ^tmp \.DS_Store
\.cvs \.svn \.hgignore \.gitignore \.dotest \.swp$ ~$)

# EOF
70 changes: 70 additions & 0 deletions bin/filter_chain
@@ -0,0 +1,70 @@
#!/usr/bin/env ruby

$DEBUG = true if ARGV.delete('-d')

usage = <<-USAGE
Usage: #{$0}
-d : set debugging
-h : print this message
Filter arguments are to be given in tuples, and form an ordered chain:
tuple : description
----------------------------------------------------------------------
+ "z" : adds a positive filter matching 'z' (output lines with 'z')
- "r" : adds a negating filter matching 'r' (remove lines with 'r')
f foo : adds a pre-defined filter named foo
c foo : adds a pre-defined chain named foo
Manually defined filters (+|-) use the ruby regular expression engine.
Expression captures are printed if present, else the whole match is printed.
Negated matches are not output, and non-matching and not negated data is
printed.
USAGE

abort usage if ARGV.delete('-h') || ARGV.size % 2 != 0 || ARGV.empty?

begin
trap(:INT) { exit }

lib_path = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
$:.unshift lib_path unless $:.include? lib_path
require 'filter_chain'

chain = FilterChain::Chain.new

until ARGV.empty?
chain.add_filter case ARGV.shift
when '+'
FilterChain::Filter.new(%r(#{ARGV.shift}), true)
when '-'
FilterChain::Filter.new(%r(#{ARGV.shift}), false)
when 'f'
FilterChain.filter(ARGV.shift)
when 'c'
FilterChain.chain(ARGV.shift)
else
abort usage
end
end

until $stdin.eof?
line = $stdin.readline
case result = chain.match(line)
when MatchData
result.captures.empty? ? print(result.string) : puts(result.captures.join(', '))
when String
print result
end
end
rescue
if $DEBUG
abort $!.backtrace.join("\n\t")
else
abort $!.message
end
end
85 changes: 85 additions & 0 deletions lib/filter_chain.rb
@@ -0,0 +1,85 @@
module FilterChain
module Filters; end
module Chains; end

def self.filter(name)
class_name = name.split('_').map { |part| part.capitalize }.join
require "filter_chain/filters/#{name}"
Filters.const_get(class_name).new
rescue LoadError
raise "Could not load filter file: filter_chain/filters/#{name}"
rescue NameError
raise "Could not find filter class: FilterChain::Filters::#{class_name}"
end

def self.chain(name)
require "filter_chain/chains/#{name}"
Chains.const_get(name.capitalize).new
rescue LoadError
raise "Could not load chain file: filter_chain/chains/#{name}"
rescue NameError
raise "Could not find chain class: FilterChain::Chains::#{name.capitalize}"
end

class Filter
attr_accessor :rexp, :positive
def initialize(rexp = /^(.*)$/, positive = true)
@rexp = rexp
@positive = positive
end

def match(line)
if line =~ @rexp
@positive ? Regexp.last_match : nil
else
line
end
end
end

class Chain
attr_accessor :filters

def initialize
@filters = []
yield self if block_given?
end

def add_filter(filter, positive = true)
@filters ||= []
filter = case filter
when Regexp
filter = Filter.new(filter, positive)
when Filter
filter
when Chain
filter
end

@filters << filter
end

def pos(filter)
add_filter(filter, true)
end

def neg(filter)
add_filter(filter, false)
end

def match(line)
@filters.each do |filter|
case result = filter.match(line)
when line
next
when nil
return
else
return result
end
end
line
end
end

end
5 changes: 5 additions & 0 deletions lib/filter_chain/filters/all.rb
@@ -0,0 +1,5 @@
class FilterChain::Filters::All < FilterChain::Filter
def initialize
super(/(.*)/)
end
end
5 changes: 5 additions & 0 deletions lib/filter_chain/filters/none.rb
@@ -0,0 +1,5 @@
class FilterChain::Filters::None < FilterChain::Filter
def initialize
super(/(.*)/, false)
end
end
Empty file added spec/.bacon
Empty file.
16 changes: 16 additions & 0 deletions spec/helper.rb
@@ -0,0 +1,16 @@
# Disable test/unit and rspec from running, in case loaded by broken tools.
Test::Unit.run = false if defined?(Test::Unit)
Spec::run = false if defined?(Spec) && Spec::respond_to?(:run=)

# Setup a nice testing environment
$TESTING=true
$:.push File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
$:.uniq!

%w[rubygems facon amok].each { |r| require r }

# Bacon doesn't do any automagic, so lets tell it to!
Bacon.summary_on_exit

require File.expand_path(
File.join(File.dirname(__FILE__), %w[.. lib filter_chain]))
9 changes: 9 additions & 0 deletions spec/runner
@@ -0,0 +1,9 @@
#!/usr/bin/env ruby
__DIR__ = File.dirname(__FILE__)
__APP__ = File.expand_path(__DIR__ + '/../')

puts
Dir.chdir(__APP__) do
files = ARGV.empty? ? Dir.glob('{test,spec}/**/{test,spec}_*.rb') : ARGV
files.each { |f| require f }
end
81 changes: 81 additions & 0 deletions tasks/ann.rake
@@ -0,0 +1,81 @@
# $Id$

begin
require 'bones/smtp_tls'
rescue LoadError
require 'net/smtp'
end
require 'time'

namespace :ann do

# A prerequisites task that all other tasks depend upon
task :prereqs

file PROJ.ann.file do
ann = PROJ.ann
puts "Generating #{ann.file}"
File.open(ann.file,'w') do |fd|
fd.puts("#{PROJ.name} version #{PROJ.version}")
fd.puts(" by #{Array(PROJ.authors).first}") if PROJ.authors
fd.puts(" #{PROJ.url}") if PROJ.url.valid?
fd.puts(" (the \"#{PROJ.release_name}\" release)") if PROJ.release_name
fd.puts
fd.puts("== DESCRIPTION")
fd.puts
fd.puts(PROJ.description)
fd.puts
fd.puts(PROJ.changes.sub(%r/^.*$/, '== CHANGES'))
fd.puts
ann.paragraphs.each do |p|
fd.puts "== #{p.upcase}"
fd.puts
fd.puts paragraphs_of(PROJ.readme_file, p).join("\n\n")
fd.puts
end
fd.puts ann.text if ann.text
end
end

desc "Create an announcement file"
task :announcement => ['ann:prereqs', PROJ.ann.file]

desc "Send an email announcement"
task :email => ['ann:prereqs', PROJ.ann.file] do
ann = PROJ.ann
from = ann.email[:from] || PROJ.email
to = Array(ann.email[:to])

### build a mail header for RFC 822
rfc822msg = "From: #{from}\n"
rfc822msg << "To: #{to.join(',')}\n"
rfc822msg << "Subject: [ANN] #{PROJ.name} #{PROJ.version}"
rfc822msg << " (#{PROJ.release_name})" if PROJ.release_name
rfc822msg << "\n"
rfc822msg << "Date: #{Time.new.rfc822}\n"
rfc822msg << "Message-Id: "
rfc822msg << "<#{"%.8f" % Time.now.to_f}@#{ann.email[:domain]}>\n\n"
rfc822msg << File.read(ann.file)

params = [:server, :port, :domain, :acct, :passwd, :authtype].map do |key|
ann.email[key]
end

params[3] = PROJ.email if params[3].nil?

if params[4].nil?
STDOUT.write "Please enter your e-mail password (#{params[3]}): "
params[4] = STDIN.gets.chomp
end

### send email
Net::SMTP.start(*params) {|smtp| smtp.sendmail(rfc822msg, from, to)}
end
end # namespace :ann

desc 'Alias to ann:announcement'
task :ann => 'ann:announcement'

CLOBBER << PROJ.ann.file

# EOF

0 comments on commit 9dabdbb

Please sign in to comment.