Skip to content

Commit

Permalink
Add selective field printing
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan Burrows committed Jun 30, 2010
1 parent a3b257b commit 668473a
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 10 deletions.
4 changes: 2 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ begin

namespace :cucumber do
Cucumber::Rake::Task.new(:ok) do |t|
t.cucumber_opts = "--format progress -t ~@wip -b"
t.cucumber_opts = "--format progress -t ~@wip -P"
end

Cucumber::Rake::Task.new(:wip) do |t|
t.cucumber_opts = "--format pretty -t @wip"
t.cucumber_opts = "--format pretty -t @wip -P"
end
end

Expand Down
1 change: 1 addition & 0 deletions cucumber.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default: --format progress -t ~@wip
44 changes: 44 additions & 0 deletions features/print_fields.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Feature: jse prints specific fields from the json

In order to only show relevant information
As a jse user
I want to only output specific json fields

Background:
Given I have a log file containing:
"""
{"id":1,"level":"INFO","message":"line one"}
{"id":2,"level":"DEBUG","message":"line two"}
{"id":3,"level":"ERROR","message":"number three"}
{"id":4,"level":"INFO","message":"line four"}
"""

Scenario: Printing one field returns just the value
When I run "jse --fields=message" on my log file
Then I should see:
"""
line one
line two
number three
line four
"""

Scenario: Printing multiple fields returns JSON
When I run "jse --fields=message,level" on my log file
Then I should see:
"""
{"level":"INFO","message":"line one"}
{"level":"DEBUG","message":"line two"}
{"level":"ERROR","message":"number three"}
{"level":"INFO","message":"line four"}
"""

Scenario: Fields can be specified with a shorter '-f' flag
When I run "jse -f message,level" on my log file
Then I should see:
"""
{"level":"INFO","message":"line one"}
{"level":"DEBUG","message":"line two"}
{"level":"ERROR","message":"number three"}
{"level":"INFO","message":"line four"}
"""
3 changes: 3 additions & 0 deletions lib/jse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
require 'jse/stream'
require 'jse/filter'
require 'jse/regexp_filter'
require 'jse/subset_transform'
require 'jse/to_json_transform'
require 'jse/field_value_transform'

module JSE
end
12 changes: 9 additions & 3 deletions lib/jse/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module JSE
module CLI
def self.execute(stdout, arguments = [])
print = []
parser = OptionParser.new do |opts|
opts.banner = <<-BANNER.gsub(/^[ \t]*/, '')
Json Stream Editor.
Expand All @@ -12,9 +13,10 @@ def self.execute(stdout, arguments = [])
Options are:
BANNER
opts.separator ""
# opts.on("-f", "--fields a,b,c", Array,
# "List of fields to return") do |fields|
# end
opts.on("-f", "--fields a,b,c", Array,
"List of fields to return") do |fields|
print = fields
end
opts.on("-h", "--help",
"Show this help message.") { stdout.puts opts; exit }
opts.parse!(arguments)
Expand All @@ -31,6 +33,10 @@ def self.execute(stdout, arguments = [])
stream.filter!(field, text)
end

unless print.empty?
stream.print!(*print)
end

stream.each_line do |line|
stdout.puts line
end
Expand Down
11 changes: 11 additions & 0 deletions lib/jse/field_value_transform.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module JSE
class FieldValueTransform
def initialize(field)
@field = field
end

def apply(hash)
hash[@field]
end
end
end
39 changes: 37 additions & 2 deletions lib/jse/stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

module JSE
class Stream
attr_accessor :filters
attr_accessor :filters, :transforms
attr_reader :inner

def initialize(inner)
@inner = inner
@transforms = []
@filters = []
end

def each_line
inner.each_line do |line|
unless filter_chain.eliminates?(line)
yield line
yield transform_chain.apply(line)
end
end
end
Expand All @@ -26,12 +27,46 @@ def filter!(field, text)
end
end

def print!(*field_list)
if field_list.size == 1
transforms << FieldValueTransform.new(field_list[0])
else
transforms << SubsetTransform.new(*field_list)
transforms << ToJsonTransform.new
end
end

