├── .gitignore ├── Rakefile ├── discoball.gemspec ├── LICENSE.txt ├── bin └── discoball ├── README.md └── lib └── discoball.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "remove built gems" 2 | task :clean do 3 | sh "rm discoball-*" rescue true 4 | end 5 | 6 | desc "build gem" 7 | task :build do 8 | sh "gem build discoball.gemspec" 9 | end 10 | 11 | desc "install gem" 12 | task :install => [:clean, :build] do 13 | sh "gem install `ls discoball-*`" 14 | end 15 | -------------------------------------------------------------------------------- /discoball.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "discoball" 3 | s.version = "0.2.3" 4 | 5 | s.required_rubygems_version = Gem::Requirement.new(">=0") if s.respond_to? :required_rubygems_version= 6 | s.specification_version = 2 if s.respond_to? :specification_version= 7 | 8 | s.author = "Caleb Spare" 9 | s.email = "cespare@gmail.com" 10 | 11 | s.description = "A simple stream filter to highlight patterns" 12 | s.summary = "A simple stream filter to highlight patterns" 13 | s.homepage = "http://github.com/cespare/discoball" 14 | s.rubyforge_project = "discoball" 15 | s.license = "MIT" 16 | 17 | s.executables = %w(discoball) 18 | s.files = %w( 19 | README.md 20 | discoball.gemspec 21 | lib/discoball.rb 22 | bin/discoball 23 | ) 24 | s.add_dependency("colorize", ">=0.5.8") 25 | s.add_dependency("trollop", ">=1.16.2") 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Caleb Spare 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /bin/discoball: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "discoball" 4 | require "trollop" 5 | 6 | options = Trollop::options do 7 | banner <<-EOS 8 | discoball is a tool for highlighting patterns in a text stream. 9 | 10 | Usage: 11 | $ discoball [options] 12 | where options are: 13 | EOS 14 | opt :group_colors, "Color all matches of the same pattern with the same color", :default => false 15 | opt :one_color, "Highlight all matches with a single color", :default => false 16 | opt :match_any, "Only print lines matching an input pattern", :default => false 17 | opt :match_all, "Only print lines matching all input patterns", :default => false 18 | end 19 | 20 | color_mode_array = [:group_colors, :one_color].select { |option| options[option] } 21 | if color_mode_array.size > 1 22 | Trollop::die "Only one of --group-colors or --one-color may be set" 23 | end 24 | if color_mode_array.empty? 25 | color_mode = :individual 26 | else 27 | color_mode = color_mode_array[0] 28 | end 29 | 30 | match_mode_array = [:match_any, :match_all].select { |option| options[option] } 31 | if match_mode_array.size > 1 32 | Trollop::die "Only one of --match-any or --match-all may be set" 33 | end 34 | if match_mode_array.empty? 35 | match_mode = :all 36 | else 37 | match_mode = match_mode_array[0] 38 | end 39 | 40 | patterns = ARGV.map { |pattern| Regexp.new(pattern) } 41 | highlighter = Discoball::Highlighter.new(patterns, color_mode, match_mode) 42 | STDIN.each do |line| 43 | filtered = highlighter.filter(line) 44 | puts filtered unless filtered.nil? 45 | end 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE: I neither use nor maintain this project anymore.** 2 | 3 | discoball 4 | ========= 5 | 6 | `discoball` is a tool to filter streams and colorize patterns. It functions somewhat like `egrep --color`, 7 | except that it can highlight multiple patterns (in different colors). Patterns are arbitrary ruby regexes that 8 | are matched against the entire line. If the regex contains groups, only the first group's match text is 9 | highlighted. 10 | 11 | Usage 12 | ----- 13 | 14 | $ discoball [options] 15 | 16 | where options are: 17 | 18 | * `--group-colors` or `-g`: Color all matches of the same pattern with the same color 19 | * `--one-color` or `-o`: Highlight all matches with a single color 20 | * `--match-any` or `-m`: Only print lines matching an input pattern 21 | * `--match-all` or `-a`: Only print lines matching all input patterns 22 | * `--help` or `-h`: Print the help message 23 | 24 | Examples 25 | -------- 26 | 27 | * Highlight instances of "foo" and "bar" in the text of `myfile.txt`: 28 | 29 | $ cat myfile.txt | discoball foo bar 30 | 31 | * Highlight paths of processes running out of `/usr/sbin/`: 32 | 33 | $ ps -ef | discoball --one-color --match-any '/usr/sbin/.*$' 34 | 35 | * I wrote discoball for use with [Steve Losh's todo-list tool, t](https://github.com/sjl/t). I put tags on 36 | my tasks annotated with `+` (inspired by [Todo.txt](http://todotxt.com/)): 37 | 38 | $ t Make an appointment with the dentist +health 39 | 40 | When I list my tasks (using `t`), I use discoball to highlight the tags with different colors: 41 | 42 | $ t | discoball '\+\S+' 43 | 44 | I can even do some fancier stuff to list particular labels. I have the following function defined in my 45 | `.bashrc`: 46 | 47 | ``` bash 48 | function tl() { 49 | if [ -z "$1" ]; then 50 | t | discoball '\+\S+' 51 | else 52 | t | discoball -a "${@/#/\+}" 53 | fi 54 | } 55 | ``` 56 | 57 | I can use this as follows: 58 | 59 | $ tl # ~> Show the list of tasks, with tags highlighted 60 | $ tl health urgent # ~> Show only tasks tagged with 'health' and 'urgent' 61 | 62 | Demo: 63 | 64 | ![Discoball + t demo](http://i.imgur.com/tVQMm.png) 65 | 66 | Installation 67 | ------------ 68 | 69 | The easiest way to get `discoball` is by using RubyGems: `$ gem install discoball`. You can also clone the git 70 | repository at `git://github.com/cespare/discoball.git` if you want the latest code. 71 | -------------------------------------------------------------------------------- /lib/discoball.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "rubygems" 4 | require "colorize" 5 | 6 | module Discoball 7 | UNUSABLE_COLORS = [/black$/, /^default$/, /white$/, /^light/] 8 | SINGLE_COLOR = :blue 9 | 10 | class Highlighter 11 | # Patterns is an array of patterns to match. There are two options that can be changed: 12 | # * color_mode 13 | # -:individual - each unique string matching one of the patterns will be a different color. 14 | # -:group_colors - the matches corresponding to each pattern will be the same color. 15 | # -:one_color - all matches will be a single color. 16 | # * match_mode 17 | # -:all - all lines are returned 18 | # -:match_any - lines matching any pattern are returned 19 | # -:match_all - only lines matching every pattern are returned 20 | def initialize(patterns, color_mode = :individual, match_mode = :all) 21 | @patterns = patterns 22 | @color_mode = color_mode 23 | @match_mode = match_mode 24 | @color_stack = String.colors.reject { |color| UNUSABLE_COLORS.any? { |unusable| color =~ unusable } } 25 | @color_assignments = {} 26 | if color_mode == :group_colors 27 | @patterns.each { |pattern| @color_assignments[pattern] = pop_rotate } 28 | end 29 | end 30 | 31 | def filter(line) 32 | match_found = {} 33 | @patterns.each { |pattern| match_found[pattern] = false } 34 | 35 | case @color_mode 36 | when :one_color 37 | matches = @patterns.reduce([]) { |memo, pattern| # No Array#flat_map in Ruby 1.8 :\ 38 | m = line.scan(pattern).map { |match| match.is_a?(Array) ? match.first : match } 39 | match_found[pattern] = true unless m.empty? 40 | memo += m 41 | }.uniq 42 | matches.each { |match| highlight!(line, match, SINGLE_COLOR) } 43 | when :group_colors 44 | @patterns.each do |pattern| 45 | matches = line.scan(pattern).map { |match| match.is_a?(Array) ? match.first : match }.uniq 46 | match_found[pattern] = true unless matches.empty? 47 | matches.each { |match| highlight!(line, match, @color_assignments[pattern]) } 48 | end 49 | when :individual 50 | matches = @patterns.reduce([]) { |memo, pattern| 51 | m = line.scan(pattern).map { |match| match.is_a?(Array) ? match.first : match } 52 | match_found[pattern] = true unless m.empty? 53 | memo += m 54 | }.uniq 55 | matches.each do |match| 56 | unless @color_assignments.include? match 57 | @color_assignments[match] = pop_rotate 58 | end 59 | matches.each { |match| highlight!(line, match, @color_assignments[match]) } 60 | end 61 | end 62 | 63 | case @match_mode 64 | when :match_any 65 | match_found.any? { |pattern, found| found } ? line : nil 66 | when :match_all 67 | match_found.all? { |pattern, found| found } ? line : nil 68 | else 69 | line 70 | end 71 | end 72 | 73 | private 74 | 75 | def highlight!(line, match, color) 76 | line.gsub!(match, match.colorize(color).underline) 77 | end 78 | 79 | # Get the next color and put it at the back 80 | def pop_rotate 81 | color = @color_stack.pop 82 | @color_stack.unshift color 83 | color 84 | end 85 | end 86 | end 87 | --------------------------------------------------------------------------------