diff options
| -rw-r--r-- | data/actions.txt | 32 | ||||
| -rw-r--r-- | lib/action_file_executor.rb | 38 | ||||
| -rw-r--r-- | lib/family_tree.rb | 207 | ||||
| -rw-r--r-- | spec/action_file_executor_spec.rb | 20 | ||||
| -rw-r--r-- | spec/family_tree_spec.rb | 75 |
5 files changed, 263 insertions, 109 deletions
diff --git a/data/actions.txt b/data/actions.txt index 8988068..97941d0 100644 --- a/data/actions.txt +++ b/data/actions.txt @@ -1,4 +1,21 @@ -GET_RELATIONSHIP "Queen Margaret" "Mother" + + +GET_RELATIONSHIP "Hugo" "Paternal-Uncle" +GET_RELATIONSHIP "Hugo" "Paternal-Aunt" +GET_RELATIONSHIP "Albus" "Maternal-Uncle" +GET_RELATIONSHIP "Remus" "Maternal-Aunt" + +GET_RELATIONSHIP "Ted" "Sister-in-Law" +GET_RELATIONSHIP "Percy" "Sister-in-Law" +GET_RELATIONSHIP "Charlie" "Sister-in-Law" + + +GET_RELATIONSHIP "Percy" "Brother-in-Law" +GET_RELATIONSHIP "Charlie" "Brother-in-Law" + +GET_RELATIONSHIP "Ted" "Brother-in-Law" + + GET_RELATIONSHIP "King Arthur" "Mother" GET_RELATIONSHIP "Bill" "Mother" GET_RELATIONSHIP "Charlie" "Mother" @@ -32,4 +49,15 @@ GET_RELATIONSHIP "Ginny" "Mother" GET_RELATIONSHIP "Molly" "Father" -GET_RELATIONSHIP "Bill" "Siblings"
\ No newline at end of file +GET_RELATIONSHIP "Bill" "Siblings" +ADD_CHILD "Victoire" "Fred" "Male" +ADD_CHILD "Ted" "Fred" "Male" +# +GET_RELATIONSHIP "Alice" "Mother" + + +ADD_CHILD "Victoire" "Fred" "Male" +ADD_CHILD "Ted" "Fred" "Male" + + +GET_RELATIONSHIP "King Arthur" "Daughter"
\ No newline at end of file diff --git a/lib/action_file_executor.rb b/lib/action_file_executor.rb index 49cb4b5..d8e583c 100644 --- a/lib/action_file_executor.rb +++ b/lib/action_file_executor.rb @@ -5,31 +5,31 @@ require_relative 'family_tree' class ActionFileExecutor def initialize(file_path) @file_path = file_path - validate_file + validate_file! end def execute_actions - File.open(@file_path, 'r') do |file| - file.each_line do |line| - process_line(line.strip) - end + File.foreach(@file_path) do |line| + process_line(line.strip) end end private - def validate_file + def validate_file! return if File.exist?(@file_path) - puts "Error: The file '#{@file_path}' does not exist." - exit 1 + abort("Error: The file '#{@file_path}' does not exist.") end def process_line(line) return if line.empty? || comment?(line) action, params = extract_action_and_params(line) - execute_action(action, params) if action + + return unless action && valid_action?(action, params) + + execute_action(action, params) end def comment?(line) @@ -42,26 +42,24 @@ class ActionFileExecutor action = match[1] params = match[2].scan(/"([^"]+)"|(\S+)/).flatten.compact + params.map! { |param| param&.strip } [action, params] end def execute_action(action, params) case action when 'ADD_CHILD' - handle_add_child(*params) + puts FamilyTree.instance.add_child(*params) when 'GET_RELATIONSHIP' - handle_get_relationship(*params) - else - puts "Ignoring unsupported action: [#{action}]" + puts FamilyTree.instance.get_relationship(*params) end end - def handle_add_child(*params) - FamilyTree.instance.add_child(*params) - end - - def handle_get_relationship(*params) - result = FamilyTree.instance.get_relationship(*params) - puts result.empty? ? 'NONE' : result.map(&:name).join(', ') + def valid_action?(action, params) + case action + when 'ADD_CHILD' then params.size == 3 + when 'GET_RELATIONSHIP' then params.size == 2 + else false # Ignore unsupported actions + end end end diff --git a/lib/family_tree.rb b/lib/family_tree.rb index 66af748..09f367f 100644 --- a/lib/family_tree.rb +++ b/lib/family_tree.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'singleton' - require_relative 'person' require_relative 'family_factory' @@ -18,53 +17,205 @@ class FamilyTree @families << family unless @families.include?(family) end - def add_child(*params) - puts "Adding Child with params: #{params.join(', ')}" + def add_child(mothers_name, name, gender) + result = find_person_in_families(mothers_name) + parent_of_family = result[:parent_of_family] + + return 'CHILD_ADDITION_FAILED' if parent_of_family.nil? || parent_of_family.mother.is_a?(NilPerson) + + return 'CHILD_ADDITION_FAILED' if parent_of_family.children.any? { |child| child.name.casecmp(name).zero? } + + child = Person.new(name, gender) + parent_of_family.add_child(child) + 'CHILD_ADDED' end def get_relationship(name, relationship) - result = find_person_and_family(name) + result = find_person_in_families(name) person = result[:person] - family = result[:family] + parent_of_family = result[:parent_of_family] + child_of_family = result[:child_of_family] - return 'PERSON_NOT_FOUND' if family.nil? || person.is_a?(NilPerson) + return 'PERSON_NOT_FOUND' if person.is_a?(NilPerson) case relationship.downcase - when 'mother' - mother = find_mother(family) - return 'PERSON_NOT_FOUND' if mother.is_a?(NilPerson) + when 'mother', 'father' + handle_parent_relationship(child_of_family, relationship) + when 'siblings' + handle_siblings_relationship(child_of_family, name) + when 'child', 'daughter', 'son' + handle_children_relationship(parent_of_family, relationship) + when 'paternal-uncle' + handle_uncle_relationship(child_of_family, 'paternal') + when 'maternal-uncle' + handle_uncle_relationship(child_of_family, 'maternal') + when 'paternal-aunt' + handle_aunt_relationship(child_of_family, 'paternal') + when 'maternal-aunt' + handle_aunt_relationship(child_of_family, 'maternal') + when 'sister-in-law' + handle_sister_in_law_relationship(person, child_of_family) + when 'brother-in-law' + handle_brother_in_law_relationship(person, child_of_family) + else + 'UNSUPPORTED_RELATIONSHIP' + end + end - mother.name - when 'father' - father = find_father(family) - return 'PERSON_NOT_FOUND' if father.is_a?(NilPerson) + private - father.name - when 'siblings' - siblings = find_siblings(family, name) - siblings.empty? ? 'NONE' : siblings.map(&:name).join(' ') + def handle_parent_relationship(child_of_family, relationship) + parent = relationship == 'mother' ? child_of_family&.mother : child_of_family&.father + return 'PERSON_NOT_FOUND' if parent.nil? || parent.is_a?(NilPerson) + + parent.name + end + + def handle_siblings_relationship(child_of_family, name) + siblings = find_siblings(child_of_family, name) + siblings.empty? ? 'NONE' : siblings.map(&:name).join(' ') + end + + def handle_children_relationship(parent_of_family, relationship) + return 'NONE' if parent_of_family.nil? + + children = parent_of_family.children + + case relationship.downcase + when 'child' + children.map(&:name).join(' ') + when 'son' + sons = children.select { |child| child.gender == Gender::MALE } + sons.empty? ? 'NONE' : sons.map(&:name).join(' ') + when 'daughter' + daughters = children.select { |child| child.gender == Gender::FEMALE } + daughters.empty? ? 'NONE' : daughters.map(&:name).join(' ') else 'UNSUPPORTED_RELATIONSHIP' end end - def find_person_and_family(name) - families.each do |family| - family.children.each do |child| - return { person: child, family: family } if child.name.casecmp(name).zero? + def handle_uncle_relationship(child_of_family, type) + return 'NONE' if child_of_family.nil? + + parent = type == 'paternal' ? child_of_family.father : child_of_family.mother + return 'NONE' if parent.nil? || parent.is_a?(NilPerson) + + # Find the family where the parent is a child + parent_result = find_person_in_families(parent.name) + parent_family = parent_result[:child_of_family] + + return 'NONE' if parent_family.nil? + + # Find the siblings of the parent + siblings = find_siblings(parent_family, parent.name) + + # Select male siblings (uncles) + uncles = siblings.select { |sibling| sibling.gender == Gender::MALE } + uncles.empty? ? 'NONE' : uncles.map(&:name).join(' ') + end + + def handle_aunt_relationship(child_of_family, type) + return 'NONE' if child_of_family.nil? + + parent = type == 'paternal' ? child_of_family.father : child_of_family.mother + return 'NONE' if parent.nil? || parent.is_a?(NilPerson) + + # Find the family where the parent is a child + parent_result = find_person_in_families(parent.name) + parent_family = parent_result[:child_of_family] + + return 'NONE' if parent_family.nil? + + # Find the siblings of the parent + siblings = find_siblings(parent_family, parent.name) + + # Select female siblings (aunts) + aunts = siblings.select { |sibling| sibling.gender == Gender::FEMALE } + aunts.empty? ? 'NONE' : aunts.map(&:name).join(' ') + end + + def handle_sister_in_law_relationship(person, child_of_family) + sisters_in_law = [] + + # Check for female siblings of the spouse + if person.spouse.is_a?(Person) + spouse_result = find_person_in_families(person.spouse.name) + spouse_family = spouse_result[:child_of_family] + + if spouse_family + spouse_siblings = find_siblings(spouse_family, person.spouse.name) + sisters_in_law.concat(spouse_siblings.select { |sibling| sibling.gender == Gender::FEMALE }.map(&:name)) end end - { person: NilPerson.new, family: nil } + + unless child_of_family.nil? + # Check for female spouses of siblings of the person + siblings = find_siblings(child_of_family, person.name) + + siblings.each do |sibling| + sisters_in_law << sibling.spouse.name if sibling.spouse && sibling.spouse.gender == Gender::FEMALE + end + end + + sisters_in_law.uniq! + sisters_in_law.empty? ? 'NONE' : sisters_in_law.join(' ') end - def find_mother(family) - mother = family.mother - mother || NilPerson.new + def handle_brother_in_law_relationship(person, child_of_family) + brothers_in_law = [] + + # Check for male siblings of the spouse + if person.spouse.is_a?(Person) + spouse_result = find_person_in_families(person.spouse.name) + spouse_family = spouse_result[:child_of_family] + + if spouse_family + spouse_siblings = find_siblings(spouse_family, person.spouse.name) + brothers_in_law.concat(spouse_siblings.select { |sibling| sibling.gender == Gender::MALE }.map(&:name)) + end + end + + unless child_of_family.nil? + # Check for male spouses of siblings of the person + siblings = find_siblings(child_of_family, person.name) + + siblings.each do |sibling| + brothers_in_law << sibling.spouse.name if sibling.spouse && sibling.spouse.gender == Gender::MALE + end + end + + brothers_in_law.uniq! + brothers_in_law.empty? ? 'NONE' : brothers_in_law.join(' ') end - def find_father(family) - father = family.father - father || NilPerson.new + def find_person_in_families(name) + result = { person: NilPerson.new, parent_of_family: nil, child_of_family: nil } + + families.each do |family| + # Check for mother + if family.mother&.name&.casecmp(name)&.zero? + result[:person] = family.mother + result[:parent_of_family] = family + end + + # Check for father + if family.father&.name&.casecmp(name)&.zero? + result[:person] = family.father + result[:parent_of_family] = family + end + + family.children.each do |child| + next if child.nil? || child.name.nil? + + if child.name.casecmp(name).zero? && family != result[:parent_of_family] + result[:person] = child + result[:child_of_family] = family + end + end + end + + result end def find_siblings(family, name) diff --git a/spec/action_file_executor_spec.rb b/spec/action_file_executor_spec.rb index ce2fa11..1d97f6b 100644 --- a/spec/action_file_executor_spec.rb +++ b/spec/action_file_executor_spec.rb @@ -31,7 +31,7 @@ RSpec.describe ActionFileExecutor do it 'prints an error message and exits' do expect do ActionFileExecutor.new(invalid_file_path) - end.to output("Error: The file 'non_existent_file.txt' does not exist.\n").to_stdout.and raise_error(SystemExit) + end.to output("Error: The file 'non_existent_file.txt' does not exist.\n").to_stderr.and raise_error(SystemExit) end end end @@ -39,11 +39,11 @@ RSpec.describe ActionFileExecutor do describe '#execute_actions' do context 'with a valid file' do before do - tempfile.write("ADD_CHILD \"Mother's Name\" \"Child's Name\"\n") + tempfile.write("ADD_CHILD \"Mother's Name\" \"Child's Name\" Male\n") tempfile.write("# A comment\n") tempfile.write("\n") tempfile.write("INVALID ACTION\n") - tempfile.write("GET_RELATIONSHIP \"Mother's Name\" \"Child's Name\"\n") + tempfile.write("GET_RELATIONSHIP \"Mother's Name\" \"Son\"\n") tempfile.rewind end @@ -60,7 +60,7 @@ RSpec.describe ActionFileExecutor do allow(action_file_executor).to receive(:execute_action) action_file_executor.execute_actions - expect(action_file_executor).to have_received(:execute_action).exactly(3).times + expect(action_file_executor).to have_received(:execute_action).exactly(2).times end end end @@ -95,23 +95,25 @@ RSpec.describe ActionFileExecutor do context 'with the ADD_CHILD action' do it 'calls the add_child method on FamilyTree' do action_file_executor = ActionFileExecutor.new(tempfile.path) - action_file_executor.send(:execute_action, 'ADD_CHILD', ["Mother's Name", "Child's Name"]) + action_file_executor.send(:execute_action, 'ADD_CHILD', ["Mother's Name", "Child's Name", 'Male']) + expect(family_tree).to have_received(:add_child).with("Mother's Name", "Child's Name", 'Male') end end context 'with the GET_RELATIONSHIP action' do it 'calls the get_relationship method on FamilyTree' do action_file_executor = ActionFileExecutor.new(tempfile.path) - action_file_executor.send(:execute_action, 'GET_RELATIONSHIP', ["Child's Name", 'Maternal-Uncle']) + action_file_executor.send(:execute_action, 'GET_RELATIONSHIP', ["Mother's Name", 'Son']) + expect(family_tree).to have_received(:get_relationship).with("Mother's Name", 'Son') end end context 'with an unsupported action' do - it 'prints an error message' do + it 'does nothing' do + action_file_executor = ActionFileExecutor.new(tempfile.path) expect do - action_file_executor = ActionFileExecutor.new(tempfile.path) action_file_executor.send(:execute_action, 'ADD_MOTHER', ["Child's Name", "Mother's Name"]) - end.to output("Ignoring unsupported action: [ADD_MOTHER]\n").to_stdout + end.not_to raise_error end end end diff --git a/spec/family_tree_spec.rb b/spec/family_tree_spec.rb index dc91719..834acca 100644 --- a/spec/family_tree_spec.rb +++ b/spec/family_tree_spec.rb @@ -30,11 +30,31 @@ RSpec.describe FamilyTree do end describe '#add_child' do - it 'outputs the correct parameters when adding a child' do - expect do - FamilyTree.instance.add_child('Jane', 'Sam', - 'Male') - end.to output("Adding Child with params: Jane, Sam, Male\n").to_stdout + before do + FamilyTree.instance.add_family(family) + end + + context 'when the mother is present' do + it 'adds a child to the family' do + result = FamilyTree.instance.add_child(mother.name, 'Charlie', Gender::FEMALE) + expect(result).to eq('CHILD_ADDED') + expect(family.children.last.name).to eq('Charlie') + end + + it 'does not add a duplicate child' do + FamilyTree.instance.add_child(mother.name, 'Charlie', Gender::FEMALE) + result = FamilyTree.instance.add_child(mother.name, 'Charlie', Gender::FEMALE) + expect(result).to eq('CHILD_ADDITION_FAILED') + expect(family.children.count { |child| child.name.casecmp('Charlie').zero? }).to eq(1) + end + end + + context 'when the mother is not present' do + it 'returns CHILD_ADDITION_FAILED' do + FamilyTree.instance.families.clear # Remove existing families + result = FamilyTree.instance.add_child('Unknown Mother', 'Charlie', Gender::FEMALE) + expect(result).to eq('CHILD_ADDITION_FAILED') + end end end @@ -98,49 +118,4 @@ RSpec.describe FamilyTree do end end end - - describe '#find_mother' do - it 'returns the mother if present' do - expect(FamilyTree.instance.find_mother(family)).to eq(mother) - end - - it 'returns NilPerson if no mother is present' do - orphan_family = Family.new(NilPerson.new, father, [child1]) - expect(FamilyTree.instance.find_mother(orphan_family)).to be_a(NilPerson) - end - end - - describe '#find_father' do - it 'returns the father if present' do - expect(FamilyTree.instance.find_father(family)).to eq(father) - end - - it 'returns NilPerson if no father is present' do - orphan_family = Family.new(mother, NilPerson.new, [child1]) - expect(FamilyTree.instance.find_father(orphan_family)).to be_a(NilPerson) - end - end - - describe '#find_siblings' do - it 'returns siblings in the same family' do - result = FamilyTree.instance.find_siblings(family, 'Anna') - expect(result).to eq([child2]) - end - - it 'returns empty array if there are no siblings' do - single_child_family = Family.new(mother, father, [child1]) - result = FamilyTree.instance.find_siblings(single_child_family, 'Anna') - expect(result).to eq([]) - end - end - - describe '#child_of_family?' do - it 'returns true if the person is a child of the family' do - expect(FamilyTree.instance.child_of_family?(family, 'Anna')).to be true - end - - it 'returns false if the person is not a child of the family' do - expect(FamilyTree.instance.child_of_family?(family, 'Unknown')).to be false - end - end end |
