gem_override_marker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ require "diff/lcs"
2
+ require "diff/lcs/hunk"
3
+
4
+ module GemOverrideMarker
5
+ # 本家ソースと override の unified diff を生成する。
6
+ class DiffGenerator
7
+ # 前後に表示するコンテキスト行数。
8
+ CONTEXT_LINES = 3
9
+
10
+ # 2つのソース文字列の unified diff を返す。差分が無ければ空文字。
11
+ def self.unified(old_source, new_source, old_label:, new_label:)
12
+ old_lines = old_source.lines.map(&:chomp)
13
+ new_lines = new_source.lines.map(&:chomp)
14
+
15
+ changes = Diff::LCS.diff(old_lines, new_lines)
16
+ return "" if changes.empty?
17
+
18
+ output = +"--- #{old_label}\n+++ #{new_label}\n"
19
+ # diff-lcs の Hunk はコンテキスト行ぶんのオフセットを file_length_difference として
20
+ # hunk 間で累積する必要がある。これを引き継がず 0 固定にすると、離れた hunk 同士で
21
+ # 内部オフセットが破綻して @data_old[lo..hi] が範囲外 (nil) になりクラッシュする。
22
+ # コンテキストが重なる hunk は直前の hunk に merge し、重ならなければ flush して出力する。
23
+ file_length_difference = 0
24
+ last_hunk = nil
25
+ changes.each do |hunk_changes|
26
+ hunk = Diff::LCS::Hunk.new(old_lines, new_lines, hunk_changes, CONTEXT_LINES, file_length_difference)
27
+ file_length_difference = hunk.file_length_difference
28
+
29
+ if last_hunk && hunk.merge(last_hunk)
30
+ # merge 済み (overlaps した) ので last_hunk は出力せず hunk に取り込まれている
31
+ elsif last_hunk
32
+ output << last_hunk.diff(:unified) << "\n"
33
+ end
34
+ last_hunk = hunk
35
+ end
36
+ output << last_hunk.diff(:unified, true) << "\n" if last_hunk
37
+ output
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,75 @@
1
+ module GemOverrideMarker
2
+ # gem のインストールパス解決と、Gemfile.lock 上の実バージョン取得を担う。
3
+ #
4
+ # diff の比較対象となる本家ソースは、`{gem名}-{バージョン}/{gem内パス}` を
5
+ # 実環境の gem インストールパス (Gem.path 配下の gems/) から解決する。
6
+ # マーカーに書かれたバージョンを正とするが、アップグレード検討時などは
7
+ # 明示的に別バージョンを指定できる。
8
+ class GemSource
9
+ class SourceNotFoundError < StandardError; end
10
+
11
+ # マーカー対象ファイル (本家ソース) の絶対パスを返す。
12
+ # 比較対象の版はマーカーに書かれた版を使う。
13
+ def self.resolve_file(marker)
14
+ resolve_path(marker.gem_name, marker.version, marker.path)
15
+ end
16
+
17
+ # gem名・版・gem内パスから本家ソースの絶対パスを返す。
18
+ # マーカーに縛られず任意の版を解決できるため、本家2版間の diff
19
+ # (upstream_diff) で from / to 双方の本家ソースを得るのに使う。
20
+ def self.resolve_path(gem_name, version, path)
21
+ dir = gem_dir(gem_name, version)
22
+ unless dir
23
+ raise SourceNotFoundError,
24
+ "#{gem_name}-#{version} not found in gem install paths"
25
+ end
26
+
27
+ file = File.join(dir, path)
28
+ unless File.exist?(file)
29
+ raise SourceNotFoundError, "gem source not found: #{file}"
30
+ end
31
+
32
+ file
33
+ end
34
+
35
+ # {gem名}-{バージョン} の gem ディレクトリを Gem.path 配下から探す。
36
+ # 見つからなければ nil。
37
+ def self.gem_dir(gem_name, version)
38
+ gem_paths.each do |base|
39
+ candidate = File.join(base, "gems", "#{gem_name}-#{version}")
40
+ return candidate if Dir.exist?(candidate)
41
+ end
42
+ nil
43
+ end
44
+
45
+ # Gemfile.lock 上で実際に解決されている gem のバージョンを返す。
46
+ # マーカーの版とこの版がズレていれば追従漏れの可能性がある (check で使う)。
47
+ #
48
+ # lockfile_path を省略すると Bundler が解決したプロジェクトの Gemfile.lock を
49
+ # 使う。Bundler は BUNDLE_GEMFILE やカレントからの探索で Gemfile の場所を
50
+ # 自動解決するため、利用側はパスを意識しなくてよい。
51
+ def self.locked_version(gem_name, lockfile_path = nil)
52
+ path = lockfile_path || default_lockfile_path
53
+ return nil if path.nil? || !File.exist?(path)
54
+
55
+ require "bundler"
56
+ parser = Bundler::LockfileParser.new(File.read(path))
57
+ spec = parser.specs.find { |s| s.name == gem_name }
58
+ spec&.version&.to_s
59
+ end
60
+
61
+ # Bundler が解決したプロジェクトの Gemfile.lock の絶対パス。
62
+ # Bundler が使えない (非 Bundler 環境) 場合はカレントの Gemfile.lock を試す。
63
+ def self.default_lockfile_path
64
+ require "bundler"
65
+ Bundler.default_lockfile.to_s
66
+ rescue StandardError
67
+ File.expand_path("Gemfile.lock")
68
+ end
69
+
70
+ # Gem.path を基本とし、現在ロード中の Gem.dir も補う。
71
+ def self.gem_paths
72
+ ([ Gem.dir ] + Gem.path).uniq
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,88 @@
1
+ module GemOverrideMarker
2
+ # `@gem-override` マーカー1行を解析した結果を表す値オブジェクト。
3
+ #
4
+ # gem のメソッドを prepend で丸ごと再実装したり、gem の view / partial を
5
+ # コピーして丸ごと上書きしている箇所に、本家のどこを基準にしているかを
6
+ # 宣言するためのマーカー。decorator メソッドの直前 / 上書きファイルの冒頭に置く。
7
+ #
8
+ # @gem-override {gem名}-{バージョン}/{gem内パス}#{メソッド名}
9
+ #
10
+ # 例:
11
+ # @gem-override spree_core-5.1.6/app/models/spree/stock/packer.rb#default_package
12
+ # @gem-override spree_storefront-5.1.6/app/views/spree/products/_cart_form.html.erb
13
+ # @gem-override devise-4.9.4/app/controllers/devise/sessions_controller.rb#create
14
+ #
15
+ # メソッド名 (`#method`) は省略可能で、省略時は対象ファイル全体を比較する
16
+ # (view / partial の丸ごと上書きを想定)。
17
+ class Marker
18
+ # マーカーは「コメント記号 + 半角スペース1個ちょうど + @gem-override」で始まる
19
+ # 行だけを有効とする。これにより、ドキュメント中で字下げして例示したもの
20
+ # (`# @gem-override ...` のようにスペースが2個以上) や、文中にバッククォート等で
21
+ # 引用したものを本物のマーカーと誤検出しない。
22
+ #
23
+ # 対応するコメント記号: Ruby (#) / ERB (<%#) / Slim・Haml (/)。
24
+ #
25
+ # gem 名はバージョンの直前のハイフンで区切る。gem 名側にハイフンや数字が
26
+ # 含まれていてもバージョン部分を正しく切り出せるよう、gem 名は最短一致とし
27
+ # 「数字始まりのトークン」をバージョンの開始位置とみなす。
28
+ # 各部の意味 (上から順に):
29
+ # \A[ \t]* 行頭の字下げ。メソッドの階層に応じた任意のインデント
30
+ # (?:\#|<%\#|\/) コメント記号。Ruby / ERB / Slim・Haml
31
+ # [ ] 半角スペース1個ちょうど。これで字下げ例を弾く
32
+ # @gem-override[ ] マーカー本体と区切りスペース1個
33
+ # gem gem 名。最短一致でバージョン直前まで
34
+ # version バージョン。数字始まり
35
+ # path gem 内の相対パス
36
+ # method メソッド名。省略可
37
+ PATTERN = /
38
+ \A[ \t]*
39
+ (?:\#|<%\#|\/)
40
+ [ ]
41
+ @gem-override[ ]
42
+ (?<gem>[a-z0-9_]+?)-
43
+ (?<version>\d[\w.\-]*)
44
+ \/
45
+ (?<path>[^\s\#]+)
46
+ (?:\#(?<method>\w+[!?=]?))?
47
+ (?:[ ]|%>|$)
48
+ /x
49
+
50
+ attr_reader :gem_name, :version, :path, :method_name, :file_path, :line_number
51
+
52
+ # マーカーを含む1行を解析する。マーカーでなければ nil を返す。
53
+ # file_path / line_number はマーカーが書かれた場所 (リポジトリ側) を記録する。
54
+ def self.parse(line, file_path: nil, line_number: nil)
55
+ match = PATTERN.match(line)
56
+ return nil unless match
57
+
58
+ new(
59
+ gem_name: match[:gem],
60
+ version: match[:version],
61
+ path: match[:path],
62
+ method_name: match[:method],
63
+ file_path: file_path,
64
+ line_number: line_number
65
+ )
66
+ end
67
+
68
+ def initialize(gem_name:, version:, path:, method_name: nil, file_path: nil, line_number: nil)
69
+ @gem_name = gem_name
70
+ @version = version
71
+ @path = path
72
+ @method_name = method_name
73
+ @file_path = file_path
74
+ @line_number = line_number
75
+ end
76
+
77
+ # メソッド名を含まない = ファイル全体を対象とするマーカーか。
78
+ def whole_file?
79
+ method_name.nil?
80
+ end
81
+
82
+ # マーカーが指す本家論理名 (狙い撃ち指定にそのまま使える1行表現)。
83
+ def logical_name
84
+ base = "#{gem_name}-#{version}/#{path}"
85
+ whole_file? ? base : "#{base}##{method_name}"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,11 @@
1
+ require "rails/railtie"
2
+
3
+ module GemOverrideMarker
4
+ # Rails アプリに組み込まれたとき、rake task を自動でロードする。
5
+ # gem 本体は Rails 非依存だが、利用側が Rails の場合の利便性のために用意する。
6
+ class Railtie < Rails::Railtie
7
+ rake_tasks do
8
+ load File.expand_path("../tasks/gem_override_marker.rake", __dir__)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,76 @@
1
+ module GemOverrideMarker
2
+ # リポジトリ内に埋め込まれた `@gem-override` マーカーを収集し、
3
+ # 狙い撃ち指定 (ファイル / ファイル+メソッド / 本家論理名) で絞り込む。
4
+ class Registry
5
+ # マーカーを探索する対象ファイルの拡張子。Ruby と各種テンプレート。
6
+ SCANNABLE_EXTENSIONS = %w[.rb .erb .slim .haml].freeze
7
+
8
+ # 探索から除外するパス片 (相対パスの任意位置に含まれていれば除外)。
9
+ # tmp や依存ライブラリのほか、テスト用のダミーマーカーを誤検出しないよう
10
+ # spec/fixtures も除外する。
11
+ EXCLUDED_PATH_SEGMENTS = %w[
12
+ tmp/
13
+ node_modules/
14
+ vendor/bundle/
15
+ .git/
16
+ spec/fixtures/
17
+ test/fixtures/
18
+ ].freeze
19
+
20
+ attr_reader :root
21
+
22
+ def initialize(root: ".")
23
+ @root = root
24
+ end
25
+
26
+ # リポジトリ全体を走査して全マーカーを返す。
27
+ def all_markers
28
+ target_files.flat_map { |file| markers_in_file(file) }
29
+ end
30
+
31
+ # 1ファイル内の全マーカーを返す。
32
+ def markers_in_file(file)
33
+ File.foreach(file).with_index(1).filter_map do |line, lineno|
34
+ Marker.parse(line, file_path: file, line_number: lineno)
35
+ end
36
+ rescue ArgumentError
37
+ # バイナリ等で読み取れない行があるファイルはスキップする。
38
+ []
39
+ end
40
+
41
+ # 狙い撃ち指定文字列からマーカー群を解決する。
42
+ # selector の解釈:
43
+ # - 既存ファイルパス → ファイル内の全マーカー
44
+ # - "ファイルパス,メソッド名" → そのメソッドのマーカーのみ
45
+ # - 本家論理名 (gem-版/パス#method) → 全マーカーから論理名一致を抽出
46
+ # - nil / 空 → リポジトリ全体の全マーカー
47
+ def resolve(selector)
48
+ return all_markers if selector.nil? || selector.strip.empty?
49
+
50
+ file_part, method_part = selector.split(",", 2).map { |s| s&.strip }
51
+
52
+ if File.file?(file_part)
53
+ markers = markers_in_file(file_part)
54
+ markers = markers.select { |m| m.method_name == method_part } if method_part
55
+ return markers
56
+ end
57
+
58
+ # ファイルとして存在しなければ本家論理名指定とみなす。
59
+ all_markers.select { |m| m.logical_name == selector.strip }
60
+ end
61
+
62
+ private
63
+
64
+ # 走査対象のファイル一覧 (除外パスと対象外拡張子を除く)。
65
+ def target_files
66
+ pattern = File.join(root, "**", "*")
67
+ Dir.glob(pattern, File::FNM_DOTMATCH).select do |path|
68
+ next false unless File.file?(path)
69
+ next false unless SCANNABLE_EXTENSIONS.include?(File.extname(path))
70
+
71
+ relative = path.delete_prefix("#{root}/")
72
+ EXCLUDED_PATH_SEGMENTS.none? { |segment| relative.include?(segment) }
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,69 @@
1
+ require "prism"
2
+
3
+ module GemOverrideMarker
4
+ # Ruby ソースから対象メソッドの実装を抽出し、インデントを正規化する。
5
+ #
6
+ # `def` から `end` までを正規表現で切り出すことはネストした `end` の判別が
7
+ # できず不可能なため、Ruby 3.3 標準の prism で AST 解析してメソッド単位で
8
+ # slice する。本家ソース・override の双方を同じ方式で抽出することで、
9
+ # クラス直下 (本家) と module 多階層 (decorator) のネスト差を吸収できる。
10
+ class SourceExtractor
11
+ class MethodNotFoundError < StandardError; end
12
+
13
+ # 指定ファイルから対象メソッドの実装を抽出して dedent する。
14
+ # method_name が nil の場合はファイル全体を返す (view / partial 等)。
15
+ def self.extract(file_path, method_name)
16
+ source = File.read(file_path)
17
+ return dedent(source) if method_name.nil?
18
+
19
+ sliced = slice_method(source, method_name.to_sym)
20
+ raise MethodNotFoundError, "method #{method_name} not found in #{file_path}" if sliced.nil?
21
+
22
+ dedent(sliced)
23
+ end
24
+
25
+ # AST を走査して対象メソッドの DefNode を探し、その範囲を slice する。
26
+ # 最初に見つかった定義を返す。見つからなければ nil。
27
+ def self.slice_method(source, method_name)
28
+ result = Prism.parse(source)
29
+ found = nil
30
+
31
+ visit = lambda do |node|
32
+ return if node.nil? || found
33
+
34
+ if node.is_a?(Prism::DefNode) && node.name == method_name
35
+ found = node.location.slice
36
+ return
37
+ end
38
+
39
+ node.compact_child_nodes.each { |child| visit.call(child) }
40
+ end
41
+
42
+ visit.call(result.value)
43
+ found
44
+ end
45
+
46
+ # prism の slice は 1行目 (def) が左寄せ・body 行が元のインデントのまま
47
+ # 返るため、body の最小インデントを def の位置まで詰めてネスト差を吸収する。
48
+ def self.dedent(code)
49
+ lines = code.lines
50
+ return code if lines.empty?
51
+
52
+ base_indent = indent_width(lines.first)
53
+ body = lines[1..] || []
54
+ body_indents = body.reject { |line| line.strip.empty? }.map { |line| indent_width(line) }
55
+ min_body_indent = body_indents.min || base_indent
56
+
57
+ remove = [ min_body_indent - base_indent, 0 ].max
58
+
59
+ result = [ lines.first ]
60
+ result += body.map { |line| line.strip.empty? ? line : line[remove..] }
61
+ result.join
62
+ end
63
+
64
+ # 行頭の半角スペース数を返す。
65
+ def self.indent_width(line)
66
+ line[/\A */].size
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module GemOverrideMarker
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,79 @@
1
+ require "gem_override_marker/version"
2
+ require "gem_override_marker/marker"
3
+ require "gem_override_marker/source_extractor"
4
+ require "gem_override_marker/gem_source"
5
+ require "gem_override_marker/diff_generator"
6
+ require "gem_override_marker/registry"
7
+
8
+ # Rails 上で使われる場合のみ rake task を自動ロードする (gem 本体は Rails 非依存)。
9
+ require "gem_override_marker/railtie" if defined?(Rails::Railtie)
10
+
11
+ # gem のメソッド prepend 再実装 / view 丸ごと上書きが、本家のどの版の何を
12
+ # 基準にしているかを `@gem-override` マーカーで管理し、本家との diff を
13
+ # 必要なときに生成する。gem アップグレード時の上書きの追随を支援するツール。
14
+ module GemOverrideMarker
15
+ # check の結果1件。マーカーの版と Gemfile.lock の版のズレを表す。
16
+ Mismatch = Struct.new(:marker, :locked_version, keyword_init: true)
17
+
18
+ module_function
19
+
20
+ # 狙い撃ち指定にマッチするマーカー一覧を返す (list 用)。
21
+ def list(selector: nil, root: ".")
22
+ Registry.new(root: root).resolve(selector)
23
+ end
24
+
25
+ # 1マーカーについて本家ソース vs override の unified diff を生成する。
26
+ # 比較対象の本家はマーカーに書かれた版を使う。
27
+ def diff_for(marker)
28
+ source_file = GemSource.resolve_file(marker)
29
+ source_code = SourceExtractor.extract(source_file, marker.method_name)
30
+ override_code = SourceExtractor.extract(marker.file_path, marker.method_name)
31
+
32
+ method_suffix = marker.method_name ? "##{marker.method_name}" : ""
33
+ DiffGenerator.unified(
34
+ source_code, override_code,
35
+ old_label: "source #{marker.gem_name}-#{marker.version}/#{marker.path}#{method_suffix}",
36
+ new_label: "override #{marker.file_path}#{method_suffix}"
37
+ )
38
+ end
39
+
40
+ # 同一マーカー対象 (gem 内の同じパス・同じメソッド) について、本家の
41
+ # 2版間 (from_version → to_version) の unified diff を生成する。
42
+ #
43
+ # gem アップグレード時に「その上書き箇所が本家側で変わったか」を選別する
44
+ # ための道具。差分が空 = 本家に変更なし → 上書きコードは触らず版表記だけ
45
+ # 更新すればよい。差分あり = 本家が変わった → 自分の diff を新本家に貼り直す。
46
+ #
47
+ # to_version を省略すると Gemfile.lock 上の実バージョン (アップグレード後の版)
48
+ # を使う。本家同士の差分マージはしない設計思想に沿い、あくまで選別用途。
49
+ def upstream_diff(marker, from_version: nil, to_version: nil)
50
+ from = from_version || marker.version
51
+ to = to_version || GemSource.locked_version(marker.gem_name)
52
+ raise GemSource::SourceNotFoundError, "could not resolve to_version for #{marker.gem_name}" if to.nil?
53
+
54
+ from_file = GemSource.resolve_path(marker.gem_name, from, marker.path)
55
+ to_file = GemSource.resolve_path(marker.gem_name, to, marker.path)
56
+
57
+ from_code = SourceExtractor.extract(from_file, marker.method_name)
58
+ to_code = SourceExtractor.extract(to_file, marker.method_name)
59
+
60
+ method_suffix = marker.method_name ? "##{marker.method_name}" : ""
61
+ DiffGenerator.unified(
62
+ from_code, to_code,
63
+ old_label: "upstream #{marker.gem_name}-#{from}/#{marker.path}#{method_suffix}",
64
+ new_label: "upstream #{marker.gem_name}-#{to}/#{marker.path}#{method_suffix}"
65
+ )
66
+ end
67
+
68
+ # マーカーの版と Gemfile.lock の版がズレているものを抽出する (check 用)。
69
+ # ズレが無ければ空配列。lockfile_path を省略すると Bundler が解決した
70
+ # プロジェクトの Gemfile.lock を使う。
71
+ def check(selector: nil, root: ".", lockfile_path: nil)
72
+ list(selector: selector, root: root).filter_map do |marker|
73
+ locked = GemSource.locked_version(marker.gem_name, lockfile_path)
74
+ next if locked.nil? || locked == marker.version
75
+
76
+ Mismatch.new(marker: marker, locked_version: locked)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,89 @@
1
+ require "gem_override_marker"
2
+
3
+ # gem の override マーカーを管理する rake task 群。
4
+ #
5
+ # 本家ソースをテキストとして解析するだけなので `:environment` には依存しない。
6
+ # DB 接続やアプリ初期化を起動せずに動く。
7
+ namespace :gem_override_marker do
8
+ desc "List all @gem-override markers (optionally narrowed by selector)"
9
+ task :list, [ :selector ] do |_task, args|
10
+ markers = GemOverrideMarker.list(selector: args[:selector])
11
+
12
+ if markers.empty?
13
+ puts "No markers found."
14
+ next
15
+ end
16
+
17
+ markers.each do |marker|
18
+ puts "#{marker.file_path}:#{marker.line_number}\t#{marker.logical_name}"
19
+ end
20
+ puts "\n#{markers.size} marker(s)"
21
+ end
22
+
23
+ desc "Generate diff between gem source and override (arg: selector)"
24
+ task :diff, [ :selector ] do |_task, args|
25
+ markers = GemOverrideMarker.list(selector: args[:selector])
26
+
27
+ if markers.empty?
28
+ puts "No matching markers found: #{args[:selector]}"
29
+ next
30
+ end
31
+
32
+ markers.each do |marker|
33
+ puts "=" * 72
34
+ puts "#{marker.file_path}:#{marker.line_number} #{marker.logical_name}"
35
+ puts "=" * 72
36
+
37
+ begin
38
+ diff = GemOverrideMarker.diff_for(marker)
39
+ puts diff.empty? ? "(no difference)" : diff
40
+ rescue GemOverrideMarker::GemSource::SourceNotFoundError,
41
+ GemOverrideMarker::SourceExtractor::MethodNotFoundError => e
42
+ warn "⚠️ #{e.message}"
43
+ end
44
+ end
45
+ end
46
+
47
+ desc "Diff upstream between two versions for each marker (args: selector, to_version)"
48
+ task :upstream_diff, [ :selector, :to_version ] do |_task, args|
49
+ markers = GemOverrideMarker.list(selector: args[:selector])
50
+
51
+ if markers.empty?
52
+ puts "No matching markers found: #{args[:selector]}"
53
+ next
54
+ end
55
+
56
+ markers.each do |marker|
57
+ puts "=" * 72
58
+ puts "#{marker.file_path}:#{marker.line_number} #{marker.logical_name}"
59
+ puts "=" * 72
60
+
61
+ begin
62
+ diff = GemOverrideMarker.upstream_diff(marker, to_version: args[:to_version])
63
+ puts diff.empty? ? "(no upstream change)" : diff
64
+ rescue GemOverrideMarker::GemSource::SourceNotFoundError,
65
+ GemOverrideMarker::SourceExtractor::MethodNotFoundError => e
66
+ warn "⚠️ #{e.message}"
67
+ end
68
+ end
69
+ end
70
+
71
+ desc "Detect markers whose version differs from Gemfile.lock (exits 1 on mismatch)"
72
+ task :check, [ :selector ] do |_task, args|
73
+ mismatches = GemOverrideMarker.check(selector: args[:selector])
74
+
75
+ if mismatches.empty?
76
+ puts "✅ All marker versions match Gemfile.lock."
77
+ next
78
+ end
79
+
80
+ warn "❌ Marker versions differ from Gemfile.lock (possible outdated overrides):"
81
+ mismatches.each do |mismatch|
82
+ marker = mismatch.marker
83
+ warn " #{marker.file_path}:#{marker.line_number}"
84
+ warn " marker version: #{marker.gem_name}-#{marker.version}"
85
+ warn " locked version: #{marker.gem_name}-#{mismatch.locked_version}"
86
+ end
87
+ exit 1
88
+ end
89
+ end
@@ -0,0 +1,14 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.6.2)
5
+ spree_core (5.1.6)
6
+
7
+ PLATFORMS
8
+ ruby
9
+
10
+ DEPENDENCIES
11
+ spree_core (= 5.1.6)
12
+
13
+ BUNDLED WITH
14
+ 2.5.6
@@ -0,0 +1,14 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ spree_core (9.9.9)
5
+ spree_storefront (9.9.9)
6
+
7
+ PLATFORMS
8
+ ruby
9
+
10
+ DEPENDENCIES
11
+ spree_core (= 9.9.9)
12
+
13
+ BUNDLED WITH
14
+ 2.5.6
@@ -0,0 +1,12 @@
1
+ # override 相当のソース (module 多階層でネストが深い状態) を模した fixture。
2
+ module App
3
+ module Decorators
4
+ module GreeterDecorator
5
+ def greet(name)
6
+ return super if name.nil?
7
+
8
+ "hi, #{name}!"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module Sample
2
+ # これは説明用の例:
3
+ # @gem-override spree_core-5.1.6/app/foo.rb#bar
4
+ # のように字下げした行はマーカーとして拾わない。
5
+ def plain
6
+ :noop
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ <%# @gem-override spree_storefront-5.1.6/app/views/spree/products/_cart_form.html.erb %>
2
+ <%# reason: registry fixture (whole file) %>
3
+ <div>cart form</div>
@@ -0,0 +1,7 @@
1
+ module Sample
2
+ # @gem-override spree_core-5.1.6/app/models/spree/stock/packer.rb#default_package
3
+ # reason: registry fixture
4
+ def default_package
5
+ :overridden
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # 本家相当のソース (class 直下にメソッドがある状態) を模した fixture。
2
+ class SourceClass
3
+ def greet(name)
4
+ if name.nil?
5
+ "hello"
6
+ else
7
+ "hello, #{name}"
8
+ end
9
+ end
10
+
11
+ def untouched
12
+ :keep
13
+ end
14
+ end