123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- # frozen_string_literal: true
-
- # Redmine - project management software
- # Copyright (C) 2006-2023 Jean-Philippe Lang
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- module Redmine
- class AssetPath
-
- attr_reader :paths, :prefix, :version
-
- def initialize(base_dir, paths, prefix=nil)
- @base_dir = base_dir
- @paths = paths
- @prefix = prefix
- @transition = Transition.new(src: Set.new, dest: Set.new)
- @version = Rails.application.config.assets.version
- end
-
- def update(transition_map:, assets:)
- each_file do |file, intermediate_path, logical_path|
- @transition.add_src intermediate_path, logical_path
- @transition.add_dest intermediate_path, logical_path
- asset = file.extname == '.css' ? Redmine::Asset.new(file, logical_path: logical_path, version: version, transition_map: transition_map)
- : Propshaft::Asset.new(file, logical_path: logical_path, version: version)
- assets[asset.logical_path.to_s] ||= asset
- end
- @transition.update(transition_map)
- nil
- end
-
- def each_file
- paths.each do |path|
- without_dotfiles(all_files_from_tree(path)).each do |file|
- relative_path = file.relative_path_from(path).to_s
- logical_path = prefix ? File.join(prefix, relative_path) : relative_path
- intermediate_path = Pathname.new("/#{prefix}").join(file.relative_path_from(@base_dir))
- yield file, intermediate_path, logical_path
- end
- end
- end
-
- private
-
- Transition = Struct.new(:src, :dest, keyword_init: true) do
-
- def add_src(file, logical_path)
- src.add path_pair(file, logical_path) if file.extname == '.css'
- end
-
- def add_dest(file, logical_path)
- return if file.extname == '.js' || file.extname == '.map'
- # No parent-child directories are needed in dest.
- dirname = file.dirname
- if child = dest.find{|d| child_path? dirname, d[0]}
- dest.delete child
- dest.add path_pair(file, logical_path)
- elsif !dest.any?{|d| parent_path? dirname, d[0]}
- dest.add path_pair(file, logical_path)
- end
- end
-
- def path_pair(file, logical_path)
- [file.dirname, Pathname.new("/#{logical_path}").dirname]
- end
-
- def parent_path?(path, other)
- return nil if other == path
- path.ascend.any?{|v| v == other}
- end
-
- def child_path?(path, other)
- return nil if path == other
- other.ascend.any?{|v| v == path}
- end
-
- def update(transition_map)
- product = src.to_a.product(dest.to_a).select{|t| t[0] != t[1]}
- maps = product.map do |t|
- AssetPathMap.new(src: t[0][0], dest: t[1][0], logical_src: t[0][1], logical_dest: t[1][1])
- end
- maps.each do |m|
- if m.before != m.after
- transition_map[m.dirname] ||= {}
- transition_map[m.dirname][m.before] = m.after
- end
- end
- end
- end
-
- AssetPathMap = Struct.new(:src, :dest, :logical_src, :logical_dest, keyword_init: true) do
-
- def dirname
- key = logical_src.to_s.sub('/', '')
- key == '' ? '.' : key
- end
-
- def before
- dest.relative_path_from(src).to_s
- end
-
- def after
- logical_dest.relative_path_from(logical_src).to_s
- end
- end
-
- def without_dotfiles(files)
- files.reject { |file| file.basename.to_s.starts_with?(".") }
- end
-
- def all_files_from_tree(path)
- path.children.flat_map { |child| child.directory? ? all_files_from_tree(child) : child }
- end
- end
-
- class AssetLoadPath < Propshaft::LoadPath
-
- attr_reader :extension_paths, :default_asset_path, :transition_map
-
- def initialize(config)
- @extension_paths = config.redmine_extension_paths
- @default_asset_path = config.redmine_default_asset_path
- super(config.paths, version: config.version)
- end
-
- def asset_files
- Enumerator.new do |y|
- Rails.logger.info all_paths
- all_paths.each do |path|
- next unless path.exist?
- without_dotfiles(all_files_from_tree(path)).each do |file|
- y << file
- end
- end
- end
- end
-
- def assets_by_path
- merge_required = @cached_assets_by_path == nil
- super
- if merge_required
- @transition_map = {}
- default_asset_path.update(assets: @cached_assets_by_path, transition_map: transition_map)
- extension_paths.each do |asset_path|
- # Support link from extension assets to assets in the application
- default_asset_path.each_file do |file, intermediate_path, logical_path|
- asset_path.instance_eval { @transition.add_dest intermediate_path, logical_path }
- end
- asset_path.update(assets: @cached_assets_by_path, transition_map: transition_map)
- end
- end
- @cached_assets_by_path
- end
-
- def cache_sweeper
- @cache_sweeper ||= begin
- exts_to_watch = Mime::EXTENSION_LOOKUP.map(&:first)
- files_to_watch = Array(all_paths).collect { |dir| [ dir.to_s, exts_to_watch ] }.to_h
- Rails.application.config.file_watcher.new([], files_to_watch) do
- clear_cache
- end
- end
- end
-
- def all_paths
- [paths, default_asset_path.paths, extension_paths.map{|path| path.paths}].flatten.compact
- end
-
- def clear_cache
- @transition_map = nil
- super
- end
- end
-
- class Asset < Propshaft::Asset
- def initialize(file, logical_path:, version:, transition_map:)
- @transition_map = transition_map
- super(file, logical_path: logical_path, version: version)
- end
-
- def content
- if conversion = @transition_map[logical_path.dirname.to_s]
- convert_path super, conversion
- else
- super
- end
- end
-
- ASSET_URL_PATTERN = /(url\(\s*["']?([^"'\s)]+)\s*["']?\s*\))/
-
- def convert_path(input, conversion)
- input.gsub(ASSET_URL_PATTERN) do |matched|
- conversion.each do |key, val|
- matched.sub!(key, val)
- end
- matched
- end
- end
- end
- end
|