Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
zerebubuth committed Feb 19, 2012
0 parents commit a677cdd
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 0 deletions.
25 changes: 25 additions & 0 deletions actions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
def mkstruct(*args)
klass = Struct.new(*args)
klass.class_eval do
def to_s
self.class.to_s + "[" + each_pair.collect {|k,v| "#{k}=#{v.inspect}"}.join(",") + "]"
end
def inspect
self.to_s
end
def pretty_inspect
self.to_s
end
def pretty_print_inspect
self.to_s
end
def pretty_print(io)
io.text(self.to_s)
end
end
return klass
end

Delete = mkstruct(:klass, :element_id)
Redact = mkstruct(:klass, :element_id, :version, :mode)
Edit = mkstruct(:obj)
104 changes: 104 additions & 0 deletions change_bot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
require './osm'
require './db'
require './actions'

class History
def initialize(versions)
# todo: ensure sort
@versions = versions
@tainted_keys = Array.new
@tainted_values = Hash.new
@cleans = Array.new
end

def each_version
@versions.each do |v|
yield v
end
end

def merge_clean_onto_clean(obj)
@cleans << true
end

def merge_clean_onto_dirty(obj)
@cleans << true
end

def merge_dirty_onto_clean(obj)
@cleans << false
end

def merge_dirty_onto_dirty(obj)
@cleans << false
end

def is_clean?
@cleans.all?
end

def actions
acts = Array.new
clean_flag = true
prev_obj = nil
max_version = nil
@versions.zip(@cleans).each do |obj,clean|
clean_flag = clean_flag && clean
unless clean_flag
if acts.empty?
if prev_obj.nil?
acts << Delete[obj.class, obj.element_id]
else
acts << Edit[prev_obj]
end
end
acts << Redact[obj.class, obj.element_id, obj.version, clean ? :visible : :hidden]
end
prev_obj = obj
max_version = obj.version
end
# need to adjust any edit actions to represent changes from
# the last version of the object we've seen.
if (not acts.empty?) and (acts[0].class == Edit)
acts[0] = acts[0].clone
acts[0].obj.changeset_id = -1
acts[0].obj.version = max_version
end
acts
end
end

class ChangeBot
def initialize(db)
@db = db
@pending_deletes = Array.new
end

def action_for(history)
h = History.new(history)
h.each_version do |element|
if changeset_is_accepted?(element.changeset_id)
if h.is_clean?
h.merge_clean_onto_clean(element)
else
h.merge_clean_onto_dirty(element)
end
else
if h.is_clean?
h.merge_dirty_onto_clean(element)
else
h.merge_dirty_onto_dirty(element)
end
end
end
h.actions
end

def changeset_is_accepted?(changeset_id)
cs = @db.changeset(changeset_id)
accepted = cs.user.accepted_cts?
accepted = accepted or (not cs.user.adopter.nil? and cs.user.adopter.accepted_cts?)
accepted = accepted or cs.override_accepted?
return accepted
end
end
17 changes: 17 additions & 0 deletions changeset.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Changeset
attr_accessor :user

def self.[](user, override_accepted = false)
Changeset.new(user, override_accepted)
end

def override_accepted?
@override_accepted
end

private
def initialize(user, override_accepted)
@user = user
@override_accepted = override_accepted
end
end
11 changes: 11 additions & 0 deletions db.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require './osm'

class DB
def initialize(changesets)
@changesets = changesets
end

def changeset(cs_id)
@changesets[cs_id]
end
end
67 changes: 67 additions & 0 deletions osm.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module OSM

module Taggable
attr_accessor :tags
private
def init_tags(tags)
@tags = tags
end
end

module Element
attr_accessor :element_id, :changeset_id, :timestamp, :visible, :version
private
def init_attrs(attrs)
@element_id = attrs[:id]
@changeset_id = attrs[:changeset]
@timestamp = attrs[:timestamp]
@visible = attrs[:visible] || false
@version = attrs[:version]
end

def self.parse_options(options)
groups = options.group_by {|k, v| k.instance_of? String and v.instance_of? String}
tags = groups[true].nil? ? Hash.new : Hash[groups[true]]
attrs = groups[false].nil? ? Hash.new : Hash[groups[false]]
return tags, attrs
end

def compare(o, arg, *args)
cmp = self.send(arg) <=> o.send(arg)
if cmp != 0 or args.empty?
cmp
else
compare(o, *args)
end
end
end

class Node
include Taggable, Element, Comparable

attr_accessor :position

def self.[](pos, options = {})
tags, attrs = Element.parse_options(options)
Node.new(attrs, pos, tags)
end

def <=>(o)
compare(o, :position, :tags, :element_id, :changeset_id, :timestamp, :visible, :version)
end

