diff options
| author | Szymon Szukalski <szymon@skas.io> | 2024-10-25 10:00:36 +1100 |
|---|---|---|
| committer | Szymon Szukalski <szymon@skas.io> | 2024-10-25 10:00:36 +1100 |
| commit | 05fda6c29f0fe4742b7ea6b237ea98f737b68b8b (patch) | |
| tree | 3fca7845fc80f0d93370d2bdadb647f3f9c65d25 | |
| parent | d41d881cf8af8cb8b6cb89b87a351585ee46063c (diff) | |
Document code with YARD
| -rw-r--r-- | bin/family_tree.rb | 3 | ||||
| -rw-r--r-- | lib/action_file_executor.rb | 30 | ||||
| -rw-r--r-- | lib/cli.rb | 11 | ||||
| -rw-r--r-- | lib/family.rb | 17 | ||||
| -rw-r--r-- | lib/family_factory.rb | 48 | ||||
| -rw-r--r-- | lib/family_tree.rb | 82 | ||||
| -rw-r--r-- | lib/gender.rb | 11 | ||||
| -rw-r--r-- | lib/nil_person.rb | 23 | ||||
| -rw-r--r-- | lib/person.rb | 20 | ||||
| -rw-r--r-- | lib/relationship_manager.rb | 9 |
10 files changed, 252 insertions, 2 deletions
diff --git a/bin/family_tree.rb b/bin/family_tree.rb index 5acc9bb..7dd95d4 100644 --- a/bin/family_tree.rb +++ b/bin/family_tree.rb @@ -2,4 +2,7 @@ require_relative '../lib/cli' +# Initializes and runs the CLI with the provided arguments. +# +# @param [Array<String>] ARGV the command-line arguments passed to the script CLI.new(ARGV).run 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 @@ -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 |
