Last active
October 20, 2018 06:09
-
-
Save JokerCatz/d93f9dab0e178df2aebb43e33e7a766e to your computer and use it in GitHub Desktop.
Ruby lightweight AOP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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