private

def looks_like_regexp?(string)
string[0,1] == '/' && string[string.size-1,1] == '/'
end

def transform_chain
TransformChain.new(transforms)
end

class TransformChain
def initialize(transforms)
@transforms = transforms
end

def apply(line)
begin
if @transforms.empty?
line
else
json = JSON.parse(line)
@transforms.inject(json){ |j, transform| transform.apply(j) }
end
rescue JSON::ParserError
$stderr.puts "Error parsing line:"
$stderr.puts line
true
end
end
end

def filter_chain
FilterChain.new(filters)
end
Expand Down
14 changes: 14 additions & 0 deletions lib/jse/subset_transform.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module JSE
class SubsetTransform
def initialize(*fields)
@fields = fields
end

def apply(json)
@fields.inject({}) do |result, field|
result[field] = json[field]
result
end
end
end
end
7 changes: 7 additions & 0 deletions lib/jse/to_json_transform.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module JSE
class ToJsonTransform
def apply(obj)
obj.to_json
end
end
end
75 changes: 72 additions & 3 deletions spec/jse/stream_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ def match?(n)
end
end

class ValueUpcaseTransform
def apply(json)
updated = json.dup
updated['value'] = json['value'].upcase
updated
end
end

class ValueDoubleTransform
def apply(json)
updated = json.dup
updated['value'] = json['value'] + json['value']
updated
end
end

let(:inner){ mock("Inner Stream") }
let(:stream){ Stream.new(inner) }

Expand Down Expand Up @@ -66,26 +82,79 @@ def match?(n)
end
results.should == ["{\"value\":4}"]
end

it "yeilds every line if there are no filters" do
inner.stub(:each_line).and_yield("{\"value\":1}").
and_yield("{\"value\":2}")
stream.filters = []
results = []
stream.each_line do |l|
results << l
end
results.should == ["{\"value\":1}", "{\"value\":2}"]
end

it "runs all transforms on the lines" do
inner.stub(:each_line).and_yield("{\"value\":\"a\"}").
and_yield("{\"value\":\"b\"}").
and_yield("{\"value\":\"c\"}")
stream.transforms = [ValueUpcaseTransform.new,
ValueDoubleTransform.new,
ToJsonTransform.new]
results = []
stream.each_line do |l|
results << l
end
results.should == ["{\"value\":\"AA\"}",
"{\"value\":\"BB\"}",
"{\"value\":\"CC\"}"]
end
end

describe "#filter!" do
it "adds a new filter to the list" do
before do
stream.filters = []
end

it "adds a new filter to the list" do
stream.filter!('field', 'text')
stream.filters.size.should == 1
end

it "adds a normal filter if it looks like a string" do
stream.filters = []
stream.filter!('field', 'text')
stream.filters.first.class.should == JSE::Filter
end

it "adds a regexp filter if it looks like a regexp" do
stream.filters = []
stream.filter!('field', '/^regexp$/')
stream.filters.first.class.should == JSE::RegexpFilter
end
end

describe "#print!" do
before do
stream.transforms = []
end

context "only on field is specified" do
it "adds on transform" do
stream.print!('field')
stream.transforms.size.should == 1
end
end

context "multiple fields are specified" do
it "adds two transforms" do
stream.print!('field', 'other')
stream.transforms.size.should == 2
end

it "adds a to_json_transform on the end" do
stream.print!('field', 'other')
stream.transforms.last.class.should == ToJsonTransform
end
end
end
end
end
14 changes: 14 additions & 0 deletions spec/jse/subset_transform_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
require 'spec_helper'

module JSE
describe SubsetTransform do
describe "#apply" do
it "returns the set of fields as a json object" do
transform = SubsetTransform.new('name', 'value')
json = JSON.parse("{\"name\":\"n\", \"value\":\"v\", \"id\":1}")
transform.apply(json).to_json.should ==
"{\"name\":\"n\",\"value\":\"v\"}"
end
end
end
end

0 comments on commit 668473a

Please sign in to comment.