Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ruby lightweight AOP
# AOP = Aspect-Oriented Programming
module AOP
class Error < StandardError
attr_accessor :flag
attr_accessor :origin_flag
attr_accessor :origin # store raw exception
end
CHAIN = {} # { name: [[:process_name , "process#method"] ... ] ... }
ERRORS = {
break_chain: "break and stop chain by process" ,
exception: "process raise exception" ,
empty: "chain processes is empty" ,
undefine: "process is undefine or no target process" ,
custom: "custom error , use `origin` to get source"
}
class ProxyObj
attr_accessor :chain_name
def initialize(chain_name)
self.chain_name = chain_name
end
def inspect
return "AOP::ProxyObj:#{self.chain_name}"
end
def method_missing(method_name , *argv)
AOP.send(method_name , self.chain_name , *argv)
end
end
def self.make_error(error_flag , exception_source = nil)
error = nil
if exception_source
warn(exception_source.message)
warn(exception_source.backtrace.join("\n"))
end
if ERRORS.has_key?(error_flag)
error = Error.new(ERRORS[error_flag])
error.flag = error_flag
else
error = Error.new(exception_source ? exception_source.message : "unknow err msg")
error.flag = :custom
end
error.origin_flag = error_flag
error.origin = exception_source
raise error
end
def self.new_chain(chain_name)
check_name_format(chain_name)
check_duplicate_chain(chain_name)
CHAIN[chain_name] = []
return ProxyObj.new(chain_name)
end
def self.<<(chain_name , source) # proxy to push()
raise "process format fail" unless source.is_a?(Array) && source.length != 3
self.push(chain_name , source[0] , source[1])
return ProxyObj.new(chain_name)
end
def self.push(chain_name , process_name , process_pointer)
check_name_format(chain_name , process_name)
check_pointer_format(process_pointer)
check_has_chain(chain_name)
check_duplicate_process_name_pointer(chain_name , process_name , process_pointer)
CHAIN[chain_name] << [process_name , process_pointer]
return ProxyObj.new(chain_name)
end
def self.push_before(chain_name , source_process_name , process_name , process_pointer)
index = get_chain_process_index(chain_name , source_process_name)
push_at_index(chain_name , source_process_name , process_name , process_pointer , index)
return ProxyObj.new(chain_name)
end
def self.push_after(chain_name , source_process_name , process_name , process_pointer)
index = get_chain_process_index(chain_name , source_process_name) + 1
push_at_index(chain_name , source_process_name , process_name , process_pointer , index)
return ProxyObj.new(chain_name)
end
def self.remove(chain_name , process_name)
check_name_format(chain_name , process_name)
check_has_process(chain_name , process_name)
CHAIN[chain_name].each_with_index do |process_obj , index|
if process_obj[0] == process_name
CHAIN[chain_name].delete_at(index)
break
end
end
return ProxyObj.new(chain_name)
end
def self.remove_chain(chain_name)
check_name_format(chain_name)
check_has_chain(chain_name)
CHAIN.delete(chain_name)
return ProxyObj.new(chain_name)
end
def self.clone(chain_name , target_chain_name)
#這邊使用 deep clone
check_name_format(chain_name , target_chain_name)
check_has_chain(chain_name)
raise "has target chain , cant clone" unless check_duplicate_chain(target_chain_name , false)
CHAIN[target_chain_name] = Marshal.load(Marshal.dump(CHAIN[chain_name]))
return ProxyObj.create(target_chain_name)
end
def self.clear!
CHAIN.clear
end
def self.print_all_chain(is_to_s = false)
temp = []
CHAIN.each_key do |chain_name|
if is_to_s
temp << "====#{chain_name}"
temp << print_chain(chain_name , is_to_s)
else
puts "====#{chain_name}"
puts print_chain(chain_name , is_to_s)
end
end
return temp.join("\n") if is_to_s
return nil
end
def self.inspect
return print_all_chain(true)
end
def self.to_s
return print_all_chain(true)
end
def self.print_chain(chain_name , is_to_s = false)
check_name_format(chain_name)
check_has_chain(chain_name)
temp = []
CHAIN[chain_name].each do |process_obj|
message = "\t#{chain_name} : #{process_obj[0]} => #{process_obj[1]}"
if is_to_s
temp << message
else
puts message
end
end
return temp.join("\n") if is_to_s
return ProxyObj.new(chain_name)
end
def self.process(chain_name)
check_name_format(chain_name)
check_has_chain(chain_name)
step_ans = {}
before_process = [:init , "init"]
chain = CHAIN[chain_name]
make_error(:empty) if chain.empty?
chain.each do |process_obj|
klass , method_name = process_obj[1].split("#")
begin
make_error(:break_chain) if step_ans.has_key?(:is_break_aop)
begin
klass = Module.const_get(klass)
rescue NameError
make_error(:undefine)
end
make_error(:undefine) unless klass.respond_to?(method_name)
step_ans.clear if step_ans.has_key?(:is_clean_up_aop) # clear all data by prev process
step_ans[:before_process] = before_process
begin
step_ans = klass.send(method_name , step_ans)
rescue StandardError => exp
make_error(:exception , exp)
end
rescue Exception => exp
raise exp if exp.is_a?(Error)
make_error(:custom , exp)
end
before_process = process_obj
end
return ProxyObj.new(chain_name)
end
private
def self.push_at_index(chain_name , source_process_name , process_name , process_pointer , index)
check_name_format(process_name)
check_pointer_format(process_pointer)
CHAIN[chain_name].insert(
index ,
[process_name , process_pointer]
)
end
def self.get_chain_process_index(chain_name , process_name)
check_name_format(chain_name , process_name)
check_has_process(chain_name , process_name)
CHAIN[chain_name].each_with_index do |process_obj , index|
return index if process_obj[0] == process_name
end
raise "no chain process : #{chain_name},#{process_name}"
end
def self.check_duplicate_chain(chain_name , is_raise = true)
if CHAIN.has_key?(chain_name)
raise "chain name duplicate : #{chain_name}" if is_raise
return false
end
return true
end
def self.check_duplicate_process_name_pointer(chain_name , process_name , process_pointer)
CHAIN[chain_name].each do |process_obj|
raise "duplicate chain process : #{chain_name},#{process_name}" if process_obj[0] == process_name
if process_obj[1] == process_pointer
warn("warning : duplicate chain process pointer : #{chain_name},#{process_name},#{process_pointer}")
end
end
end
def self.check_name_format(*argv)
argv.each do |name|
raise "chain name need symbol : #{name}(#{name.class})" unless name.is_a?(Symbol)
end
end
def self.check_pointer_format(pointer)
raise "pointer need string : #{pointer}(#{pointer.class})" unless pointer.is_a?(String)
raise "pointer format need like `String#to_s` : #{pointer}" unless pointer.match(/^[A-Za-z\d]+#[A-Za-z\d_]+$/) # String#to_s
end
def self.check_has_chain(chain_name)
raise "chain is not exists : #{chain_name}" unless CHAIN.has_key?(chain_name)
end
def self.check_has_process(chain_name , process_name)
check_has_chain(chain_name)
CHAIN[chain_name].each do |process_obj|
return true if process_obj[0] == process_name
end
raise "chain process is not exists : #{chain_name}"
end
end
module AOPtester
def self.proc_add_time(source)
source[:time] = Time.now
return source
end
def self.proc_add_rand(source)
source[:rand] = rand(65535)
return source
end
def self.proc_show_time_rand(source)
puts "#{source[:time].strftime("%Y-%m-%d %H:%M:%S")} , #{source[:rand]}"
return source
end
def self.proc_clear(source)
source[:is_clean_up_aop] = true
return source
end
def self.proc_check_clear(source)
puts "clear : #{source}"
return source
end
def self.proc_rand_break(source)
if rand > 0.8
source[:is_break_aop] = true
puts "die QwQ"
else
source[:survive_counter] ||= 0
source[:survive_counter] += 1
puts "im survive : #{source[:survive_counter]}"
end
return source
end
def self.test
AOP.clear!
# `<<` == `push()`
AOP.new_chain(:chain_a) <<
[:a1 , "AOPtester#proc_add_time" ] <<
[:a2 , "AOPtester#proc_add_rand" ] <<
[:a3 , "AOPtester#proc_show_time_rand"] <<
[:a4 , "AOPtester#proc_clear" ] <<
[:a5 , "AOPtester#proc_check_clear" ]
AOP.new_chain(:chain_b) <<
[:b1 , "AOPtester#proc_add_time" ] <<
[:b2 , "AOPtester#proc_add_rand" ] <<
[:b3 , "AOPtester#proc_rand_break" ] << #random exception
[:b4 , "AOPtester#proc_rand_break" ] <<
[:b5 , "AOPtester#proc_rand_break" ] <<
[:b6 , "AOPtester#proc_show_time_rand"]
# chain_c = chain_a
AOP.new_chain(:chain_c
).push( :c3 , "AOPtester#proc_show_time_rand"
).push_before(:c3 , :c2 , "AOPtester#proc_add_rand"
).push_after( :c3 , :c4 , "AOPtester#proc_clear"
).push( :c5 , "AOPtester#proc_check_clear"
).push_before(:c2 , :c1 , "AOPtester#proc_add_time"
)
puts AOP
AOP.process(:chain_a)
begin
AOP.process(:chain_b)
rescue AOP::Error => e
puts "====pass exception : #{e.message}"
end
AOP.process(:chain_c)
#make empty error
begin
AOP.new_chain(:chain_d).process
rescue AOP::Error => e
puts "====pass exception : #{e.message}"
end
end
end
AOPtester.test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment