summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorSzymon Szukalski <szymon@skas.io>2024-10-25 10:00:36 +1100
committerSzymon Szukalski <szymon@skas.io>2024-10-25 10:00:36 +1100
commit05fda6c29f0fe4742b7ea6b237ea98f737b68b8b (patch)
tree3fca7845fc80f0d93370d2bdadb647f3f9c65d25 /lib
parentd41d881cf8af8cb8b6cb89b87a351585ee46063c (diff)
Document code with YARD
Diffstat (limited to 'lib')
-rw-r--r--lib/action_file_executor.rb30
-rw-r--r--lib/cli.rb11
-rw-r--r--lib/family.rb17
-rw-r--r--lib/family_factory.rb48
-rw-r--r--lib/family_tree.rb82
-rw-r--r--lib/gender.rb11
-rw-r--r--lib/nil_person.rb23
-rw-r--r--lib/person.rb20
-rw-r--r--lib/relationship_manager.rb9
9 files changed, 249 insertions, 2 deletions
diff --git a/lib/action_file_executor.rb b/lib/action_file_executor.rb
index d8e583c..2354dd7 100644
--- a/lib/action_file_executor.rb
+++ b/lib/action_file_executor.rb
@@ -2,6 +2,9 @@
require_relative 'family_tree'
+# ActionFileExecutor class to handle the execution of actions from a file.
+# This class processes each line of the file, extracts actions and their parameters,
+# and executes the corresponding actions. It handles both quoted and unquoted parameters.
class ActionFileExecutor
def initialize(file_path)
@file_path = file_path
@@ -22,20 +25,33 @@ class ActionFileExecutor
abort("Error: The file '#{@file_path}' does not exist.")
end
+ # Processes a line from the action file.
+ # Extracts the action and parameters, and executes the action if valid.
+ #
+ # @param line [String] The line to process.
+ # @return [void]
def process_line(line)
- return if line.empty? || comment?(line)
+ return if comment?(line)
action, params = extract_action_and_params(line)
-
return unless action && valid_action?(action, params)
execute_action(action, params)
end
+ # Checks if a line is a comment.
+ #
+ # @param line [String] The line to check.
+ # @return [Boolean] True if the line is a comment, false otherwise.
def comment?(line)
line.start_with?('#')
end
+ # Extracts the action and parameters from a line.
+ # Handles both quoted and unquoted parameters.
+ #
+ # @param line [String] The line to extract from.
+ # @return [Array<String, Array<String>>] An array containing the action and parameters.
def extract_action_and_params(line)
match = line.match(/^(\S+)(.*)$/)
return unless match
@@ -46,6 +62,11 @@ class ActionFileExecutor
[action, params]
end
+ # Executes the action with the given parameters.
+ #
+ # @param action [String] The action to execute.
+ # @param params [Array<String>] The parameters for the action.
+ # @return [void]
def execute_action(action, params)
case action
when 'ADD_CHILD'
@@ -55,6 +76,11 @@ class ActionFileExecutor
end
end
+ # Validates the action and its parameters.
+ #
+ # @param action [String] The action to validate.
+ # @param params [Array<String>] The parameters to validate.
+ # @return [Boolean] True if the action and parameters are valid, false otherwise.
def valid_action?(action, params)
case action
when 'ADD_CHILD' then params.size == 3
diff --git a/lib/cli.rb b/lib/cli.rb
index 3087212..a252c71 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -2,12 +2,19 @@
require_relative 'action_file_executor'
+# CLI class to handle command-line interface operations.
class CLI
+ # Initializes the CLI with command-line arguments.
+ #
+ # @param args [Array<String>] The command-line arguments.
def initialize(args)
@args = args
validate_arguments
end
+ # Runs the CLI, executing actions from the provided file.
+ #
+ # @return [void]
def run
file_path = @args[0]
action_file_executor = ActionFileExecutor.new(file_path)
@@ -16,6 +23,10 @@ class CLI
private
+ # Validates the command-line arguments.
+ #
+ # @return [void]
+ # @raise [SystemExit] if no arguments are provided or the file does not exist.
def validate_arguments
if @args.empty?
puts 'Usage: family_tree <path/to/actions.txt>'
diff --git a/lib/family.rb b/lib/family.rb
index 2c1acca..5aec487 100644
--- a/lib/family.rb
+++ b/lib/family.rb
@@ -2,9 +2,16 @@
require_relative 'person'
+# Family class representing a family unit with a mother, father, and children.
class Family
attr_reader :mother, :father, :children
+ # Initializes a new Family object.
+ #
+ # @param mother [Person, NilPerson] The mother of the family, defaults to NilPerson.
+ # @param father [Person, NilPerson] The father of the family, defaults to NilPerson.
+ # @param children [Array<Person>] The children of the family, defaults to an empty array.
+ # @raise [ArgumentError] if the mother is not female or the father is not male.
def initialize(mother = NilPerson.new, father = NilPerson.new, children = [])
raise ArgumentError, 'Mother must be female' if !mother.is_a?(NilPerson) && mother.gender != Gender::FEMALE
raise ArgumentError, 'Father must be male' if !father.is_a?(NilPerson) && father.gender != Gender::MALE
@@ -14,12 +21,22 @@ class Family
@children = children
end
+ # Assigns a new mother to the family.
+ #
+ # @param mother [Person] The new mother.
+ # @raise [ArgumentError] if the mother is not female.
+ # @return [void]
def assign_mother(mother)
raise ArgumentError, 'Mother must be female' if mother.gender != Gender::FEMALE
@mother = mother
end
+ # Assigns a new father to the family.
+ #
+ # @param father [Person] The new father.
+ # @raise [ArgumentError] if the father is not male.
+ # @return [void]
def assign_father(father)
raise ArgumentError, 'Father must be male' if father.gender != Gender::MALE
diff --git a/lib/family_factory.rb b/lib/family_factory.rb
index a42c5ea..cd06de9 100644
--- a/lib/family_factory.rb
+++ b/lib/family_factory.rb
@@ -5,12 +5,19 @@ require_relative 'person'
require_relative 'gender'
require_relative 'relationship_manager'
+# FamilyFactory class to create initial families for the FamilyTree.
+# The primary purpose of this class is to create and initialize predefined families.
class FamilyFactory
+ # Initializes a new FamilyFactory object.
+ # Sets up a hash to store people and gets the singleton instance of RelationshipManager.
def initialize
@people = {}
@relationship_manager = RelationshipManager.instance
end
+ # Creates and returns an array of predefined families.
+ #
+ # @return [Array<Family>] An array of predefined Family objects.
def create_families
[
create_queen_margaret_and_king_arthur_family,
@@ -27,21 +34,38 @@ class FamilyFactory
private
+ # Finds or creates a person with the given name and gender.
+ #
+ # @param name [String] The name of the person.
+ # @param gender [String] The gender of the person.
+ # @return [Person] The found or created Person object.
def find_or_create_person(name, gender)
return @people[name] if @people.key?(name)
person = Person.new(name, gender)
@people[name] = person
+ person
end
+ # Finds or creates a male person with the given name.
+ #
+ # @param name [String] The name of the male person.
+ # @return [Person] The found or created male Person object.
def find_or_create_male(name)
find_or_create_person(name, Gender::MALE)
end
+ # Finds or creates a female person with the given name.
+ #
+ # @param name [String] The name of the female person.
+ # @return [Person] The found or created female Person object.
def find_or_create_female(name)
find_or_create_person(name, Gender::FEMALE)
end
+ # Creates the family of Queen Margaret and King Arthur.
+ #
+ # @return [Family] The created Family object.
def create_queen_margaret_and_king_arthur_family
queen_margaret = find_or_create_female('Queen Margaret')
king_arthur = find_or_create_male('King Arthur')
@@ -57,6 +81,9 @@ class FamilyFactory
Family.new(queen_margaret, king_arthur, [bill, charlie, percy, ronald, ginerva])
end
+ # Creates the family of Flora and Bill.
+ #
+ # @return [Family] The created Family object.
def create_flora_and_bill_family
bill = find_or_create_male('Bill')
flora = find_or_create_female('Flora')
@@ -70,6 +97,9 @@ class FamilyFactory
Family.new(flora, bill, [victoire, dominique, louis])
end
+ # Creates the family of Victoire and Ted.
+ #
+ # @return [Family] The created Family object.
def create_victoire_and_ted_family
victoire = find_or_create_female('Victoire')
ted = find_or_create_male('Ted')
@@ -81,6 +111,9 @@ class FamilyFactory
Family.new(victoire, ted, [remus])
end
+ # Creates the family of Percy and Audrey.
+ #
+ # @return [Family] The created Family object.
def create_percy_and_audrey_family
percy = find_or_create_male('Percy')
audrey = find_or_create_female('Audrey')
@@ -93,6 +126,9 @@ class FamilyFactory
Family.new(audrey, percy, [molly, lucy])
end
+ # Creates the family of Ronald and Helen.
+ #
+ # @return [Family] The created Family object.
def create_ronald_and_helen_family
ronald = find_or_create_male('Ronald')
helen = find_or_create_female('Helen')
@@ -105,6 +141,9 @@ class FamilyFactory
Family.new(helen, ronald, [rose, hugo])
end
+ # Creates the family of Malfoy and Rose.
+ #
+ # @return [Family] The created Family object.
def create_malfoy_and_rose_family
malfoy = find_or_create_male('Malfoy')
rose = find_or_create_female('Rose')
@@ -117,6 +156,9 @@ class FamilyFactory
Family.new(rose, malfoy, [draco, aster])
end
+ # Creates the family of Ginerva and Harry.
+ #
+ # @return [Family] The created Family object.
def create_ginerva_and_harry_family
ginerva = find_or_create_female('Ginerva')
harry = find_or_create_male('Harry')
@@ -130,6 +172,9 @@ class FamilyFactory
Family.new(ginerva, harry, [james, albus, lily])
end
+ # Creates the family of Darcy and James.
+ #
+ # @return [Family] The created Family object.
def create_darcy_and_james_family
darcy = find_or_create_female('Darcy')
james = find_or_create_male('James')
@@ -141,6 +186,9 @@ class FamilyFactory
Family.new(darcy, james, [william])
end
+ # Creates the family of Alice and Albus.
+ #
+ # @return [Family] The created Family object.
def create_alice_and_albus_family
alice = find_or_create_female('Alice')
albus = find_or_create_male('Albus')
diff --git a/lib/family_tree.rb b/lib/family_tree.rb
index 0909ed1..908cb65 100644
--- a/lib/family_tree.rb
+++ b/lib/family_tree.rb
@@ -4,19 +4,35 @@ require 'singleton'
require_relative 'person'
require_relative 'family_factory'
+# FamilyTree class representing a family tree.
+# This class follows the Singleton pattern to ensure there is only one instance
+# of the family tree throughout the application. It uses the FamilyFactory to
+# seed the initial family tree with predefined families.
class FamilyTree
include Singleton
attr_accessor :families
+ # Initializes the FamilyTree instance.
+ # Seeds the initial family tree using the FamilyFactory.
def initialize
@families = FamilyFactory.new.create_families
end
+ # Adds a family to the family tree.
+ #
+ # @param family [Family] The family to add.
+ # @return [void]
def add_family(family)
@families << family unless @families.include?(family)
end
+ # Adds a child to a family based on the mother's name.
+ #
+ # @param mothers_name [String] The name of the mother.
+ # @param name [String] The name of the child.
+ # @param gender [String] The gender of the child.
+ # @return [String] 'CHILD_ADDED' if the child is added successfully, 'CHILD_ADDITION_FAILED' otherwise.
def add_child(mothers_name, name, gender)
result = find_person_in_families(mothers_name)
parent_of_family = result[:parent_of_family]
@@ -30,6 +46,11 @@ class FamilyTree
'CHILD_ADDED'
end
+ # Retrieves the relationship of a person based on their name and the specified relationship.
+ #
+ # @param name [String] The name of the person.
+ # @param relationship [String] The type of relationship to retrieve.
+ # @return [String] The name(s) of the related person(s) or an appropriate message if not found.
def get_relationship(name, relationship)
result = find_person_in_families(name)
person = result[:person]
@@ -64,6 +85,11 @@ class FamilyTree
private
+ # Handles the parent relationship retrieval.
+ #
+ # @param child_of_family [Family] The family of the child.
+ # @param relationship [String] The type of parent relationship ('mother' or 'father').
+ # @return [String] The name of the parent or 'PERSON_NOT_FOUND' if not found.
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)
@@ -71,11 +97,21 @@ class FamilyTree
parent.name
end
+ # Handles the siblings relationship retrieval.
+ #
+ # @param child_of_family [Family] The family of the child.
+ # @param name [String] The name of the child.
+ # @return [String] The names of the siblings or 'NONE' if not found.
def handle_siblings_relationship(child_of_family, name)
siblings = find_siblings(child_of_family, name)
siblings.empty? ? 'NONE' : siblings.map(&:name).join(' ')
end
+ # Handles the children relationship retrieval.
+ #
+ # @param parent_of_family [Family] The family of the parent.
+ # @param relationship [String] The type of children relationship ('child', 'son', or 'daughter').
+ # @return [String] The names of the children or 'NONE' if not found.
def handle_children_relationship(parent_of_family, relationship)
return 'NONE' if parent_of_family.nil?
@@ -95,6 +131,12 @@ class FamilyTree
end
end
+ # Handles the sibling relationship retrieval for uncles and aunts.
+ #
+ # @param child_of_family [Family] The family of the child.
+ # @param type [String] The type of sibling relationship ('uncle' or 'aunt').
+ # @param side [String] The side of the family ('paternal' or 'maternal').
+ # @return [String] The names of the uncles or aunts or 'NONE' if not found.
def handle_sibling_relationship(child_of_family, type, side)
return 'NONE' if child_of_family.nil?
@@ -123,22 +165,48 @@ class FamilyTree
relatives.empty? ? 'NONE' : relatives.map(&:name).join(' ')
end
+ # Handles the uncle relationship retrieval.
+ #
+ # @param child_of_family [Family] The family of the child.
+ # @param type [String] The type of uncle relationship ('paternal' or 'maternal').
+ # @return [String] The names of the uncles or 'NONE' if not found.
def handle_uncle_relationship(child_of_family, type)
handle_sibling_relationship(child_of_family, 'uncle', type)
end
+ # Handles the aunt relationship retrieval.
+ #
+ # @param child_of_family [Family] The family of the child.
+ # @param type [String] The type of aunt relationship ('paternal' or 'maternal').
+ # @return [String] The names of the aunts or 'NONE' if not found.
def handle_aunt_relationship(child_of_family, type)
handle_sibling_relationship(child_of_family, 'aunt', type)
end
+ # Handles the sister-in-law relationship retrieval.
+ #
+ # @param person [Person] The person whose sister-in-law is to be found.
+ # @param child_of_family [Family] The family of the child.
+ # @return [String] The names of the sisters-in-law or 'NONE' if not found.
def handle_sister_in_law_relationship(person, child_of_family)
find_in_laws(person, child_of_family, Gender::FEMALE)
end
+ # Handles the brother-in-law relationship retrieval.
+ #
+ # @param person [Person] The person whose brother-in-law is to be found.
+ # @param child_of_family [Family] The family of the child.
+ # @return [String] The names of the brothers-in-law or 'NONE' if not found.
def handle_brother_in_law_relationship(person, child_of_family)
find_in_laws(person, child_of_family, Gender::MALE)
end
+ # Finds the in-laws of a person based on their gender.
+ #
+ # @param person [Person] The person whose in-laws are to be found.
+ # @param child_of_family [Family] The family of the child.
+ # @param gender [String] The gender of the in-laws to find.
+ # @return [String] The names of the in-laws or 'NONE' if not found.
def find_in_laws(person, child_of_family, gender)
in_laws = []
@@ -166,6 +234,10 @@ class FamilyTree
in_laws.empty? ? 'NONE' : in_laws.join(' ')
end
+ # Finds a person in the families by name.
+ #
+ # @param name [String] The name of the person to find.
+ # @return [Hash] A hash containing the person, parent_of_family, and child_of_family.
def find_person_in_families(name)
result = { person: NilPerson.new, parent_of_family: nil, child_of_family: nil }
@@ -195,10 +267,20 @@ class FamilyTree
result
end
+ # Finds the siblings of a person in a family.
+ #
+ # @param family [Family] The family of the person.
+ # @param name [String] The name of the person.
+ # @return [Array<Person>] An array of siblings.
def find_siblings(family, name)
family.children.reject { |child| child.name.casecmp(name).zero? }
end
+ # Checks if a person is a child of a family.
+ #
+ # @param family [Family] The family to check.
+ # @param name [String] The name of the person.
+ # @return [Boolean] True if the person is a child of the family, false otherwise.
def child_of_family?(family, name)
return false unless family.is_a?(Family)
diff --git a/lib/gender.rb b/lib/gender.rb
index 9bd3b06..a4f1b60 100644
--- a/lib/gender.rb
+++ b/lib/gender.rb
@@ -1,13 +1,24 @@
# frozen_string_literal: true
+# Gender class to handle gender-related operations.
class Gender
+ # Constant representing male gender.
MALE = 'male'
+
+ # Constant representing female gender.
FEMALE = 'female'
+ # Returns all defined genders.
+ #
+ # @return [Array<String>] An array of all genders.
def self.all
[MALE, FEMALE]
end
+ # Checks if the provided gender is valid.
+ #
+ # @param gender [String] The gender to validate.
+ # @return [Boolean] True if the gender is valid, false otherwise.
def self.valid?(gender)
return false unless gender.is_a?(String)
diff --git a/lib/nil_person.rb b/lib/nil_person.rb
index 30a175f..607ae77 100644
--- a/lib/nil_person.rb
+++ b/lib/nil_person.rb
@@ -1,26 +1,49 @@
# frozen_string_literal: true
+# NilPerson class following the NullObject pattern.
+# This class represents a null object for a person, providing default
+# implementations that return nil or self as appropriate.
class NilPerson
+ # Returns the name of the NilPerson.
+ #
+ # @return [nil] Always returns nil.
def name
nil
end
+ # Returns the father of the NilPerson.
+ #
+ # @return [NilPerson] Always returns self.
def father
self
end
+ # Returns the mother of the NilPerson.
+ #
+ # @return [NilPerson] Always returns self.
def mother
self
end
+ # Returns the gender of the NilPerson.
+ #
+ # @return [nil] Always returns nil.
def gender
nil
end
+ # Checks equality with another object.
+ #
+ # @param other [Object] The object to compare with.
+ # @return [Boolean] True if the other object is a NilPerson, false otherwise.
def ==(other)
other.is_a?(NilPerson)
end
+ # Checks equality with another object (alias for ==).
+ #
+ # @param other [Object] The object to compare with.
+ # @return [Boolean] True if the other object is a NilPerson, false otherwise.
def eql?(other)
self == other
end
diff --git a/lib/person.rb b/lib/person.rb
index 8571754..9a3dcb6 100644
--- a/lib/person.rb
+++ b/lib/person.rb
@@ -2,27 +2,47 @@
require_relative 'nil_person'
+# Person class representing an individual with a name, gender, and spouse.
class Person
attr_accessor :name, :gender, :spouse
+ # Initializes a new Person object.
+ #
+ # @param name [String] The name of the person.
+ # @param gender [String] The gender of the person.
+ # @param spouse [Person, NilPerson] The spouse of the person, defaults to NilPerson.
def initialize(name, gender, spouse = NilPerson.new)
@name = name
@gender = gender
@spouse = spouse
end
+ # Checks equality with another object.
+ #
+ # @param other [Object] The object to compare with.
+ # @return [Boolean] True if the other object is a Person with the same name, gender, and spouse, false otherwise.
def ==(other)
other.is_a?(Person) && name == other.name && gender == other.gender && spouse == other.spouse
end
+ # Checks equality with another object (alias for ==).
+ #
+ # @param other [Object] The object to compare with.
+ # @return [Boolean] True if the other object is a Person with the same name, gender, and spouse, false otherwise.
def eql?(other)
self == other
end
+ # Computes the hash code for the Person object.
+ #
+ # @return [Integer] The hash code based on the name, gender, and spouse.
def hash
[name, gender, spouse].hash
end
+ # Returns a string representation of the Person.
+ #
+ # @return [String] The string representation in the format "name (gender)".
def to_s
"#{name} (#{gender})"
end
diff --git a/lib/relationship_manager.rb b/lib/relationship_manager.rb
index 08d87e1..30dfd94 100644
--- a/lib/relationship_manager.rb
+++ b/lib/relationship_manager.rb
@@ -2,9 +2,18 @@
require 'singleton'
+# RelationshipManager class to manage relationships between people.
+# This class follows the Singleton pattern to ensure there is only one instance
+# of the relationship manager throughout the application.
class RelationshipManager
include Singleton
+ # Links two people as spouses.
+ #
+ # @param person1 [Person] The first person to link.
+ # @param person2 [Person] The second person to link.
+ # @raise [RuntimeError] if either person is already linked to someone else.
+ # @return [void]
def link_spouses(person1, person2)
# Check if either person is already linked to someone else
if person1.spouse != NilPerson.new && person1.spouse != person2