copy_tuner_client 1.5.0 → 2.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -2
  3. data/.github/workflows/rspec.yml +1 -1
  4. data/CHANGELOG.md +27 -0
  5. data/CLAUDE.md +6 -4
  6. data/README.md +6 -27
  7. data/UPGRADING.md +201 -0
  8. data/app/assets/javascripts/copytuner.js +162 -250
  9. data/biome.json +39 -0
  10. data/index.html +9 -11
  11. data/lib/copy_tuner_client/cache.rb +0 -2
  12. data/lib/copy_tuner_client/configuration.rb +23 -37
  13. data/lib/copy_tuner_client/copyray/marker.rb +24 -0
  14. data/lib/copy_tuner_client/copyray/rewriter.rb +113 -0
  15. data/lib/copy_tuner_client/copyray.rb +11 -4
  16. data/lib/copy_tuner_client/copyray_middleware.rb +10 -8
  17. data/lib/copy_tuner_client/engine.rb +1 -1
  18. data/lib/copy_tuner_client/helper_extension.rb +0 -3
  19. data/lib/copy_tuner_client/i18n_backend.rb +5 -12
  20. data/lib/copy_tuner_client/version.rb +1 -1
  21. data/mise.toml +3 -0
  22. data/package.json +9 -13
  23. data/pnpm-lock.yaml +600 -0
  24. data/skills/copy-tuner/SKILL.md +37 -6
  25. data/skills/copy-tuner-to-locales-cleanup/SKILL.md +4 -4
  26. data/skills/copy-tuner-to-locales-migrate-prefix/references/local-first-regexp.md +4 -9
  27. data/skills/copy-tuner-to-t-migrate/SKILL.md +131 -0
  28. data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +189 -0
  29. data/spec/copy_tuner_client/cache_spec.rb +2 -10
  30. data/spec/copy_tuner_client/client_spec.rb +1 -0
  31. data/spec/copy_tuner_client/configuration_spec.rb +16 -16
  32. data/spec/copy_tuner_client/copyray/marker_spec.rb +41 -0
  33. data/spec/copy_tuner_client/copyray/rewriter_spec.rb +216 -0
  34. data/spec/copy_tuner_client/copyray_middleware_spec.rb +88 -0
  35. data/spec/copy_tuner_client/copyray_spec.rb +22 -39
  36. data/spec/copy_tuner_client/helper_extension_spec.rb +18 -5
  37. data/spec/copy_tuner_client/i18n_backend_spec.rb +8 -15
  38. data/spec/support/client_spec_helpers.rb +0 -1
  39. data/src/copyray-overlay.ts +125 -0
  40. data/src/copytuner-bar.ts +153 -0
  41. data/src/main.ts +33 -26
  42. data/src/{copyray.css → styles.ts} +112 -136
  43. data/src/util.ts +9 -1
  44. data/tsconfig.json +5 -10
  45. data/vite.config.ts +0 -1
  46. metadata +15 -8
  47. data/.eslintrc.js +0 -12
  48. data/app/assets/stylesheets/copytuner.css +0 -1
  49. data/src/copyray.ts +0 -130
  50. data/src/copytuner_bar.ts +0 -115
  51. data/src/specimen.ts +0 -84
  52. data/yarn.lock +0 -2540
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: copy-tuner-to-t-migrate
3
+ description: >-
4
+ copy_tuner_client v2.0.0 への移行で、削除された独自ヘルパー tt の呼び出しを Rails 標準の t(translate)へ
5
+ 置換するスキル。tt は PR #122 で存在理由を失い gem から削除されたため、残った tt(...) は NoMethodError に
6
+ なる。大半は機械的に tt( → t( で済むが、訳文を文字列加工している箇所だけは t ではなく I18n.t へ移す必要があり
7
+ (開発環境のマーカートークン混入バグ対策)、そこは 1 件ずつ確認する。対象は app 配下の .rb / .haml / .erb。
8
+ disable-model-invocation: true
9
+ license: MIT
10
+ ---
11
+
12
+ # tt → t 移行スキル(copy_tuner_client v2.0.0)
13
+
14
+ copy_tuner_client がかつて `ActionView` にフックして生やしていた独自ヘルパー **`tt`**(シグネチャは
15
+ `tt(key, **options)`、引数は `t` と完全同一)を、Rails 標準の `t`(`translate`)へ置き換えるワークフロー。
16
+ gem を v2.0.0 に上げたアプリのビューに `tt(...)` が残っていると **`NoMethodError`(未定義ヘルパー)** になる。
17
+
18
+ このスキルは**破壊的な一括書き換え**を含むため、ユーザが明示的に呼んだときだけ動く(`disable-model-invocation`)。
19
+
20
+ ## なぜ tt を t へ置換するのか
21
+
22
+ `tt` はもともと「**copyray マーカーを注入しない生の訳文を取る**」ためのヘルパーだった。旧方式では `t('key')` の
23
+ 戻り値そのものにマーカーが埋め込まれ、`truncate` / `length` 等で**訳文を文字列加工する箇所**でマーカー長が
24
+ 混入して壊れる問題があり、その対策が `tt` だった。
25
+
26
+ PR #122 のマーカー方式刷新で、マーカー注入は戻り値ではなく middleware(`CopyrayMiddleware` → `Rewriter`)で
27
+ 行い HTML 配信前に完全除去するようになった。これにより通常の `t` がどこでも安全に使えるようになり、`tt` は
28
+ 存在理由を失って削除された。背景の詳細は `skills/copy-tuner/SKILL.md` の「翻訳ヘルパー(`t`)」と
29
+ 「落とし穴: 開発環境で訳文にマーカートークンが混入する」を参照。
30
+
31
+ **ただし罠が一つ残る。** マーカー除去は「最終的に HTML へ出力される訳文」に対してのみ効く。development で
32
+ `t('key')` の戻り値を**ビュー内で文字列加工する**コードは、除去前のマーカー込み文字列に作用してしまう(本番では
33
+ middleware 自体が無いので再現しない=開発環境だけ壊れる)。
34
+
35
+ → つまり **`tt` を素朴に `t` へ変えてよいのは「文字列加工していない箇所」だけ**。文字列加工している `tt` を
36
+ `t` にすると、`tt` がもともと潰していたバグをそのまま復活させてしまう。そこは middleware のラッパーを通らない
37
+ **`I18n.t`(絶対キー)** へ移すのが正解で、機械的には決められないため 1 件ずつ確認する。
38
+
39
+ ## 変換の 3 分類
40
+
41
+ | 分類 | 例 | 扱い |
42
+ |---|---|---|
43
+ | **safe** | `tt('views.foo')` / `= tt '.title'` / `tt(key, default: x)` | `tt(` → `t(` に決定論的一括変換 |
44
+ | **suspicious** | `truncate(tt('k'))` / `tt('k').length` / `tt('k')[0..n]` / `tt('k') =~ /re/` / `tt('k').gsub(...)` 等 | `I18n.t('絶対キー')` へ。**1 件ずつ確認**。スクリプトは触らない |
45
+ | **other** | `def tt` / `alias tt`(定義側)/ app 外(`lib/` 等)/ `tt` を含む別識別子の誤検出 | 最後にまとめて提示。自動変換しない |
46
+
47
+ **重要(定義側の罠):** アプリが v2.0.0 を待つ間の後方互換として `ApplicationHelper` に `def tt(key, **) = t(key, **)`
48
+ のような **`tt` の定義**を生やしていることがある。これは「呼び出し」ではないので `t(` へ機械置換すると Rails の `t`
49
+ を再定義して破滅する。スクリプトは `def tt` / `alias tt` を **other** に隔離して自動変換しない。**呼び出しを全て
50
+ `t` / `I18n.t` へ移し終えた後**に、この定義を手で削除する(順序を逆にすると呼び出しが `NoMethodError` になる)。
51
+
52
+ 判定はヒューリスティック(完全な AST ではない)。**誤判定のコストが非対称**なので疑わしきは suspicious に倒す:
53
+ safe を取りこぼす(本当は怪しいのに safe)とマーカー混入バグが再発して重大、suspicious を過剰検出しても人間が
54
+ 1 件確認するだけで軽微。
55
+
56
+ ## ワークフロー
57
+
58
+ ### 1. 利用箇所の洗い出しと 3 分類
59
+
60
+ 同梱スクリプトで `app/` 配下の `.rb` / `.haml` / `.erb` を走査し、3 分類でレポートする(変更は加えない)。
61
+ Rails context は不要なので素の `ruby` で動く。
62
+
63
+ ```bash
64
+ ruby skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb --report
65
+ ```
66
+
67
+ safe / suspicious / other の件数と該当箇所(`ファイル:行: コード`)が出る。まず全体像をユーザと共有する。
68
+
69
+ ### 2. safe な箇所を一括変換
70
+
71
+ safe 分類は引数がそのまま通る(`tt(key, **options)` → `t(key, **options)`)ので決定論的に置換できる。
72
+ **件数をユーザに伝え、承認を得てから**適用する(`--apply-safe` はファイルを直接書き換えるため)。
73
+
74
+ ```bash
75
+ ruby skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb --apply-safe
76
+ ```
77
+
78
+ suspicious / other の行は**一切触らない**(行番号で限定しているため)。適用後に `git diff` を見せて、safe 行
79
+ だけが `t(` になったことを確認してもらう。
80
+
81
+ ### 3. suspicious な箇所を 1 件ずつ確認
82
+
83
+ ここが人間の判断が要る核心。手順 1 の suspicious 各件について、次を 1 件ずつ提示して確認を取る:
84
+
85
+ - 該当コード(`ファイル:行`)と、なぜ怪しいか(戻り値を文字列加工している=マーカー混入の危険)
86
+ - 提案する置換: `t(...)` ではなく **`I18n.t('絶対キー')`**(`I18n` モジュール直呼びはラッパーを通らずマーカーが付かない)
87
+
88
+ 注意点:
89
+ - **たとえユーザが「全部まとめて I18n.t にして」と言っても、suspicious は必ず 1 件ずつ提示する。** 相対キーを含む件は絶対キーをユーザと確認しないと置換できないためで、省略すると誤ったキーを書き込む危険がある。
90
+ - **相対キー(先頭ドット `tt('.foo')`)は `I18n.t` では解決できない。** 絶対キー(`I18n.t('views.foo.bar')`)へ
91
+ 書き換える必要があり、partial のパスからキーを補う判断が要る。スクリプトはこの相対キーの suspicious を
92
+ `相対キー(.foo)は絶対キーへ書き換えが必要` と強調表示するので、特に丁寧に確認する。ヘルパーや
93
+ コントローラ(`app/helpers/`, `app/controllers/`)に相対キーがある場合は、呼び出し元のビューのパスを
94
+ 調べて基点キーを特定するか、ユーザに直接絶対キーを確認する。絶対キーが不明なときは
95
+ `grep -r "キー末尾部分" config/locales/` でロケールファイルを検索して候補を絞り込む。
96
+ - 本当に文字列加工していない(誤検出)なら、その場合は `t(...)` でよい。ユーザの判断に従う。
97
+
98
+ 確認が取れた箇所だけ Edit で個別に置換する(スクリプトでは変換しない)。
99
+
100
+ ### 4. その他ヒット(定義側・app 外・誤検出)を提示
101
+
102
+ 手順 1 の other をまとめて提示する。
103
+
104
+ - **`def tt` / `alias tt`(定義側)**: 後方互換シムが残っていることが多い。**呼び出しを全て移し終えてから手で削除**する
105
+ (3 分類表の「定義側の罠」参照)。早く消すと残った呼び出しが `NoMethodError` になる。
106
+ - **app 外(`lib/` のヘルパーやコントローラ等)**: ビューヘルパー `tt` がそのスコープから見えるかは別問題なので、
107
+ 自動変換せずユーザに判断を委ねる。
108
+
109
+ 念のため、スクリプトの単語境界判定をすり抜けた可能性に備えて素朴な grep でも最終確認する:
110
+
111
+ ```bash
112
+ git grep -nwI tt -- app # -I でバイナリ(画像等)の誤マッチを除外
113
+ ```
114
+
115
+ ### 5. 検証
116
+
117
+ - `git grep -nwI tt -- app` の残りが「未対応の suspicious + まだ消していない定義側」だけであること
118
+ (safe は 0 件、`I18n.t` 化した suspicious も `tt` としては消える)。
119
+ - 対象アプリで `bundle exec rspec`(または該当アプリのテスト)を回し、`NoMethodError` が出ないこと。
120
+ - development で実画面を開き、文字列加工していた箇所にマーカートークン(`⟦CT:...⟧`)が混入していないこと。
121
+
122
+ ## スクリプトの責務(決定論的な範囲のみ)
123
+
124
+ `scripts/migrate_tt.rb` は**機械的に確定できる部分だけ**を担う:
125
+
126
+ - `--report`(既定): 3 分類を出力。`--json` で機械可読出力も可能。**ファイルは変更しない**。
127
+ - `--apply-safe`: safe 分類のみ `tt(` → `t(` を適用。suspicious / other は触らない。
128
+ - `--root DIR`: リポジトリルート指定(既定はカレント)。
129
+
130
+ suspicious の確定変換(`I18n.t` 化・相対→絶対キー)は機械的に決められないため**スクリプトは行わない**。
131
+ 必ず手順 3 で人間が 1 件ずつ確認する。
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ # copy_tuner_client v2.0.0 移行: tt → t 置換スクリプト(SKILL.md 手順 1〜2)。
4
+ #
5
+ # copy_tuner_client が生やしていた独自ヘルパー tt は PR #122 で削除された。gem を上げた
6
+ # アプリのビューに tt(...) が残ると NoMethodError になるため t へ移す。大半は機械的に
7
+ # tt( → t( で済むが、訳文を文字列加工している箇所だけは素朴に t へ変えると開発環境で
8
+ # マーカートークン混入バグが再発する(skills/copy-tuner/SKILL.md の罠を参照)。そこは
9
+ # I18n.t(絶対キー)へ変換すべきなので、このスクリプトは自動変換せず「怪しい」として抽出する。
10
+ #
11
+ # Rails context は不要なので素の ruby で動く:
12
+ #
13
+ # ruby skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb --report
14
+ # ruby skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb --apply-safe
15
+ #
16
+ # 出力する 3 分類:
17
+ # (1) safe : 文字列加工されていない tt 呼び出し。tt( → t( に決定論的変換できる。
18
+ # (2) suspicious : 戻り値が同一行で文字列加工されている tt 呼び出し。I18n.t 提案・要確認。
19
+ # (3) other : app 外 / tt を含む別識別子の誤検出など。自動変換せず目視で確認。
20
+ #
21
+ # 判定はヒューリスティック(完全な AST 解析ではない)。誤判定のコストは
22
+ # safe 誤分類(怪しいのに safe)= マーカー混入バグ再発 → 重大
23
+ # suspicious 誤分類(安全なのに suspicious)= 人間が 1 件確認するだけ → 軽微
24
+ # なので、疑わしきは suspicious に倒す。
25
+
26
+ require 'optparse'
27
+
28
+ # NOTE: 中断を呼び出し側(Bash 手順)へ確実に伝えるため warn + exit(1) を使う。
29
+ def die(message)
30
+ warn message
31
+ exit(1)
32
+ end
33
+
34
+ # ---- 引数 ----
35
+ options = { mode: :report, root: '.' }
36
+ parser =
37
+ OptionParser.new do |opts|
38
+ opts.banner = 'Usage: ruby migrate_tt.rb [--report | --apply-safe] [--root DIR]'
39
+ opts.on('--report', '3 分類を出力する(既定)。ファイルは変更しない') { options[:mode] = :report }
40
+ opts.on('--apply-safe', 'safe 分類のみ tt( → t( を実ファイルに適用する') { options[:mode] = :apply_safe }
41
+ opts.on('--root DIR', 'リポジトリルート(既定: カレントディレクトリ)') { |v| options[:root] = v }
42
+ opts.on('--json', 'report を JSON で出力する(機械処理用)') { options[:json] = true }
43
+ opts.on('-h', '--help', 'ヘルプ') do
44
+ puts opts
45
+ exit(0)
46
+ end
47
+ end
48
+ parser.parse!(ARGV)
49
+
50
+ ROOT = File.expand_path(options[:root])
51
+ die("ルートが見つからない: #{ROOT}") unless File.directory?(ROOT)
52
+
53
+ # 対象: app 配下の .rb / .haml / .erb。それ以外のヒットは other 扱いで提示のみ。
54
+ APP_GLOB = File.join(ROOT, 'app', '**', '*.{rb,haml,erb}')
55
+ # app 外も走査して other に入れる(lib/ 等の取りこぼし提示用)。
56
+ ALL_GLOB = File.join(ROOT, '**', '*.{rb,haml,erb}')
57
+
58
+ # 単語境界の tt 呼び出し。tt( だけでなく haml の `= tt '...'`(括弧なし)も拾う。
59
+ # 直前が識別子文字(attr/http/setting 等の一部や `.tt` のメソッド呼び出し)でないことを (?<![\w.]) で担保。
60
+ TT_CALL = /(?<![\w.])tt(\s*\(|\s+['":@])/
61
+
62
+ # tt の「定義」側(呼び出しではない)。アプリが後方互換で `def tt` を生やしている場合がある。
63
+ # これを safe として t( へ機械置換すると Rails の t を再定義してしまい破滅的なので、定義は別扱いにする。
64
+ TT_DEFINITION = /\b(?:def\s+(?:self\.)?tt\b|alias(?:_method)?\s+:?tt\b|alias(?:_method)?\s+['"]tt['"])/
65
+
66
+ # tt(...) の戻り値が同一行で文字列加工されているかの判定に使う語彙。
67
+ STRING_HELPERS = %w[truncate simple_format strip_tags highlight excerpt word_wrap].freeze
68
+ STRING_METHODS = %w[
69
+ length size bytesize slice first last truncate truncate_words
70
+ gsub sub gsub! sub! strip lstrip rstrip chomp chop chars bytes lines
71
+ scan match match? =~ start_with? end_with? include? index rindex
72
+ upcase downcase capitalize ljust rjust center delete squeeze tr
73
+ to_i to_f to_sym html_safe
74
+ ].freeze
75
+
76
+ # (a) truncate( ... tt( ... ) のように文字列加工ヘルパーの引数になっている
77
+ STRING_HELPER_RE = /\b(?:#{STRING_HELPERS.join('|')})\s*\(\s*[^)]*\btt\b/
78
+ # (b) tt(...) または tt '...' の直後に .method / =~ / [ が続く。引数の閉じ括弧をまたぐ単純近似。
79
+ STRING_METHOD_RE = /\btt\b\s*(?:\([^\n]*?\)|['":@][^\n,]*)\s*(?:\.\s*(?:#{STRING_METHODS.join('|')})\b|=~|\[)/
80
+
81
+ # 先頭ドットの相対キー tt('.foo') は I18n.t では解決できず絶対キー化が要る → 強調表示用。
82
+ RELATIVE_KEY_RE = /\btt\b\s*(?:\(\s*)?['":]\s*\./
83
+
84
+ def relative(path)
85
+ path.sub("#{ROOT}/", '')
86
+ end
87
+
88
+ safe = []
89
+ suspicious = []
90
+ other = []
91
+
92
+ app_set = Dir.glob(APP_GLOB).to_set
93
+
94
+ # 1 ファイル分を走査し、ヒット行を safe / suspicious / other へ振り分ける。
95
+ def scan_file(path, in_app:)
96
+ rel = relative(path)
97
+ File.readlines(path).each_with_index do |line, idx|
98
+ next unless line =~ TT_CALL
99
+
100
+ entry = { file: rel, lineno: idx + 1, code: line.rstrip }
101
+ yield(classify_line(line, entry, in_app: in_app))
102
+ end
103
+ rescue StandardError
104
+ nil
105
+ end
106
+
107
+ # ヒット 1 行を分類し、[:safe | :suspicious | :other, entry] を返す。
108
+ def classify_line(line, entry, in_app:)
109
+ # tt の定義(def/alias)は呼び出しではない。t( へ置換すると Rails の t を壊すので絶対に自動変換しない。
110
+ if line.match?(TT_DEFINITION)
111
+ entry[:reason] = 'tt の定義(def/alias)。自動変換禁止。呼び出しを全て t へ移した後に手動で削除する'
112
+ return [:other, entry]
113
+ end
114
+
115
+ unless in_app
116
+ entry[:reason] = 'app 外(要目視)'
117
+ return [:other, entry]
118
+ end
119
+
120
+ return [:safe, entry] unless line.match?(STRING_HELPER_RE) || line.match?(STRING_METHOD_RE)
121
+
122
+ entry[:relative_key] = line.match?(RELATIVE_KEY_RE)
123
+ entry[:hint] = if entry[:relative_key]
124
+ 'I18n.t へ。相対キー(.foo)は絶対キーへ書き換えが必要'
125
+ else
126
+ 'I18n.t(絶対キー) へ置換を検討'
127
+ end
128
+ [:suspicious, entry]
129
+ end
130
+
131
+ buckets = { safe: safe, suspicious: suspicious, other: other }
132
+ Dir.glob(ALL_GLOB).each do |path|
133
+ # vendor / node_modules / tmp 等のノイズを除外
134
+ next if relative(path).start_with?('vendor/', 'node_modules/', 'tmp/', '.git/')
135
+
136
+ scan_file(path, in_app: app_set.include?(path)) do |bucket, entry|
137
+ buckets[bucket] << entry
138
+ end
139
+ end
140
+
141
+ if options[:json]
142
+ require 'json'
143
+ puts JSON.pretty_generate(safe: safe, suspicious: suspicious, other: other)
144
+ exit(0)
145
+ end
146
+
147
+ def print_section(title, entries)
148
+ puts "\n== #{title} (#{entries.size}) =="
149
+ entries.each do |e|
150
+ suffix = if e[:hint]
151
+ " # #{e[:hint]}"
152
+ else
153
+ (e[:reason] ? " # #{e[:reason]}" : '')
154
+ end
155
+ puts "#{e[:file]}:#{e[:lineno]}: #{e[:code].strip}#{suffix}"
156
+ end
157
+ end
158
+
159
+ if options[:mode] == :report
160
+ print_section('safe(tt( → t( に決定論的変換できる)', safe)
161
+ print_section('suspicious(文字列加工あり・I18n.t 提案・要 1 件ずつ確認)', suspicious)
162
+ print_section('other(app 外・誤検出・要目視)', other)
163
+ puts "\n合計: safe=#{safe.size} suspicious=#{suspicious.size} other=#{other.size}"
164
+ exit(0)
165
+ end
166
+
167
+ # ---- --apply-safe: safe 分類のみ tt( → t( を適用 ----
168
+ # 同一ファイル内の safe 行だけを対象に、単語境界の tt を t へ置換する。
169
+ # suspicious / other 行は触らない(行番号で限定)。
170
+ by_file = safe.group_by { |e| e[:file] }
171
+ changed = 0
172
+ by_file.each do |rel, entries|
173
+ path = File.join(ROOT, rel)
174
+ lines = File.readlines(path)
175
+ target_linenos = entries.to_set { |e| e[:lineno] }
176
+ target_linenos.each do |lineno|
177
+ line = lines[lineno - 1]
178
+ # safe 行内の tt 呼び出しのみ t へ。tt('...') や tt(... の tt を t に。
179
+ new_line = line.gsub(TT_CALL) { "t#{Regexp.last_match(1)}" }
180
+ if new_line != line
181
+ lines[lineno - 1] = new_line
182
+ changed += 1
183
+ end
184
+ end
185
+ File.write(path, lines.join)
186
+ end
187
+
188
+ puts "safe 置換を適用: #{changed} 箇所 / #{by_file.size} ファイル"
189
+ puts "残った suspicious=#{suspicious.size} other=#{other.size} は手作業で対応すること(--report で再確認)"
@@ -24,16 +24,6 @@ describe 'CopyTunerClient::Cache' do
24
24
  expect(cache.keys).to match_array(%w[en.test.key en.test.other_key])
25
25
  end
26
26
 
27
- it 'exclude_key_regexpが設定されている場合、該当データを除外すること' do
28
- cache = build_cache(exclude_key_regexp: /^en\.test\.other_key$/)
29
- cache['en.test.key'] = 'expected'
30
- cache['en.test.other_key'] = 'expected'
31
-
32
- cache.download
33
-
34
- expect(cache.queued.keys).to match_array(%w[en.test.key])
35
- end
36
-
37
27
  it 'local_first_key_regexpにマッチするキー(locale除く)はアップロードキューに入れないこと' do
38
28
  cache = build_cache(local_first_key_regexp: /\Aviews\./)
39
29
  cache['ja.views.foo'] = 'local value'
@@ -284,6 +274,7 @@ describe 'CopyTunerClient::Cache' do
284
274
  it 'トップレベルからflushできること' do
285
275
  cache = build_cache
286
276
  CopyTunerClient.configure do |config|
277
+ config.project_id = 1
287
278
  config.cache = cache
288
279
  end
289
280
  expect(cache).to receive(:flush).at_least(:once)
@@ -410,6 +401,7 @@ describe 'CopyTunerClient::Cache' do
410
401
 
411
402
  it 'トップレベル定数から呼び出せること' do
412
403
  CopyTunerClient.configure do |config|
404
+ config.project_id = 1
413
405
  config.cache = cache
414
406
  end
415
407
  expect(cache).to receive(:export)
@@ -207,6 +207,7 @@ describe 'CopyTunerClient' do
207
207
  client = build_client
208
208
  allow(client).to receive(:download)
209
209
  CopyTunerClient.configure do |config|
210
+ config.project_id = 1
210
211
  config.client = client
211
212
  end
212
213
  expect(client).to receive(:deploy)
@@ -253,27 +253,24 @@ describe CopyTunerClient::Configuration do
253
253
  end
254
254
  end
255
255
 
256
- describe '#exclude_key_regexp= (deprecated)' do
257
- let(:config) { CopyTunerClient::Configuration.new }
258
- let(:deprecator) { instance_double(ActiveSupport::Deprecation, warn: nil) }
259
-
260
- before { allow(ActiveSupport::Deprecation).to receive(:new).and_return(deprecator) }
261
-
262
- it 'warns when a value is set' do
263
- expect(deprecator).to receive(:warn).with(/exclude_key_regexp is deprecated/)
264
-
265
- config.exclude_key_regexp = /\Aja\.views\./
256
+ describe 'project_id の必須化' do
257
+ let(:config) do
258
+ config = CopyTunerClient::Configuration.new
259
+ config.api_key = 'abc123'
260
+ config
266
261
  end
267
262
 
268
- it 'stores the assigned value' do
269
- config.exclude_key_regexp = /\Aja\.views\./
270
- expect(config.exclude_key_regexp).to eq(/\Aja\.views\./)
263
+ it 'project_id が未設定のとき apply が ArgumentError を出すこと' do
264
+ expect { config.apply }.to raise_error(ArgumentError, 'project_id is required')
271
265
  end
272
266
 
273
- it 'does not warn when set to nil' do
274
- expect(deprecator).not_to receive(:warn)
267
+ it 'project_id が未設定のとき project_url ArgumentError を出すこと' do
268
+ expect { config.project_url }.to raise_error(ArgumentError, 'project_id is required')
269
+ end
275
270
 
276
- config.exclude_key_regexp = nil
271
+ it 'project_id を設定すると project_url がそれを使った URL を返すこと' do
272
+ config.project_id = 77
273
+ expect(config.project_url).to include('/projects/77')
277
274
  end
278
275
  end
279
276
  end
@@ -294,6 +291,9 @@ shared_context 'stubbed configuration' do
294
291
  allow(CopyTunerClient::Poller).to receive(:new).and_return(poller)
295
292
  allow(CopyTunerClient::ProcessGuard).to receive(:new).and_return(process_guard)
296
293
  subject.logger = logger
294
+ # NOTE: apply は project_id 必須になったため、未設定だと raise する。applied 系テストは
295
+ # project_id 自体を検証しないので適当な値を補っておく
296
+ subject.project_id ||= 1
297
297
  apply
298
298
  end
299
299
  end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'copy_tuner_client/copyray/marker'
3
+
4
+ describe CopyTunerClient::Copyray::Marker do
5
+ describe '.encode' do
6
+ it 'キーを可視トークンの区切り記号で囲む' do
7
+ expect(described_class.encode('views.home.index.message')).to eq '⟦CT:views.home.index.message⟧'
8
+ end
9
+
10
+ it 'ドット・アンダースコア・スラッシュを含むキーをそのまま保持する' do
11
+ expect(described_class.encode('en.foo_bar.baz/qux')).to eq '⟦CT:en.foo_bar.baz/qux⟧'
12
+ end
13
+
14
+ it 'マルチバイトキーをそのまま保持する' do
15
+ expect(described_class.encode('ja.見出し')).to eq '⟦CT:ja.見出し⟧'
16
+ end
17
+ end
18
+
19
+ describe '::SCAN_REGEXP' do
20
+ it 'エンコード済みトークンからキーを取り出す' do
21
+ key = 'views.home.index.message'
22
+ match = described_class::SCAN_REGEXP.match(described_class.encode(key))
23
+ expect(match[1]).to eq key
24
+ end
25
+
26
+ it 'ドット・アンダースコア・スラッシュ・マルチバイトを含むキーを取り出す' do
27
+ key = 'ja.foo_bar.baz/qux.見出し'
28
+ match = described_class::SCAN_REGEXP.match(described_class.encode(key))
29
+ expect(match[1]).to eq key
30
+ end
31
+
32
+ it '非貪欲マッチで隣接するトークンを分離する' do
33
+ text = "#{described_class.encode('a.b')}hello#{described_class.encode('c.d')}"
34
+ expect(text.scan(described_class::SCAN_REGEXP).flatten).to eq ['a.b', 'c.d']
35
+ end
36
+
37
+ it 'プレーンテキストにはマッチしない' do
38
+ expect('just a normal sentence with CT: and brackets [x]').not_to match described_class::SCAN_REGEXP
39
+ end
40
+ end
41
+ end