lib/citeproc/names.rb in citeproc-0.0.8 vs lib/citeproc/names.rb in citeproc-0.0.9

- old
+ new

@@ -1,556 +1,655 @@ +# -*- coding: utf-8 -*- module CiteProc - - # Names consist of several dependent parts of strings. Simple personal names - # are composed of family and given elements, containing respectively the - # family and given name of the individual. - # - # Name.new(:family => 'Doe', :given => 'Jane') - # - # Institutional and other names that should always be presented literally - # (such as "The Artist Formerly Known as Prince", "Banksy", or "Ramses IV") - # should be delivered as a single :literal element: - # - # Name.new(:literal => 'Banksy') - # - # Name particles, such as the "von" in "Alexander von Humboldt", can be - # delivered separately from the family and given name, as :dropping-particle - # and :non-dropping-particle elements. - # - # Name suffixes such as the "Jr." in "Frank Bennett, Jr." and the "III" in - # "Horatio Ramses III" can be delivered as a suffix element. - # - # Name.new do |n| - # n.family, n.given, n.suffix = 'Ramses', 'Horatio', 'III' - # end - # - # Names not written in the Latin or Cyrillic scripts are always displayed - # with the family name first. Sometimes it might be desired to handle a - # Latin or Cyrillic transliteration as if it were a fixed (non-Byzantine) - # name. This behavior can be prompted by including activating - # static-ordering: - # - # Name.new(:family => 'Muramaki', :given => 'Haruki').to_s - # #=> "Haruki Muramaki" - # Name.new(:family => 'Muramaki', :given => 'Haruki').static_order!.to_s - # #=> "Muramaki Haruki" - # - class Name - - extend Forwardable - - include Attributes - include Comparable - - # Based on the regular expression in Frank G. Bennett's citeproc-js - # https://bitbucket.org/fbennett/citeproc-js/overview - ROMANESQUE = - /^[a-zA-Z\u0080-\u017f\u0400-\u052f\u0386-\u03fb\u1f00-\u1ffe\.,\s\u0027\u02bc\u2019-]*$/ + + # Names consist of several dependent parts of strings. Simple personal names + # are composed of family and given elements, containing respectively the + # family and given name of the individual. + # + # Name.new(:family => 'Doe', :given => 'Jane') + # + # Institutional and other names that should always be presented literally + # (such as "The Artist Formerly Known as Prince", "Banksy", or "Ramses IV") + # should be delivered as a single :literal element: + # + # Name.new(:literal => 'Banksy') + # + # Name particles, such as the "von" in "Alexander von Humboldt", can be + # delivered separately from the family and given name, as :dropping-particle + # and :non-dropping-particle elements. + # + # Name suffixes such as the "Jr." in "Frank Bennett, Jr." and the "III" in + # "Horatio Ramses III" can be delivered as a suffix element. + # + # Name.new do |n| + # n.family, n.given, n.suffix = 'Ramses', 'Horatio', 'III' + # end + # + # Names not written in the Latin or Cyrillic scripts are always displayed + # with the family name first. Sometimes it might be desired to handle a + # Latin or Cyrillic transliteration as if it were a fixed (non-Byzantine) + # name (e.g., for Hungarian names). This behavior can be prompted by + # including activating static-ordering: + # + # Name.new(:family => 'Murakami', :given => 'Haruki').to_s + # #-> "Haruki Murakami" + # + # Name.new(:family => 'Murakami', :given => 'Haruki').static_order!.to_s + # #-> "Murakami Haruki" + class Name + + extend Forwardable + + include Attributes + include Comparable # Class instance variables - # Default formatting options - @defaults = { - :form => 'long', - :'name-as-sort-order' => false, - :'demote-non-dropping-particle' => 'never', - :'sort-separator' => ', ', - :'initialize-with' => nil - }.freeze + @romanesque = CiteProc.oniguruma { + '^[\p{Latin}\p{Greek}\p{Cyrillic}\p{Hebrew}\p{Armenian}\p{Georgian}\p{Common}]*$' + } || CiteProc.ruby_18 { + # @todo improve fallback range + /^[a-zA-Zäöüéè\s[:punct:]]*$/u + } + + # Default formatting options + @defaults = { + :form => 'long', + :'name-as-sort-order' => false, + :'demote-non-dropping-particle' => :never, + :'sort-separator' => ', ', + :'initialize-with' => nil + }.freeze - @parts = [:family, :given,:literal, :suffix, :'dropping-particle', - :'non-dropping-particle'].freeze - - class << self - - attr_reader :defaults, :parts - - def parse(name_string) - parse!(name_string) - rescue ParseError - nil - end - - def parse!(name_string) - fail 'not implemented yet' - end - - end + @parts = [:family, :given,:literal, :suffix, :'dropping-particle', + :'non-dropping-particle'].freeze + + class << self + attr_reader :defaults, :parts, :romanesque + end + - - # Method generators - - attr_reader :options - - attr_predicates :'comma-suffix', :'static-ordering', :multi, *@parts - - # Aliases - [[:last, :family], [:first, :given], [:particle, :'non_dropping_particle']].each do |a, m| + # Method generators + + # @!attribute [r] options + # @return the name's formatting options + attr_reader :options + + attr_predicates :'comma-suffix', :'static-ordering', :multi, *@parts + + # Aliases + [[:particle, :'non_dropping_particle']].each do |a, m| alias_method(a, m) if method_defined?(m) - wa, wm = "#{a}=", "#{m}=" + wa, wm = "#{a}=", "#{m}=" alias_method(wa, wm) if method_defined?(wm) - pa, pm = "#{a}?", "#{m}?" + pa, pm = "#{a}?", "#{m}?" alias_method(pa, pm) if method_defined?(pm) - pa, pm = "has_#{a}?", "has_#{m}?" + pa, pm = "has_#{a}?", "has_#{m}?" alias_method(pa, pm) if method_defined?(pm) end - - # Names quack sorta like a String - def_delegators :to_s, :=~, :===, - *String.instance_methods(false).reject { |m| m =~ /^\W|!$|to_s|replace|first|last/ } - - # Delegate bang! methods to each field's value - String.instance_methods(false).each do |m| - if m.to_s.end_with?('!') - define_method(m) do |*arguments, &block| - Name.parts.each do |part| - p = attributes[part] - p.send(m, *arguments, &block) if p.respond_to?(m) - end - self - end - end - end - - - # Instance methods - - def initialize(attributes = {}, options = {}) - @options = Name.defaults.merge(options) - @sort_prefix = (/^(the|an?|der|die|das|eine?|l[ae])\s+|^l\W/i).freeze - - merge(attributes) - - yield self if block_given? - end - - def initialize_copy(other) - @attributes = other.attributes.deep_copy - @options = other.options.dup - end - - - # Returns true if the Name looks like it belongs to a person. - def personal? - !!family && !literal? - end - - # Returns true if the name contains only romanesque characters. This - # should be the case for the majority of names written in latin or - # greek based script. It will be false, for example, for names written - # in Chinese, Japanese, Arabic or Hebrew. - def romanesque? - !!([given, family].join.gsub(Variable.markup, '') =~ ROMANESQUE) - end - - alias byzantine? romanesque? - - def static_order? - static_ordering? || !romanesque? - end + + # Names quack sorta like a String + def_delegators :to_s, :=~, :===, + *String.instance_methods(false).reject { |m| m =~ /^\W|!$|to_s|replace|first|last/ } + + # Delegate bang! methods to each field's value + String.instance_methods(false).each do |m| + if m.to_s.end_with?('!') + define_method(m) do |*arguments, &block| + Name.parts.each do |part| + p = attributes[part] + p.send(m, *arguments, &block) if p.respond_to?(m) + end + self + end + end + end + + + # Instance methods + + def initialize(attributes = {}, options = {}) + @options = Name.defaults.merge(options) + @sort_prefix = (/^(the|an?|der|die|das|eine?|l[ae])\s+|^l\W/i).freeze + + merge(attributes) + + yield self if block_given? + end + + def initialize_copy(other) + @attributes = other.attributes.deep_copy + @options = other.options.dup + end + + + # @return [Boolean] whether or not the Name looks like it belongs to a person + def personal? + !empty? && !literal? + end + - def static_order! - self.static_ordering = true - self - end + # A name is `romanesque' if it contains only romanesque characters. This + # should be the case for the majority of names written in latin- or + # greek-based script. It will be false, for example, for names written + # in Chinese, Japanese, Arabic or Hebrew. + # + # @return [Boolean] whether or not the name is romanesque + def romanesque? + !!([given, family].join.gsub(Variable.markup, '') =~ Name.romanesque) + end + + alias byzantine? romanesque? + + # @return [Boolean] whether or not the name should be printed in static order + def static_order? + static_ordering? || !romanesque? + end - alias static_order static_ordering - alias static_order= static_ordering= + # Set the name to use static order for printing, i.e., print the family + # name before the given name as is customary, for example, in Hungarian + # and many Asian languages. + # + # @return [self] + def static_order! + self.static_ordering = true + self + end - # Returns true if this Name's sort-oder options currently set. - def sort_order? - !!(options[:'name-as-sort-order'].to_s =~ /^(y(es)?|always|t(rue)?)$/i) - end - - def display_order? - !sort_order? - end - - # Sets this name sort-order option to true. Returns the Name instance. - def sort_order! - options[:'name-as-sort-order'] = true - self - end + alias static_order static_ordering + alias static_order= static_ordering= + + # @return [Boolean] whether or not the name will be printed in sort-order + def sort_order? + !!(options[:'name-as-sort-order'].to_s =~ /^(y(es)?|always|t(rue)?)$/i) + end - # The reverse of @sort_order! - def display_order! - options[:'name-as-sort-order'] = false - self - end - - def sort_separator - options[:'sort-separator'] - end - - alias comma sort_separator - - def short_form? - options[:form] == 'short' - end + def display_order? + !sort_order? + end + + # Sets the name to use sort-order. The reverse of {#display_order!}. + # @return [self] + def sort_order! + options[:'name-as-sort-order'] = true + self + end + + # Sets the name to use display-order. The reverse of {#sort_order!}. + # @return [self] + def display_order! + options[:'name-as-sort-order'] = false + self + end + + # @return [String] the current sort separator + def sort_separator + options[:'sort-separator'] + end + + alias comma sort_separator + + # @return [Boolean] whether or not the short form will be used for printing + def short_form? + options[:form].to_s =~ /short/i + end - def short_form! - options[:form] = 'short' - self - end + # Use short form for printing the name + # @return [self] + def short_form! + options[:form] = :short + self + end - def long_form? - options[:form] == 'long' - end + # @return [Boolean] whether or not the long form will be used for printing + def long_form? + !short_form? + end - def long_form! - options[:form] = 'long' - self - end + # Use long form for printing the name + # @return [self] + def long_form! + options[:form] = :long + self + end + + # @return [Boolean] whether or not initials will be used for printing + def initials? + !!options[:'initialize-with'] && personal? && romanesque? + end + + def demote_non_dropping_particle? + always_demote_non_dropping_particle? || + !!(sort_order? && options[:'demote-non-dropping-particle'] =~ /^sort(-only)?$/i) + end - # TODO should be done via mixin to be reused for variables - # def transliterable? - # end - # - # def transliterate(locale) - # end - - def initials? - !!options[:'initialize-with'] && personal? && romanesque? - end - - def demote_non_dropping_particle? - always_demote_non_dropping_particle? || - !!(sort_order? && options[:'demote-non-dropping-particle'] =~ /^sort(-only)?$/i) - end + alias demote_particle? demote_non_dropping_particle? + + def never_demote_non_dropping_particle? + !!(options[:'demote-non-dropping-particle'] =~ /^never$/i) + end - alias demote_particle? demote_non_dropping_particle? - - def never_demote_non_dropping_particle? - !!(options[:'demote-non-dropping-particle'] =~ /^never$/i) - end + def never_demote_non_dropping_particle! + options[:'demote-non-dropping-particle'] = 'never' + self + end - def never_demote_non_dropping_particle! - options[:'demote-non-dropping-particle'] = 'never' - self - end + alias never_demote_particle? never_demote_non_dropping_particle? + alias never_demote_particle! never_demote_non_dropping_particle! - alias never_demote_particle? never_demote_non_dropping_particle? - alias never_demote_particle! never_demote_non_dropping_particle! + def always_demote_non_dropping_particle? + !!(options[:'demote-non-dropping-particle'] =~ /^(display-and-sort|always)$/i) + end - def always_demote_non_dropping_particle? - !!(options[:'demote-non-dropping-particle'] =~ /^(display-and-sort|always)$/i) - end + def always_demote_non_dropping_particle! + options[:'demote-non-dropping-particle'] = 'display-and-sort' + self + end - def always_demote_non_dropping_particle! - options[:'demote-non-dropping-particle'] = 'display-and-sort' - self - end + alias always_demote_particle? always_demote_non_dropping_particle? + alias always_demote_particle! always_demote_non_dropping_particle! - alias always_demote_particle? always_demote_non_dropping_particle? - alias always_demote_particle! always_demote_non_dropping_particle! + alias demote_particle! always_demote_non_dropping_particle! - alias demote_particle! always_demote_non_dropping_particle! + # Compares two names. The comparison is based on #sort_order_downcase + # + # @see #sort_order_downcase + # + # @param other [#sort_order_downcase] the other name + # @return [Fixnum,nil] -1, 0, or 1 depending on the result of the + # comparison; nil if the name cannot be compared to the passed-in object + def <=>(other) + return nil unless other.respond_to?(:sort_order_downcase) + sort_order_downcase <=> other.sort_order_downcase + end + + # @return [String] the name formatted according to the current options + def to_s + case + when literal? + literal.to_s + when static_order? + [family, given].compact.join(' ') + when !short_form? + case + when !sort_order? + [[given, dropping_particle, particle, family].compact_join(' '), + suffix].compact_join(comma_suffix? ? comma : ' ') - # Compares two names. The comparison is based on #sort_order_downcase. - def <=>(other) - return nil unless other.respond_to?(:sort_order_downcase) - sort_order_downcase <=> other.sort_order_downcase - end - - # Returns the Name as a String according to the Name's formatting options. - def to_s - case - when literal? - literal.to_s - - when static_order? - [family, given].compact.join(' ') - - when !short_form? - case - when !sort_order? - [[given, dropping_particle, particle, family].compact_join(' '), - suffix].compact_join(comma_suffix? ? comma : ' ') - - when !demote_particle? - [[particle, family].compact_join(' '), [given, - dropping_particle].compact_join(' '), suffix].compact_join(comma) - - else - [family, [given, dropping_particle, particle].compact_join(' '), - suffix].compact_join(comma) - end - - else - [particle, family].compact_join(' ') - end - end - - # Returns an ordered array of formatted name parts to be used for sorting. - def sort_order - case - when literal? - [literal.to_s.sub(sort_prefix, '')] - when never_demote_particle? - [[particle, family].compact_join(' '), dropping_particle, given, suffix].map(&:to_s) - else - [family, [particle, dropping_particle].compact_join(' '), given, suffix].map(&:to_s) - end - end - - def strip_markup - gsub(Variable.markup, '') - end - - def strip_markup! - gsub!(Variable.markup, '') - end - - # Returns the sort order array stripped off markup and downcased. - def sort_order_downcase - sort_order.map { |s| s.downcase.gsub(Variable.markup, '') } - end - - def inspect - "#<CiteProc::Name #{to_s.inspect}>" - end - - private - - attr_reader :sort_prefix - - end - - - + when !demote_particle? + [[particle, family].compact_join(' '), [given, + dropping_particle].compact_join(' '), suffix].compact_join(comma) - # Names are a CiteProc Variable containing an ordered list of Name objects. - # - # Names can be formatted using CSL formatting options. The available options - # and their default values are described below. - # - # * and: specifies the delimiter between the penultimate and the last name. - # Defaults to '&'. - # - # * delimiter: Soecifies the text string to seaparate the individual names. - # The default value is ', '. - # - # * delimiter-precedes-last: determines in which cases the delimiter used - # to delimit names is also used to separate the second to last and the - # last name in name lists. The possible values are: 'contextual' (default, - # the delimiter is only included for name lists with three or more names), - # 'always', and 'never'. - # - # * et-al-min and et-al-use-first: Together, these attributes control et-al - # abbreviation. When the number of names in a name variable matches or - # exceeds the number set on et-al-min, the rendered name list is truncated - # at the number of names set on et-al-use-first. If truncation occurs, the - # "et-al" term is appended to the names rendered (see also Et-al). With a - # single name (et-al-use-first="1"), the "et-al" term is preceded by a - # space (e.g. "Doe et al."). With multiple names, the "et-al" term is - # preceded by the name delimiter (e.g. "Doe, Smith, et al."). - # - # * et-al-subsequent-min / et-al-subsequent-use-first: The (optional) - # et-al-min and et-al-use-first attributes take effect for all cites and - # bibliographic entries. With the et-al-subsequent-min and - # et-al-subsequent-use-first attributes divergent et-al abbreviation rules - # can be specified for subsequent cites (cites referencing earlier cited - # items). - # - class Names < Variable - - @defaults = { - :and => '&', - :delimiter => ', ', - :'delimiter-precedes-last' => :contextual, - :'et-al' => 'et al.', - :'et-al-min' => 5, - :'et-al-use-first' => 3, - :'et-al-subsequent-min' => 5, - :'et-al-subsequent-use-first' => 3 - }.freeze - - class << self - - attr_reader :defaults - - def parse(names_string) - parse!(names_string) - rescue ParseError - nil - end - - def parse!(names_string) - fail 'not implemented yet' - end - - end - - - include Enumerable + else + [family, [given, dropping_particle, particle].compact_join(' '), + suffix].compact_join(comma) + end + else + [particle, family].compact_join(' ') + end + end + + # @return [Array<String>] an ordered array of formatted name parts to be used for sorting + def sort_order + case + when literal? + [literal.to_s.sub(sort_prefix, '')] + when never_demote_particle? + [[particle, family].compact_join(' '), dropping_particle, given, suffix].map(&:to_s) + else + [family, [particle, dropping_particle].compact_join(' '), given, suffix].map(&:to_s) + end + end + + # @return [String] the name as a string stripped off all markup + def strip_markup + gsub(Variable.markup, '') + end + + # @return [self] the name with all parts stripped off markup + def strip_markup! + gsub!(Variable.markup, '') + end + + # @return [Array<String>] the sort order array stripped off markup and downcased + def sort_order_downcase + sort_order.map { |s| s.downcase.gsub(Variable.markup, '') } + end + + # @return [String] a human-readable representation of the name object + def inspect + "#<CiteProc::Name #{to_s.inspect}>" + end + + private + + attr_reader :sort_prefix + + end + + + - attr_reader :options - - alias names value - - # Don't expose value/names writer - undef_method :value= - - # Delegate bang! methods to each name - Name.instance_methods(false).each do |m| - if m.to_s.end_with?('!') - define_method(m) do |*arguments, &block| - names.each do |name| - name.send(m, *arguments, &block) - end - self - end - end - end - - # Names quack sorta like an Array - def_delegators :names, :length, :empty?, :[], :join - - # Some delegators should return self - [:push, :<<, :unshift].each do |m| - define_method(m) do |*arguments, &block| - names.send(m, *arguments, &block) - self - end - end - - - def initialize(*arguments) - @options = Names.defaults.dup - super(arguments.flatten(1)) - end - - def initialize_copy(other) - @options, @value = other.options.dup, other.value.map(&:dup) - end - - def replace(values) - @value = [] - - values.each do |value| - case - when value.is_a?(Name) - @value << value - - when value.is_a?(Hash) - @value << Name.new(value) - - when value.respond_to?(:to_s) - @value << Name.parse!(value.to_s) - - else - raise TypeError, "failed to create names from #{value.inspect}" - end - end - - self - end - - # Returns true if the Names, if printed, will be abbreviated. - def abbreviate? - length >= options[:'et-al-min'].to_i - end - - # Returns true if the Names, if printed on subsequent cites, will be abbreviated. - def abbreviate_subsequent? - length >= options[:'et-al-subsequent-min'].to_i - end - - def delimiter - options[:delimiter] - end - - def last_delimiter - delimiter_precedes_last? ? [delimiter, connector].compact.join : connector - end - - # Returns whether or not the delimiter will be inserted between the penultimate and the last name. - def delimiter_precedes_last? - case - when delimiter_never_precedes_last? - false - when delimiter_always_precedes_last? - true - else - length > 2 - end - end - - def delimiter_always_precedes_last? - !!(options[:'delimiter-precedes-last'].to_s =~ /^always$/i) - end + # Represents a {Variable} containing an ordered list of {Name} + # objects. The names can be formatted using CSL formatting options (see + # {Names.defaults} for details). + class Names < Variable + + @defaults = { + :and => ' & ', + :delimiter => ', ', + :'delimiter-precedes-last' => :contextual, + :'et-al' => 'et al.', + :'et-al-min' => 5, + :'et-al-use-first' => 3, + :'et-al-subsequent-min' => 5, + :'et-al-subsequent-use-first' => 3 + }.freeze + + class << self - def delimiter_always_precedes_last! - options[:'delimiter-precedes-last'].to_s = :always - end + # @!attribute [r] defaults + # @example + # { + # :and => '&', + # # The delimiter between the penultimate and last name + # + # :delimiter => ', ', + # # The delimiter between the other names + # + # :'delimiter-precedes-last' => :contextual, + # # Determines in which cases the delimiter used to delimit names + # # is also used to separate the second to last and the last name + # # in name lists. The possible values are: 'contextual' (default, + # # the delimiter is only included for name lists with three or + # # more names), 'always', and 'never' + # + # :'et-al' => 'et al.', + # # The string used for the phrase 'and others' + # + # :'et-al-min' => 5, + # :'et-al-use-first' => 3, + # # Together, these attributes control et-al abbreviation. When + # # the number of names in a name variable matches or exceeds + # # the number set on et-al-min, the rendered name list is truncated + # # at the number of names set on et-al-use-first. If truncation + # # occurs, the "et-al" term is appended to the names rendered. + # # With a single name (et-al-use-first="1"), the "et-al" term is + # # preceded by a space (e.g. "Doe et al."). With multiple names, + # # the "et-al" term is preceded by the name delimiter (e.g. + # # "Doe, Smith, et al.") + # + # :'et-al-subsequent-min' => 5, + # :'et-al-subsequent-use-first' => 3 + # # See above. Abbreviation rules for subsequent cites (cites + # # referencing earlier cited items) + # } + # + # @return [Hash] the Names' default formatting options + attr_reader :defaults + + # Parses the passed-in string and returns a Names object. Behaves like + # parse but returns nil for bad input without raising an error. + # + # @see .parse! + # + # @param names [String] the name or names to be parsed + # @return [Names,nil] the parsed names + def parse(names) + parse!(names) + rescue ParseError + nil + end + + # Parses the passed-in string and returns a Names object. + # + # @param names [String] the name or names to be parsed + # @return [Names] the parsed names + # + # @raise [ParseError] if the string cannot be parsed. + def parse!(names) + new Namae.parse!(names) + rescue + raise ParseError, $!.message + end - alias delimiter_precedes_last! delimiter_always_precedes_last! - - def delimiter_never_precedes_last? - !!(options[:'delimiter-precedes-last'].to_s =~ /^never$/i) - end + end + + include Enumerable - def delimiter_never_precedes_last! - options[:'delimiter-precedes-last'].to_s = :never - end + # @!attribute [r] options + # @return [Hash] the current formatting options - def delimiter_never_precedes_last? - !!(options[:'delimiter-precedes-last'].to_s =~ /^contextual/i) - end + attr_reader :options + + alias names value + + # Don't expose value/names writer + undef_method :value= + + # Delegate bang! methods to each name + Name.instance_methods(false).each do |m| + if m.to_s.end_with?('!') + define_method(m) do |*arguments, &block| + names.each do |name| + name.send(m, *arguments, &block) + end + self + end + end + end + + # Names quack sorta like an Array + def_delegators :names, :length, :empty?, :[], :join - def delimiter_contextually_precedes_last! - options[:'delimiter-precedes-last'].to_s = :contextual - end + + # Some delegators should return self - - - # Returns the string used as connector between the penultimate and the last name. - def connector - options[:and] - end - - # Names are not numeric - def numeric? - false - end - - def each - if block_given? - names.each(&Proc.new) - self - else - to_enum - end - end - - def <=>(other) - return nil unless other.respond_to?(:names) - names <=> other.names - end - - def to_s - if length < 2 - names.join(last_delimiter) - else - [names[0...-1].join(delimiter), names[-1]].join(last_delimiter) - end - end - - def to_bibtex - map { |n| n.dup.sort_order! }.join(' and ') - end - - def to_citeproc - map(&:to_citeproc) - end - - def inspect - "#<CiteProc::Names #{to_s}>" - end - - end - + # @!method push(name) + # Appends the given name to the list of names. + # @param name [Name] a name + # @return [self] + # @!method unshift(name) + # Inserts the given name at the beginning of the list of names. + # @param name [Name] a name + # @return [self] + [:<<, :push, :unshift].each do |m| + define_method(m) do |*arguments, &block| + names.send(m, *arguments, &block) + self + end + end + + def initialize(*arguments) + @options = Names.defaults.dup + super(arguments.flatten(1)) + end + + def initialize_copy(other) + @options, @value = other.options.dup, other.value.map(&:dup) + end + + def replace(values) + @value = [] + + [*values].each do |value| + case + when value.is_a?(Name) + @value << value + when value.respond_to?(:each_pair), value.respond_to?(:to_hash) + @value << Name.new(value) + when value.respond_to?(:to_s) + begin + @value.concat Namae.parse!(value.to_s) + rescue + raise TypeError, $!.message + end + else + raise TypeError, "failed to create names from #{value.inspect}" + end + end + + self + end + + # @return [Fixnum] the maximum number of names that should be printed + def max_names + [length, options[:'et-al-use-first'].to_i.abs].min + end + + # @return [Boolean] whether or not the Names should be truncate + def truncate? + length >= options[:'et-al-min'].to_i.abs + end + + # @return [Boolean] whether ot not the Names, if printed on subsequent + # cites, should be truncated + def truncate_subsequent? + length >= options[:'et-al-subsequent-min'].to_i + end + + # @return [String] the delimiter between names + def delimiter + options[:delimiter] + end + + # @return [String] the delimiter between the penultimate and last name + # @see #connector + # @see #delimiter_precedes_last? + def last_delimiter + if delimiter_precedes_last? + [delimiter, connector].compact.join.squeeze(' ') + else + connector + end + end + + # @return [String] the delimiter between the last name printed name and + # the 'and others' term + def truncated_delimiter + max_names > 1 ? delimiter : ' ' + end + + # @return [Boolean] whether or not the delimiter will be inserted between + # the penultimate and the last name + def delimiter_precedes_last? + case + when delimiter_never_precedes_last? + false + when delimiter_always_precedes_last? + true + else + length > 2 + end + end + + # @return [Boolean] whether or not the should always be inserted between + # the penultimate and the last name + def delimiter_always_precedes_last? + !!(options[:'delimiter-precedes-last'].to_s =~ /^always$/i) + end + + # Set the :'delimiter-precedes-last' option to :always + # @return [self] self + def delimiter_always_precedes_last! + options[:'delimiter-precedes-last'] = :always + self + end + + alias delimiter_precedes_last! delimiter_always_precedes_last! + + + # @return [Boolean] whether or not the should never be inserted between + # the penultimate and the last name + def delimiter_never_precedes_last? + !!(options[:'delimiter-precedes-last'].to_s =~ /^never$/i) + end + + # Set the :'delimiter-precedes-last' option to :never + # @return [self] self + def delimiter_never_precedes_last! + options[:'delimiter-precedes-last'] = :never + self + end + + # @return [Boolean] whether or not the should be inserted between the + # penultimate and the last name depending on the number of names + def delimiter_contextually_precedes_last? + !!(options[:'delimiter-precedes-last'].to_s =~ /^contextual/i) + end + + # Set the :'delimiter-precedes-last' option to :contextual + # @return [self] self + def delimiter_contextually_precedes_last! + options[:'delimiter-precedes-last'] = :contextual + self + end + + # @return [String] the connector between the penultimate and the last name + def connector + options[:and] + end + + # @return [false] Names are non-numeric Variables + def numeric? + false + end + + # Calls a block once for each name. If no block is given, an enumerator + # is returned instead. + # + # @yieldparam name [Name] a name in the list + # @return [self,Enumerator] self or an enumerator if no block is given + def each + if block_given? + names.each(&Proc.new) + self + else + to_enum + end + end + + # Compares two lists of Names. + # @param other [(Name)] a list of names + # @return [Fixnum,nil] -1, 0, or 1 depending on the result of the + # comparison; or nil if the two objects cannot be compared + def <=>(other) + return nil unless other.respond_to?(:to_a) + to_a <=> other.to_a + end + + # Converts the list of names into a formatted string depending on the + # current formatting options. + # @return [String] the formatted list of names + def to_s + case + when truncate? + [names[0...max_names].join(delimiter), options[:'et-al']].join(truncated_delimiter) + when length < 2 + names.join(last_delimiter) + else + [names[0...-1].join(delimiter), names[-1]].join(last_delimiter) + end + end + + # @return [String] the names in a BibTeX-compatible format + def to_bibtex + map { |n| n.dup.sort_order! }.join(' and ') + end + + # @return [Array<Hash>] the list of names converted to hash objects + def to_citeproc + map(&:to_citeproc) + end + + # @return [String] a human-readable representation of the Names object + def inspect + "#<CiteProc::Names #{to_s.inspect}>" + end + + end + end