Initial commit
This commit is contained in:
commit
f2f0a14b87
194
python/jsh.py
Normal file
194
python/jsh.py
Normal file
@ -0,0 +1,194 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
class ComplexEncoder(json.JSONEncoder):
|
||||
"""Special JSON encoder that detects objects that implement the
|
||||
JSONSerializable interface and invokes their to_json method."""
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, JSONSerializable):
|
||||
return obj.to_json()
|
||||
# Let the base class default method raise the TypeError
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
class JSONSerializable(object):
|
||||
"""Interface for JSON-serializable classes"""
|
||||
|
||||
def to_json(self):
|
||||
raise NotImplementedError("Must implement 'to_json'")
|
||||
|
||||
|
||||
class Counter(object):
|
||||
"""Wrapper for a count to be shared among substreams"""
|
||||
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
|
||||
def increment(self):
|
||||
self.count += 1
|
||||
|
||||
def clear(self):
|
||||
self.count = 0
|
||||
|
||||
|
||||
class Stream(JSONSerializable):
|
||||
"""A Stream manages printing serialized objects in a fixed-number
|
||||
of chunks. Streams can have substreams that are keyed on a special
|
||||
name. Substreams share a global counter for the number of items
|
||||
that are printed out at once."""
|
||||
|
||||
def __init__(self, out, buf_size=10, counter=None, root=None):
|
||||
if not counter:
|
||||
counter = Counter()
|
||||
if not root:
|
||||
root = self
|
||||
self.buf_size = buf_size
|
||||
self.out = out
|
||||
self.counter = counter
|
||||
self.root = root
|
||||
self.data = {}
|
||||
self.substreams = {}
|
||||
self.first = True
|
||||
|
||||
def get_start(self):
|
||||
return '['
|
||||
|
||||
def start(self):
|
||||
"""Marks the start of the Stream
|
||||
|
||||
:param should_print: Whether it should write to self.out
|
||||
:return: A value to be printed"""
|
||||
self.out.write(self.get_start())
|
||||
|
||||
def flush(self):
|
||||
"""Serializes the current contents of the stream for output and
|
||||
recursively flushes everything.
|
||||
|
||||
Does nothing if it's not the root stream.
|
||||
|
||||
:param should_print: Whether it should write to self.out
|
||||
:return: A value to be printed"""
|
||||
if self.root == self:
|
||||
self.out.write(self.to_json())
|
||||
self.clear()
|
||||
self.out.flush()
|
||||
|
||||
def get_stop(self):
|
||||
return ']'
|
||||
|
||||
def stop(self):
|
||||
"""Marks the start of the Stream
|
||||
|
||||
:param should_print: Whether it should write to self.out
|
||||
:return: A value to be printed"""
|
||||
self.flush()
|
||||
self.out.write(self.get_stop())
|
||||
|
||||
def clear(self):
|
||||
"""Clears the shared counter and recursively clears all
|
||||
substreams."""
|
||||
self.counter.clear()
|
||||
self.data = {}
|
||||
for substream in self.substreams.values():
|
||||
substream.clear()
|
||||
|
||||
def output(self, stream_name, obj):
|
||||
"""Stores an object in the buffer. If the number of things
|
||||
stored exceeds the global buffer size, automatically flush
|
||||
the streams.
|
||||
|
||||
:param stream_name: The key under which to store the stream
|
||||
:param obj: The object"""
|
||||
if stream_name not in self.data:
|
||||
self.data[stream_name] = []
|
||||
self.data[stream_name].append(obj)
|
||||
self.counter.increment()
|
||||
if self.counter.count >= self.buf_size:
|
||||
self.root.flush()
|
||||
|
||||
def new_stream(self, name):
|
||||
"""Creates a substream with the same 'out' and 'buf_size'.
|
||||
Copies the references to the counter and root node to the
|
||||
substream, and stores a reference in the substreams dict. Also
|
||||
invokes the "start" method without any side-effect printing.
|
||||
"""
|
||||
s = Stream(self.out, self.buf_size)
|
||||
s.counter = self.counter
|
||||
s.root = self.root
|
||||
self.substreams[name] = s
|
||||
return s
|
||||
|
||||
def is_empty(self):
|
||||
"""Determine whether this substream and all substreams are
|
||||
empty.
|
||||
|
||||
:return: Whether this stream is empty"""
|
||||
empty = self.data == {}
|
||||
for substream in self.substreams.values():
|
||||
empty = empty and substream.is_empty()
|
||||
return empty
|
||||
|
||||
def add_comma_if_not_first(self, s):
|
||||
"""Adds a comma to the provided list 's' if it is not the first
|
||||
thing to be printed. Otherwise, we set the flag.
|
||||
|
||||
:param s: The list in which to add the comma"""
|
||||
if self.first:
|
||||
self.first = False
|
||||
else:
|
||||
s.append(',')
|
||||
|
||||
def to_json(self):
|
||||
"""Converts the stream to JSON. Does not include the opening
|
||||
and closing square brackets. If printing, you should include
|
||||
those first.
|
||||
|
||||
:return: JSON representation of the stream."""
|
||||
s = []
|
||||
# serialize streams
|
||||
if self.data:
|
||||
self.add_comma_if_not_first(s)
|
||||
s.append(json.dumps(self.data, cls=ComplexEncoder))
|
||||
# recursively serialize substreams
|
||||
for key, stream in self.substreams.items():
|
||||
if stream.is_empty():
|
||||
continue
|
||||
self.add_comma_if_not_first(s)
|
||||
s.append('{{"{}":'.format(key))
|
||||
s.append(stream.get_start())
|
||||
s.append(stream.to_json())
|
||||
s.append(stream.get_stop())
|
||||
s.append("}")
|
||||
return ''.join(s)
|
||||
|
||||
def __str__(self):
|
||||
return self.to_json()
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
def main():
|
||||
s = Stream(sys.stdout, 4)
|
||||
s.start()
|
||||
|
||||
for i in range(7):
|
||||
proc = {"pid": i + 1, "name": "init"}
|
||||
s.output("things", proc)
|
||||
q = s.new_stream('q')
|
||||
q.output("test", 'potato')
|
||||
|
||||
for i in range(10):
|
||||
proc = {"pid": i + 1, "name": "init"}
|
||||
s.output("processes", proc)
|
||||
|
||||
q.output("test", 'salad')
|
||||
q.output("test", 'rocks')
|
||||
s.flush()
|
||||
s.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
145
ruby/jsh.rb
Normal file
145
ruby/jsh.rb
Normal file
@ -0,0 +1,145 @@
|
||||
require 'json'
|
||||
|
||||
class Counter
|
||||
def initialize
|
||||
@count = 0
|
||||
end
|
||||
|
||||
def increment
|
||||
@count += 1
|
||||
end
|
||||
|
||||
def clear
|
||||
@count = 0
|
||||
end
|
||||
|
||||
def count
|
||||
@count
|
||||
end
|
||||
end
|
||||
|
||||
class Stream
|
||||
def initialize(out, buf_size = 10, counter = nil, root = self)
|
||||
counter = Counter.new if counter.nil?
|
||||
@buf_size = buf_size
|
||||
@out = out
|
||||
@counter = counter
|
||||
@root = root
|
||||
@data = {}
|
||||
@substreams = {}
|
||||
@first = true
|
||||
end
|
||||
|
||||
def start
|
||||
'['
|
||||
end
|
||||
|
||||
def start!
|
||||
@out.write(start)
|
||||
end
|
||||
|
||||
def flush
|
||||
if @root == self
|
||||
r = to_json
|
||||
clear
|
||||
@out.write(r)
|
||||
@out.flush
|
||||
return r
|
||||
end
|
||||
end
|
||||
|
||||
def stop
|
||||
']'
|
||||
end
|
||||
|
||||
def stop!
|
||||
flush
|
||||
@out.write(stop)
|
||||
end
|
||||
|
||||
def clear
|
||||
@counter.clear
|
||||
@data = {}
|
||||
@substreams.values.each do | substream |
|
||||
substream.clear
|
||||
end
|
||||
end
|
||||
|
||||
def output(stream_name, obj)
|
||||
if not @data.keys.include? stream_name
|
||||
@data[stream_name] = []
|
||||
end
|
||||
@data[stream_name].push(obj)
|
||||
@counter.increment
|
||||
if @counter.count >= @buf_size
|
||||
@root.flush
|
||||
end
|
||||
end
|
||||
|
||||
def new_stream(name)
|
||||
s = Stream.new(@out, @buf_size, @counter, @root)
|
||||
@substreams[name] = s
|
||||
s
|
||||
end
|
||||
|
||||
def empty?
|
||||
empty = @data == {}
|
||||
@substreams.values.each do |substream|
|
||||
empty = empty and substream.empty?
|
||||
end
|
||||
empty
|
||||
end
|
||||
|
||||
def add_comma_if_not_first(s)
|
||||
if @first
|
||||
@first = false
|
||||
else
|
||||
s.push(',')
|
||||
end
|
||||
end
|
||||
|
||||
def to_json
|
||||
s = []
|
||||
if not @data.empty?
|
||||
add_comma_if_not_first(s)
|
||||
s.push(@data.to_json)
|
||||
end
|
||||
@substreams.each do |key, stream|
|
||||
if not stream.empty?
|
||||
add_comma_if_not_first(s)
|
||||
s.push("{#{key}:")
|
||||
s.push(stream.start)
|
||||
s.push(stream.to_json)
|
||||
s.push(stream.stop)
|
||||
s.push('}')
|
||||
end
|
||||
end
|
||||
s.join ''
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def main
|
||||
s = Stream.new $stdout, 4
|
||||
s.start!
|
||||
7.times do |i|
|
||||
proc = {:pid => i + 1, :name => "init"}
|
||||
s.output(:things, proc)
|
||||
end
|
||||
q = s.new_stream 'q'
|
||||
q.output(:test, 'potato')
|
||||
|
||||
10.times do |i|
|
||||
proc = {:pid => i + 1, :name => "init"}
|
||||
s.output(:processes, proc)
|
||||
end
|
||||
|
||||
q.output(:test, "salad")
|
||||
q.output(:test, "rocks")
|
||||
|
||||
s.flush
|
||||
s.stop!
|
||||
end
|
||||
|
||||
|
||||
main
|
Reference in New Issue
Block a user