def to_s
self.class.to_s + "[" +
@position.inspect + "," +
[:element_id, :changeset_id, :timestamp, :visible, :version].collect {|attr| "#{attr.inspect}=>#{self.send(attr)}"}.join(",") + "," +
@tags.to_a.collect {|k,v| "#{k.inspect}=>#{v.inspect}"}.join(",") + "]"
end

private
def initialize(attrs, pos, tags)
@position = pos
init_attrs(attrs)
init_tags(tags)
end
end
end
87 changes: 87 additions & 0 deletions test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
require './change_bot'
require './user'
require './changeset'
require './db'
require './actions'
require 'test/unit'

class TestChangeBox < Test::Unit::TestCase
def setup
@db = DB.new(1 => Changeset[User[true]],
2 => Changeset[User[true]],
3 => Changeset[User[false]])
end

# if a node has been edited only by people who have agreed then
# it should be clean.
def test_simple_node_clean
history = [OSM::Node[[0,0], :changeset => 1],
OSM::Node[[0,0], :changeset => 2]]
bot = ChangeBot.new(@db)
actions = bot.action_for(history)
assert_equal([], actions)
end

# if a node has been created by a person who hasn't agreed then
# it should be deleted and the one version redacted.
def test_simple_node_unclean
history = [OSM::Node[[0,0], :id => 1, :changeset => 3, :version => 1]]
bot = ChangeBot.new(@db)
actions = bot.action_for(history)
assert_equal([Delete[OSM::Node, 1], Redact[OSM::Node, 1, 1, :hidden]], actions)
end

# if a node has been created by a person who hasn't agreed and
# edited by another who hasn't agreed then it should be deleted
# and all the versions redacted.
def test_simple_node_unclean_multiple_edit
history = [OSM::Node[[0,0], :id => 1, :changeset => 3, :version => 1],
OSM::Node[[0,0], :id => 1, :changeset => 3, :version => 2]]
bot = ChangeBot.new(@db)
actions = bot.action_for(history)
assert_equal([Delete[OSM::Node, 1], Redact[OSM::Node, 1, 1, :hidden], Redact[OSM::Node, 1, 2, :hidden]], actions)
end

# if a node has been created by a person who hasn't agreed and
# edited by someone who has agreed then it should be deleted
# and all the versions redacted, but the version by the person
# who agreed should be redacted as "visible".
def test_simple_node_unclean_edited_clean_later
history = [OSM::Node[[0,0], :id => 1, :changeset => 3, :version => 1],
OSM::Node[[0,0], :id => 1, :changeset => 1, :version => 2]]
bot = ChangeBot.new(@db)
actions = bot.action_for(history)
assert_equal([Delete[OSM::Node, 1], Redact[OSM::Node, 1, 1, :hidden], Redact[OSM::Node, 1, 2, :visible]], actions)
end

# if a node has been created by a person who has agreed, with
# some tags, and then a person who hasn't agreed edits those
# tags then it should be edited to revert to the previous
# version of that node and the non-agreeing edit should be
# redacted.
def test_simple_node_clean_edited_unclean_later
history = [OSM::Node[[0,0], :id => 1, :changeset => 1, :version => 1, "foo" => "bar"],
OSM::Node[[0,0], :id => 1, :changeset => 3, :version => 2, "foo" => "blah"]]
bot = ChangeBot.new(@db)
actions = bot.action_for(history)
assert_equal([Edit[OSM::Node[[0,0], :id => 1, :changeset => -1, :version => 2, "foo" => "bar"]],
Redact[OSM::Node, 1, 2, :hidden]
], actions)
end

# same as above, but there's a subsequent clean edit which adds
# a new tag to the element. this extra tag isn't tainted in any
# way by the previous edit, so should be preserved and the extra
# edit redacted 'visible'.
def test_simple_node_clean_edited_unclean_later_then_clean_again
history = [OSM::Node[[0,0], :id => 1, :changeset => 1, :version => 1, "foo" => "bar"],
OSM::Node[[0,0], :id => 1, :changeset => 3, :version => 2, "foo" => "blah"],
OSM::Node[[0,0], :id => 1, :changeset => 2, :version => 3, "foo" => "blah", "bar" => "blah"]]
bot = ChangeBot.new(@db)
actions = bot.action_for(history)
assert_equal([Edit[OSM::Node[[0,0], :id => 1, :changeset => -1, :version => 3, "foo" => "bar", "bar" => "blah"]],
Redact[OSM::Node, 1, 2, :hidden],
Redact[OSM::Node, 1, 3, :visible]
], actions)
end
end
17 changes: 17 additions & 0 deletions user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class User
attr_accessor :adopter

def self.[](accepted_cts, adopter = nil)
User.new(accepted_cts, adopter)
end

def accepted_cts?
@accepted_cts
end

private
def initialize(accepted_cts, adopter)
@accepted_cts = accepted_cts
@adopter = adopter
end
end

0 comments on commit a677cdd

Please sign in to comment.