Module | Merb::Slices::ModuleMixin |
In: |
merb-slices/lib/merb-slices/module_mixin.rb
|
# File merb-slices/lib/merb-slices/module_mixin.rb, line 8 8: def self.extended(slice_module) 9: slice_module.meta_class.module_eval do 10: attr_accessor :identifier, :identifier_sym, :root, :file 11: attr_accessor :description, :version, :author 12: end 13: end
Stub activation hook - runs after AfterAppLoads BootLoader.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 28 28: def activate; end
Return all application path component types
@return <Array[Symbol]> Component types.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 257 257: def app_components 258: [:view, :model, :controller, :helper, :mailer, :part] 259: end
Retrieve the absolute path to a app-level directory.
@param type<Symbol> The type of path to retrieve directory for, e.g. :view.
@return <String> The directory for the requested type.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 147 147: def app_dir_for(type) self.app_paths[type].first end
Construct an app-level path.
@param <Symbol> The type of component. @param *segments<Array[to_s]> Path segments to append.
@return <String>
A path within the host application, with added segments.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 183 183: def app_path_for(type, *segments) 184: prefix = type.is_a?(Symbol) ? self.app_dir_for(type) : self.app_dir_for(:root) / type 185: File.join(prefix, *segments) 186: end
Clone all files from the slice to their app-level location; this will also copy /lib, causing merb-slices to pick up the slice there.
@return <Array[Array]>
Array of two arrays, one for all copied files, the other for overrides that may have been preserved to resolve collisions.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 314 314: def clone_slice! 315: app_slice_root = app_dir_for(:root) 316: copied, duplicated = [], [] 317: manifest.each do |source, relative_path| 318: mirror_file(source, app_slice_root / relative_path, copied, duplicated) 319: end 320: [copied, duplicated] 321: end
The app-level load paths that have been used when the slice was loaded.
This may be a subset of app_paths, which includes any path to look for.
@return <Array[String]> Application load paths (with glob pattern)
# File merb-slices/lib/merb-slices/module_mixin.rb, line 105 105: def collected_app_paths 106: @collected_app_paths ||= [] 107: end
The slice-level load paths that have been used when the slice was loaded.
This may be a subset of app_paths, which includes any path to look for.
@return <Array[String]> load paths (with glob pattern)
# File merb-slices/lib/merb-slices/module_mixin.rb, line 96 96: def collected_slice_paths 97: @collected_slice_paths ||= [] 98: end
Stub deactivation method - not triggered automatically.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 31 31: def deactivate; end
Retrieve the absolute path to a slice-level directory.
@param type<Symbol> The type of path to retrieve directory for, e.g. :view.
@return <String> The absolute path for the requested type.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 135 135: def dir_for(type) self.slice_paths[type].first end
Stub initialization hook - runs before AfterAppLoads BootLoader.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 25 25: def init; end
Load slice and it‘s classes located in the slice-level load paths.
Assigns collected_slice_paths and collected_app_paths, then loads the collected_slice_paths and triggers the loaded hook method.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 77 77: def load_slice 78: # load application.rb (or similar) for thin slices 79: Merb::Slices::Loader.load_file self.dir_for(:application) if File.file?(self.dir_for(:application)) 80: # assign all relevant paths for slice-level and app-level 81: self.collect_load_paths 82: # load all slice-level classes from paths 83: Merb::Slices::Loader.load_classes self.collected_slice_paths 84: # call hook if available 85: self.loaded if self.respond_to?(:loaded) 86: Merb.logger.info!("Loaded slice '#{self}' ...") 87: rescue => e 88: Merb.logger.warn!("Failed loading #{self} (#{e.message})") 89: end
Return all *.rb files from valid component paths
@return <Array> Full paths to loadable ruby files.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 246 246: def loadable_files 247: app_components.inject([]) do |paths, type| 248: paths += Dir[dir_for(type) / '**/*.rb'] if slice_paths.key?(type) 249: paths += Dir[app_dir_for(type) / '**/*.rb'] if app_paths.key?(type) 250: paths 251: end 252: end
Stub classes loaded hook - runs before LoadClasses BootLoader right after a slice‘s classes have been loaded internally.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 22 22: def loaded; end
Return all slice files mapped from the source to their relative path
@param type<Symbol> Which type to use; defaults to :root (all) @return <Array[Array]> An array of arrays [abs. source, relative dest.]
# File merb-slices/lib/merb-slices/module_mixin.rb, line 296 296: def manifest(type = :root) 297: files = if type == :root 298: Dir.glob(self.root / "**/*") 299: elsif slice_paths.key?(type) 300: glob = ((type == :view) ? view_templates_glob : glob_for(type) || "**/*") 301: Dir.glob(dir_for(type) / glob) 302: else 303: [] 304: end 305: files.map { |source| [source, source.relative_path_from(root)] } 306: end
Copies all files from mirrored_components to their app-level location
This includes application and public components.
@return <Array[Array]>
Array of two arrays, one for all copied files, the other for overrides that may have been preserved to resolve collisions.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 348 348: def mirror_all! 349: mirror_files_for mirrored_components + mirrored_public_components 350: end
Copies all application files from mirrored_components to their app-level location
@return <Array[Array]>
Array of two arrays, one for all copied files, the other for overrides that may have been preserved to resolve collisions.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 366 366: def mirror_app! 367: components = mirrored_app_components 368: components << :application if application_file? 369: mirror_files_for components 370: end
Copy files from specified component path types to their app-level location
App-level overrides are preserved by creating duplicates before writing gem-level files. Because of their _override postfix they will load after their original implementation. In the case of views, this won‘t work, but the user override is preserved nonetheless.
@return <Array[Array]>
Array of two arrays, one for all copied files, the other for overrides that may have been preserved to resolve collisions.
@note Only explicitly defined component paths will be taken into account to avoid
cluttering the app's Merb.root by mistake - since undefined paths default to that.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 393 393: def mirror_files_for(*types) 394: seen, copied, duplicated = [], [], [] # keep track of files we copied 395: types.flatten.each do |type| 396: if app_paths.key?(type) && (source_path = dir_for(type)) && (destination_path = app_dir_for(type)) 397: manifest(type).each do |source, relative_path| # this relative path is not what we need here 398: next if seen.include?(source) 399: mirror_file(source, destination_path / source.relative_path_from(source_path), copied, duplicated) 400: seen << source 401: end 402: end 403: end 404: [copied, duplicated] 405: end
Copies all application files from mirrored_components to their app-level location
@return <Array[Array]>
Array of two arrays, one for all copied files, the other for overrides that may have been preserved to resolve collisions.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 377 377: def mirror_public! 378: mirror_files_for mirrored_public_components 379: end
Copies all files from the (optional) stubs directory to their app-level location
@return <Array[Array]>
Array of two arrays, one for all copied files, the other for overrides that may have been preserved to resolve collisions.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 357 357: def mirror_stubs! 358: mirror_files_for :stub 359: end
Return all application path component types to mirror
@return <Array[Symbol]> Component types.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 281 281: def mirrored_app_components 282: mirrored_components & app_components 283: end
Return all path component types to mirror
If config option :mirror is set return a subset, otherwise return all types.
@return <Array[Symbol]> Component types.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 273 273: def mirrored_components 274: all = slice_paths.keys 275: config[:mirror].is_a?(Array) ? config[:mirror] & all : all 276: end
Return all public path component types to mirror
@return <Array[Symbol]> Component types.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 288 288: def mirrored_public_components 289: mirrored_components & public_components 290: end
Return all public path component types
@return <Array[Symbol]> Component types.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 264 264: def public_components 265: [:stylesheet, :javascript, :image] 266: end
Retrieve the relative path to a public directory.
@param type<Symbol> The type of path to retrieve directory for, e.g. :view.
@return <String> The relative path to the public directory for the requested type.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 159 159: def public_dir_for(type) 160: dir = type.is_a?(Symbol) ? self.app_dir_for(type) : self.app_dir_for(:public) / type 161: dir = dir.relative_path_from(Merb.dir_for(:public)) rescue '.' 162: dir == '.' ? '/' : "/#{dir}" 163: end
Construct a path relative to the public directory
@param <Symbol> The type of component. @param *segments<Array[to_s]> Path segments to append.
@return <String>
A path relative to the public directory, with added segments.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 172 172: def public_path_for(type, *segments) 173: File.join(self.public_dir_for(type), *segments) 174: end
This is the core mechanism for setting up your app-level layout.
@param type<Symbol> The type of path being registered (i.e. :view) @param path<String> The full path @param file_glob<String>
A glob that will be used to autoload files under the path. Defaults to "**/*.rb".
@note The :public path is adapted when the slice is run from bin/slice.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 227 227: def push_app_path(type, path, file_glob = "**/*.rb") 228: enforce!(type => Symbol) 229: if type == :public && standalone? && $SLICE_MODULE 230: path.gsub!(/\/slices\/#{self.identifier}$/, '') 231: end 232: app_paths[type] = [path, file_glob] 233: end
This is the core mechanism for setting up your slice-level layout.
@param type<Symbol> The type of path being registered (i.e. :view) @param path<String> The full path @param file_glob<String>
A glob that will be used to autoload files under the path. Defaults to "**/*.rb".
# File merb-slices/lib/merb-slices/module_mixin.rb, line 206 206: def push_path(type, path, file_glob = "**/*.rb") 207: enforce!(type => Symbol) 208: slice_paths[type] = [path, file_glob] 209: end
Stub that gets triggered when a slice has been registered.
@note This is rarely needed but still provided for edge cases.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 18 18: def registered; end
Removes given types of application components from app-level load path this slice uses for autoloading.
@param *args<Array[Symbol]> Components names, for instance, :views, :models
# File merb-slices/lib/merb-slices/module_mixin.rb, line 239 239: def remove_app_paths(*args) 240: args.each { |arg| self.app_paths.delete(arg) } 241: end
Removes given types of application components from slice-level load path this slice uses for autoloading.
@param *args<Array[Symbol]> Components names, for instance, :views, :models
# File merb-slices/lib/merb-slices/module_mixin.rb, line 215 215: def remove_paths(*args) 216: args.each { |arg| self.slice_paths.delete(arg) } 217: end
Check if there have been any routes setup.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 37 37: def routed? 38: self.named_routes && !self.named_routes.empty? 39: end
This sets up the default slice-level and app-level structure.
You can create your own structure by implementing setup_structure and using the push_path and push_app_paths. By default this setup matches what the merb-gen slice generator creates.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 412 412: def setup_default_structure! 413: self.push_app_path(:root, Merb.root / 'slices' / self.identifier, nil) 414: 415: self.push_path(:stub, root_path('stubs'), nil) 416: self.push_app_path(:stub, app_dir_for(:root), nil) 417: 418: self.push_path(:application, root_path('app'), nil) 419: self.push_app_path(:application, app_dir_for(:root) / 'app', nil) 420: 421: app_components.each do |component| 422: self.push_path(component, dir_for(:application) / "#{component}s") 423: self.push_app_path(component, app_dir_for(:application) / "#{component}s") 424: end 425: 426: self.push_path(:public, root_path('public'), nil) 427: self.push_app_path(:public, Merb.dir_for(:public) / 'slices' / self.identifier, nil) 428: 429: public_components.each do |component| 430: self.push_path(component, dir_for(:public) / "#{component}s", nil) 431: self.push_app_path(component, app_dir_for(:public) / "#{component}s", nil) 432: end 433: end
Stub to setup routes inside the host application.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 34 34: def setup_router(scope); end
Construct a slice-level path.
@param <Symbol> The type of component. @param *segments<Array[to_s]> Path segments to append.
@return <String>
A path within the slice source (Gem), with added segments.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 195 195: def slice_path_for(type, *segments) 196: prefix = type.is_a?(Symbol) ? self.dir_for(type) : self.dir_for(:root) / type 197: File.join(prefix, *segments) 198: end
Whether we‘re in an application or running from the slice dir itself.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 42 42: def standalone? 43: Merb.root == self.root 44: end
Return a value suitable for routes/urls.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 47 47: def to_param 48: self.identifier 49: end
Unpack a subset of files from the slice to their app-level location; this will also copy /lib, causing merb-slices to pick up the slice there.
@return <Array[Array]>
Array of two arrays, one for all copied files, the other for overrides that may have been preserved to resolve collisions.
@note Files for the :stub component type are skipped.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 331 331: def unpack_slice! 332: app_slice_root = app_dir_for(:root) 333: copied, duplicated = mirror_public! 334: manifest.each do |source, relative_path| 335: next unless unpack_file?(relative_path) 336: mirror_file(source, app_slice_root / relative_path, copied, duplicated) 337: end 338: [copied, duplicated] 339: end
Predicate method to check if the :application component is a file
# File merb-slices/lib/merb-slices/module_mixin.rb, line 504 504: def application_file? 505: File.file?(dir_for(:application) / glob_for(:application)) 506: end
Collect slice-level and app-level load paths to load from.
@param modify_load_path<Boolean>
Whether to add certain paths to $LOAD_PATH; defaults to true.
@param push_merb_path<Boolean>
Whether to add app-level paths using Merb.push_path; defaults to true.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 443 443: def collect_load_paths(modify_load_path = true, push_merb_path = true) 444: self.collected_slice_paths.clear; self.collected_app_paths.clear 445: Merb.push_path("#{self.name.snake_case}_file""#{self.name.snake_case}_file", File.dirname(self.file), File.basename(self.file)) 446: self.collected_app_paths << self.file 447: self.slice_paths.each do |component, path| 448: if File.directory?(component_path = path.first) 449: $LOAD_PATH.unshift(component_path) if modify_load_path && component.in?(:model, :controller, :lib) && !$LOAD_PATH.include?(component_path) 450: # slice-level component load path - will be preceded by application/app/component - loaded next by Setup.load_classes 451: self.collected_slice_paths << path.first / path.last if path.last 452: # app-level component load path (override) path - loaded by BootLoader::LoadClasses 453: if (app_glob = self.app_glob_for(component)) && File.directory?(app_component_dir = self.app_dir_for(component)) 454: self.collected_app_paths << app_component_dir / app_glob 455: Merb.push_path("#{self.name.snake_case}_#{component}""#{self.name.snake_case}_#{component}", app_component_dir, app_glob) if push_merb_path 456: end 457: end 458: end 459: end
Helper method to copy a source file to destination while resolving any conflicts.
@param source<String> The source path. @param dest<String> The destination path. @param copied<Array> Keep track of all copied files - relative paths. @param duplicated<Array> Keep track of all duplicated files - relative paths. @param postfix<String> The postfix to use for resolving conflicting filenames.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 468 468: def mirror_file(source, dest, copied = [], duplicated = [], postfix = '_override') 469: base, rest = split_name(source) 470: dst_dir = File.dirname(dest) 471: dup_path = dst_dir / "#{base}#{postfix}.#{rest}" 472: if File.file?(source) 473: FileUtils.mkdir_p(dst_dir) unless File.directory?(dst_dir) 474: if File.exists?(dest) && !File.exists?(dup_path) && !FileUtils.identical?(source, dest) 475: # copy app-level override to *_override.ext 476: FileUtils.copy_entry(dest, dup_path, false, false, true) 477: duplicated << dup_path.relative_path_from(Merb.root) 478: end 479: # copy gem-level original to location 480: if !File.exists?(dest) || (File.exists?(dest) && !FileUtils.identical?(source, dest)) 481: FileUtils.copy_entry(source, dest, false, false, true) 482: copied << dest.relative_path_from(Merb.root) 483: end 484: end 485: end
Split a file name so a postfix can be inserted
@return <Array[String]>
The first element will be the name up to the first dot, the second will be the rest.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 517 517: def split_name(name) 518: file_name = File.basename(name) 519: mres = /^([^\/\.]+)\.(.+)$/i.match(file_name) 520: mres.nil? ? [file_name, ''] : [mres[1], mres[2]] 521: end
Predicate method to check if a file should be taken into account when unpacking files
By default any public component paths and stubs are skipped; additionally you can set the :skip_files in the slice‘s config for other relative paths to skip.
@param file<String> The relative path to test. @return <TrueClass,FalseClass> True if the file may be mirrored.
# File merb-slices/lib/merb-slices/module_mixin.rb, line 494 494: def unpack_file?(file) 495: @mirror_exceptions_regexp ||= begin 496: skip_paths = (mirrored_public_components + [:stub]).map { |type| dir_for(type).relative_path_from(self.root) } 497: skip_paths += config[:skip_files] if config[:skip_files].is_a?(Array) 498: Regexp.new("^(#{skip_paths.join('|')})") 499: end 500: not file.match(@mirror_exceptions_regexp) 501: end