class Jimmy::FileMap

Maps a directory of files to schemas with URIs. Can be used as a URI resolver with {SchemerFactory}.

Given +~/schemas/user.rb+ as a schema file:

file_map = FileMap.new('~/schemas', 'http://example.com/schemas/', suffix: '.json')
file_map.resolve('user.json') # => SchemaWithURI

Calling {SchemaWithURI#as_json} on the above will include the full ID http://example.com/schemas/user.json# in the +$id+ key.

Including the suffix in the call to {FileMap#resolve} is optional.

If you initialize a {FileMap} with +live: true+, files will be loaded lazily and repeatedly, every time {FileMap#resolve} or {FileMap#index} is called. This is intended as convenience for development environments.

Constants

DEFAULT_LOADERS

Public Class Methods

new( base_dir, base_uri = nil, live: false, loaders: DEFAULT_LOADERS, suffix: '' ) click to toggle source

@param [Pathname, String] base_dir The directory that should map to the

given URI.

@param [Json::URI, URI, String] base_uri The URI that should resolve to

the given directory. If omitted, a +file://+ URI will be used for the
absolute path of the given directory.

@param [true, false] live When true, schemas are not stored in memory, and

are instead live-reloaded on demand. Typically only useful for
development environments.

@param [Hash{String => call}] loaders Loaders for one or more file types.

By default, +.json+ files are parsed as-is, and +.rb+ files are
evaluated in the context of a {Loaders::Ruby} instance, which
exposes the methods in {Macros}.

@param [String] suffix Optional suffix that will be appended to each

schema ID. This can be set to +".json"+ if, for example, you want your
schemas to have +.json+ suffixes when you serve them over HTTP.
# File lib/jimmy/file_map.rb, line 47
def initialize(
  base_dir,
  base_uri = nil,
  live:    false,
  loaders: DEFAULT_LOADERS,
  suffix:  ''
)
  @dir = Pathname(base_dir).realpath
  unless @dir.directory? && @dir.readable?
    raise Error::BadArgument, 'Expected a readable directory'
  end

  base_uri ||= uri_for_dir
  @uri = Json::URI.new(base_uri.to_s, container: true)

  @live    = live
  @loaders = loaders
  @suffix  = suffix

  index unless live
end

Public Instance Methods

[](uri)
Alias for: resolve
index() click to toggle source

Get an index of all schemas in the file map's directory. @return [Jimmy::Index]

# File lib/jimmy/file_map.rb, line 87
def index
  return @index if @index

  index = build_index
  @index = index unless live?

  index
end
live?() click to toggle source

Returns true if live-reloading is enabled. @return [true, false]

# File lib/jimmy/file_map.rb, line 98
def live?
  @live
end
resolve(uri) click to toggle source

Given a URI, either absolute or relative to the file map's base URI, returns a {SchemaWithURI} if a matching schema is found. @param [Json::URI, URI, String] uri @return [Jimmy::SchemaWithURI, nil]

# File lib/jimmy/file_map.rb, line 73
def resolve(uri)
  uri          = make_child_uri(uri)
  absolute_uri = @uri + uri

  return index.resolve(absolute_uri) unless live?

  schema = load_file(path_for_uri uri)&.get_fragment(uri.fragment)
  schema && SchemaWithURI.new(absolute_uri, schema)
end
Also aliased as: []

Private Instance Methods

build_index() click to toggle source
# File lib/jimmy/file_map.rb, line 136
def build_index
  index = Index.new

  Dir[@dir + "**/*.{#{@loaders.keys.join ','}}"].sort.each do |file|
    relative_uri = relative_uri_for_file(file)
    uri          = @uri + relative_uri

    index[uri] = @loaders.fetch(File.extname(file)[1..]).call(file)
  end

  index
end
fs_to_rfc3968(path) click to toggle source
# File lib/jimmy/file_map.rb, line 158
def fs_to_rfc3968(path)
  path
    .to_s
    .split(File::SEPARATOR)
    .map { |p| URI.encode_www_form_component p.sub(/:\z/, '') }
    .join('/')
end
load_file(file_base) click to toggle source
# File lib/jimmy/file_map.rb, line 104
def load_file(file_base)
  @loaders.each do |ext, loader|
    file = Pathname("#{file_base}.#{ext}")
    next unless file.file?
    return loader.call(file) if file.readable?

    warn "Jimmy cannot read #{file}"
  end
  nil
end
make_child_uri(uri) click to toggle source
# File lib/jimmy/file_map.rb, line 125
def make_child_uri(uri)
  uri = @uri.route_to(@uri + uri)

  unless uri.host.nil? && !uri.path.match?(%r{\A(\.\.|/)})
    raise Error::BadArgument, 'The given URI is outside this FileMap'
  end

  uri.path += @suffix unless uri.path.end_with? @suffix
  uri
end
path_for_uri(uri) click to toggle source
# File lib/jimmy/file_map.rb, line 119
def path_for_uri(uri)
  path  = uri.path[0..(-@suffix.length - 1)]
  parts = path.split('/').map(&URI.method(:decode_www_form_component))
  @dir.join(*parts)
end
relative_uri_for_file(file) click to toggle source
# File lib/jimmy/file_map.rb, line 149
def relative_uri_for_file(file)
  path = Pathname(file)
    .relative_path_from(@dir)
    .to_s
    .sub(/\.[^.]+\z/, '')

  Json::URI.new fs_to_rfc3968(path) + @suffix
end
uri_for_dir() click to toggle source
# File lib/jimmy/file_map.rb, line 115
def uri_for_dir
  Json::URI.new 'file://' + fs_to_rfc3968(@dir)
end