kigster/arduino-library

View on GitHub
lib/arduino/library/model.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require_relative 'types'
require_relative 'utilities'
require_relative 'instance_methods'
require_relative 'finder'
require 'json'

module Arduino
  module Library
    # This class represents a single entry into the library-index.json file,
    # in other words — a `library.properties` file.
    class Model < Dry::Struct
      include Comparable

      # noinspection RubyResolve
      constructor_type :schema

      Types::LIBRARY_PROPERTIES.each_pair do |field, type|
        self.attribute field, eval(type)
      end

      SEARCHABLE_FIELDS = %i(name archiveFileName checksum)

      # Instance Methods

      # Convert a version such as '1.44.3' into a number '1044003' for easy
      # sorting and comparison.
      def version_to_i
        if version
          first, second, third = version.split(/\./).map(&:to_i)
          10 ** 6 * (first || 0) + 10 ** 3 * (second || 0) + (third || 0)
        else
          0
        end
      rescue
        0
      end

      # @returns true if the library has enough data to be searched in the db
      def partial?
        self.url.nil? && SEARCHABLE_FIELDS.any? { |field| self.send(field) }
      end

      def <=>(another)
        if self.name == another.name
          self.version_to_i <=> another.version_to_i
        else
          self.name <=> another.name
        end
      end

      # Class Methods

      class << self
        include Utilities
        include InstanceMethods

        attr_writer :database

        def from_hash(hash)
          new(Types.schema[hash])
        end

        def from_json(json)
          from_hash(JSON.load(json))
        end

        def from_json_file(file_or_url)
          file = read_file_or_url(file_or_url)
          from_json(file.read)
        end

        def from_properties_file(file_or_url)
          raise "File #{file_or_url} does not exist?" unless File.exist?(file_or_url)
          Presenters::Properties.from(file_or_url)
        end

        def database
          @database ||= DefaultDatabase.instance
        end

        def find(**opts)
          database.search(**opts)
        end

        # @param [Object] source — file name or a URL to JSON or .properties file
        #
        # ## Searching
        #
        # #### Database
        #
        # Searching requires a database, which can either be set via
        #
        #       Arduino::Library::Model.database = Database.from(file)
        #
        # otherwise it defaults to the default database, +DefaultDatabase.instance+.
        #
        # @param [Hash] opts   — search parameters to the current database
        #
        # #### Query
        #
        # +opts+ is a Hash that you can use to pass attributes with values, any
        # number of them. All matching results are returned as models from the
        # current database.
        #
        #   name: 'AudioZero'
        #   author: /konstantin/i              - regexp supported
        #   architectures: [ 'avr' ]           - array is matched if it's a subset
        #   version: proc do |value|           — or a proc for max flexibility
        #     value.start_with?('1.') )
        #   ends
        #
        # @return [Model | Array<Model> ] — array for search, otherwise a model
        def from(source = nil, **opts)
          model = model_by_class(source, **opts)
          if model&.partial?
            Finder.find_library(model)
          else
            model
          end
        end

        private

        def model_by_class(source, **opts)
          case source
            when Hash
              from_hash(source)
            when String
              if source =~ /^{/m
                from_json(source)
              elsif File.exist?(source)
                if source =~ /\.json(\.gz)?$/i
                  from_json_file(source)
                elsif source =~ /\.properties(\.gz)?$/i
                  from_properties_file(source)
                end
              end
            when NilClass
              if opts
                if SEARCHABLE_FIELDS.any? { |field| opts[field] }
                  results = search(**opts)
                  if results
                    results.sort.last
                  else
                    nil
                  end
                else
                  from_hash(opts)
                end
              end
            else
              nil
          end
        end
      end
    end
  end
end


require_relative 'presenters/properties'