lostcoords-ui 0.4.1 → 0.6.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +364 -96
  3. data/app/assets/tailwind/lostcoords/ui/avatar.css +6 -6
  4. data/app/assets/tailwind/lostcoords/ui/base.css +45 -0
  5. data/app/assets/tailwind/lostcoords/ui/card.css +23 -22
  6. data/app/assets/tailwind/lostcoords/ui/choice.css +39 -0
  7. data/app/assets/tailwind/lostcoords/ui/code-block.css +10 -0
  8. data/app/assets/tailwind/lostcoords/ui/{disclosure.css → collapsible.css} +6 -6
  9. data/app/assets/tailwind/lostcoords/ui/components/code-block.css +5 -0
  10. data/app/assets/tailwind/lostcoords/ui/components/collapsible.css +5 -0
  11. data/app/assets/tailwind/lostcoords/ui/components/dialog.css +9 -0
  12. data/app/assets/tailwind/lostcoords/ui/components/emoji-picker.css +5 -0
  13. data/app/assets/tailwind/lostcoords/ui/components/menu.css +5 -0
  14. data/app/assets/tailwind/lostcoords/ui/components/prose.css +3 -0
  15. data/app/assets/tailwind/lostcoords/ui/components/table.css +3 -0
  16. data/app/assets/tailwind/lostcoords/ui/components/toast.css +5 -0
  17. data/app/assets/tailwind/lostcoords/ui/components/tooltip.css +4 -0
  18. data/app/assets/tailwind/lostcoords/ui/components.css +12 -0
  19. data/app/assets/tailwind/lostcoords/ui/copy-button.css +9 -9
  20. data/app/assets/tailwind/lostcoords/ui/core.css +21 -0
  21. data/app/assets/tailwind/lostcoords/ui/dialog.css +17 -17
  22. data/app/assets/tailwind/lostcoords/ui/emoji-picker.css +86 -88
  23. data/app/assets/tailwind/lostcoords/ui/field.css +2 -2
  24. data/app/assets/tailwind/lostcoords/ui/glass.css +9 -8
  25. data/app/assets/tailwind/lostcoords/ui/layout.css +2 -2
  26. data/app/assets/tailwind/lostcoords/ui/popover.css +14 -14
  27. data/app/assets/tailwind/lostcoords/ui/prose.css +66 -0
  28. data/app/assets/tailwind/lostcoords/ui/sheet.css +62 -63
  29. data/app/assets/tailwind/lostcoords/ui/squircle.css +7 -27
  30. data/app/assets/tailwind/lostcoords/ui/table.css +51 -0
  31. data/app/assets/tailwind/lostcoords/ui/theme.css +209 -225
  32. data/app/assets/tailwind/lostcoords/ui/toast.css +23 -0
  33. data/app/assets/tailwind/lostcoords/ui/tooltip.css +51 -0
  34. data/app/assets/tailwind/lostcoords/ui/typography.css +31 -61
  35. data/app/assets/tailwind/lostcoords/ui/ui.css +2 -21
  36. data/app/assets/tailwind/lostcoords/ui/utilities.css +14 -22
  37. data/app/helpers/lostcoords/ui/badge_helper.rb +67 -110
  38. data/app/helpers/lostcoords/ui/button_helper.rb +44 -35
  39. data/app/helpers/lostcoords/ui/card_helper.rb +9 -52
  40. data/app/helpers/lostcoords/ui/choice_helper.rb +87 -0
  41. data/app/helpers/lostcoords/ui/code_block_helper.rb +22 -0
  42. data/app/helpers/lostcoords/ui/collapsible_helper.rb +9 -0
  43. data/app/helpers/lostcoords/ui/copy_button_helper.rb +7 -7
  44. data/app/helpers/lostcoords/ui/description_list_helper.rb +12 -0
  45. data/app/helpers/lostcoords/ui/dialog_helper.rb +15 -17
  46. data/app/helpers/lostcoords/ui/emoji_picker_helper.rb +6 -0
  47. data/app/helpers/lostcoords/ui/feedback_helper.rb +25 -6
  48. data/app/helpers/lostcoords/ui/form_helper.rb +36 -59
  49. data/app/helpers/lostcoords/ui/glass_helper.rb +7 -7
  50. data/app/helpers/lostcoords/ui/layout_helper.rb +13 -11
  51. data/app/helpers/lostcoords/ui/menu_helper.rb +45 -10
  52. data/app/helpers/lostcoords/ui/navigation_helper.rb +144 -0
  53. data/app/helpers/lostcoords/ui/primitives_helper.rb +39 -61
  54. data/app/helpers/lostcoords/ui/reading_helper.rb +2 -2
  55. data/app/helpers/lostcoords/ui/render_attributes_helper.rb +17 -1
  56. data/app/helpers/lostcoords/ui/sheet_helper.rb +7 -45
  57. data/app/helpers/lostcoords/ui/structures_helper.rb +26 -125
  58. data/app/helpers/lostcoords/ui/table_helper.rb +11 -0
  59. data/app/helpers/lostcoords/ui/tailwind_helper.rb +13 -4
  60. data/app/helpers/lostcoords/ui/toast_helper.rb +22 -0
  61. data/app/helpers/lostcoords/ui/tooltip_helper.rb +17 -0
  62. data/app/helpers/lostcoords/ui/typography_helper.rb +41 -11
  63. data/app/javascript/lostcoords/ui/controllers/lc_code_block_controller.js +23 -0
  64. data/app/javascript/lostcoords/ui/controllers/{disclosure_controller.js → lc_collapsible_controller.js} +4 -4
  65. data/app/javascript/lostcoords/ui/controllers/{confirm_dialog_controller.js → lc_confirm_dialog_controller.js} +4 -4
  66. data/app/javascript/lostcoords/ui/controllers/{modal_controller.js → lc_dialog_controller.js} +1 -1
  67. data/app/javascript/lostcoords/ui/controllers/{emoji_picker_controller.js → lc_emoji_picker_controller.js} +38 -10
  68. data/app/javascript/lostcoords/ui/controllers/lc_kbd_controller.js +12 -0
  69. data/app/javascript/lostcoords/ui/controllers/{row_menu_controller.js → lc_menu_controller.js} +25 -3
  70. data/app/javascript/lostcoords/ui/controllers/{ui_popover_controller.js → lc_popover_controller.js} +1 -1
  71. data/app/javascript/lostcoords/ui/controllers/{ui_selection_controller.js → lc_selection_controller.js} +7 -15
  72. data/app/javascript/lostcoords/ui/controllers/{sheet_controller.js → lc_sheet_controller.js} +17 -17
  73. data/app/javascript/lostcoords/ui/controllers/lc_shortcut_controller.js +45 -0
  74. data/app/javascript/lostcoords/ui/controllers/lc_tabs_controller.js +42 -0
  75. data/app/javascript/lostcoords/ui/controllers/{toast_controller.js → lc_toast_controller.js} +2 -2
  76. data/app/javascript/lostcoords/ui/controllers/lc_toast_region_controller.js +62 -0
  77. data/app/javascript/lostcoords/ui/controllers/lc_tooltip_controller.js +54 -0
  78. data/app/javascript/lostcoords/ui/helpers/catalog_picker.js +77 -25
  79. data/app/javascript/lostcoords/ui/helpers/context_dialog.js +6 -6
  80. data/app/javascript/lostcoords/ui/helpers/dialog.js +2 -2
  81. data/app/javascript/lostcoords/ui/helpers/dismiss_stack.js +30 -7
  82. data/app/javascript/lostcoords/ui/helpers/floating_panel.js +7 -8
  83. data/app/javascript/lostcoords/ui/helpers/menu_keys.js +19 -18
  84. data/app/javascript/lostcoords/ui/helpers/roving_focus.js +175 -0
  85. data/app/javascript/lostcoords/ui/helpers/sheet_motion.js +4 -4
  86. data/app/javascript/lostcoords/ui/helpers/shortcuts.js +162 -0
  87. data/app/javascript/lostcoords/ui/helpers/top_layer.js +48 -10
  88. data/app/javascript/lostcoords/ui/index.js +57 -0
  89. data/app/views/lostcoords/ui/components/_emoji_picker.html.erb +46 -46
  90. data/app/views/lostcoords/ui/components/_emoji_picker_grid.html.erb +3 -3
  91. data/app/views/lostcoords/ui/components/_lc_code_block.html.erb +18 -0
  92. data/app/views/lostcoords/ui/components/_lc_collapsible.html.erb +5 -0
  93. data/app/views/lostcoords/ui/components/_lc_dialog.html.erb +11 -8
  94. data/app/views/lostcoords/ui/components/_lc_floating_panel.html.erb +8 -0
  95. data/config/importmap.rb +10 -2
  96. data/config/locales/en.yml +23 -0
  97. data/config/locales/es.yml +23 -0
  98. data/config/locales/ja.yml +23 -0
  99. data/lib/lostcoords/ui/action_view.rb +6 -3
  100. data/lib/lostcoords/ui/builders.rb +2 -2
  101. data/lib/lostcoords/ui/form_builder.rb +126 -0
  102. data/lib/lostcoords/ui/parts.rb +154 -0
  103. data/lib/lostcoords/ui/stylesheets.rb +25 -0
  104. data/lib/lostcoords/ui/tokens.rb +24 -1
  105. data/lib/lostcoords/ui/version.rb +1 -1
  106. data/lib/tasks/lostcoords_ui.rake +11 -0
  107. metadata +54 -21
  108. data/app/assets/tailwind/lostcoords/ui/reading.css +0 -60
  109. data/app/views/lostcoords/ui/components/_form_alert.html.erb +0 -4
  110. data/app/views/lostcoords/ui/components/_lc_disclosure.html.erb +0 -5
  111. data/app/views/lostcoords/ui/components/_skeleton.html.erb +0 -4
  112. data/app/views/lostcoords/ui/components/_spinner.html.erb +0 -10
  113. data/app/views/lostcoords/ui/components/_ui_floating_panel.html.erb +0 -8
  114. data/config/helper_renames.json +0 -113
  115. data/script/migrate_helpers.rb +0 -136
  116. /data/app/javascript/lostcoords/ui/controllers/{ui_copy_controller.js → lc_copy_controller.js} +0 -0
  117. /data/app/javascript/lostcoords/ui/controllers/{ui_toggle_controller.js → lc_toggle_controller.js} +0 -0
@@ -1,60 +0,0 @@
1
- @layer utilities {
2
- .ui-prose {
3
- --tw-prose-body: var(--ink-2);
4
- --tw-prose-headings: var(--ink);
5
- --tw-prose-lead: var(--ink-2);
6
- --tw-prose-links: var(--accent, var(--sky));
7
- --tw-prose-bold: var(--ink);
8
- --tw-prose-counters: var(--ink-3);
9
- --tw-prose-bullets: var(--ink-3);
10
- --tw-prose-hr: var(--hairline);
11
- --tw-prose-quotes: var(--ink);
12
- --tw-prose-quote-borders: var(--hairline);
13
- --tw-prose-captions: var(--ink-3);
14
- --tw-prose-code: var(--ink);
15
- --tw-prose-pre-code: var(--ink);
16
- --tw-prose-pre-bg: var(--paper-2);
17
- --tw-prose-th-borders: var(--hairline);
18
- --tw-prose-td-borders: var(--hairline);
19
-
20
- & :where(p, ul, ol, dl, blockquote, pre, table, figure, hr):not(:where(.not-prose, .not-prose *)) {
21
- margin-block: var(--ui-reading-flow, 1.25em) 0;
22
- }
23
-
24
- & :where(h1, h2, h3, h4):not(:where(.not-prose, .not-prose *)) {
25
- margin-block: calc(var(--ui-reading-flow, 1.25em) * 1.6) 0;
26
- font-family: var(--font-display, inherit);
27
- line-height: var(--ui-reading-heading-leading, 1.25);
28
- text-wrap: balance;
29
- }
30
-
31
- & :where(h1, h2, h3, h4) + :where(p, ul, ol):not(:where(.not-prose, .not-prose *)) {
32
- margin-block-start: calc(var(--ui-reading-flow, 1.25em) * 0.5);
33
- }
34
-
35
- & :where(li > p, li > ul, li > ol):not(:where(.not-prose, .not-prose *)) {
36
- margin-block-start: calc(var(--ui-reading-flow, 1.25em) * 0.5);
37
- }
38
-
39
- & > :where(:first-child):not(:where(.not-prose, .not-prose *)) {
40
- margin-block-start: 0;
41
- }
42
-
43
- &:lang(ja) {
44
- line-break: strict;
45
- word-break: normal;
46
- overflow-wrap: anywhere;
47
- }
48
-
49
- &:lang(ja) :where(h1, h2, h3, h4):not(:where(.not-prose, .not-prose *)) {
50
- line-height: var(--ui-reading-heading-leading, 1.4);
51
- letter-spacing: normal;
52
- }
53
- }
54
-
55
- @supports (word-break: auto-phrase) {
56
- .ui-prose:lang(ja) :where(h1, h2, h3, h4):not(:where(.not-prose, .not-prose *)) {
57
- word-break: auto-phrase;
58
- }
59
- }
60
- }
@@ -1,4 +0,0 @@
1
- <%= tag.div class: lc_form_alert_class(extra: local_assigns[:class]), data: { tone: local_assigns.fetch(:tone, :error) }, **local_assigns.fetch(:attributes, {}) do %>
2
- <%= lc_form_alert_icon(tone: local_assigns.fetch(:tone, :error)) %>
3
- <p class="m-0 min-w-0 text-pretty [overflow-wrap:anywhere]"><%= local_assigns[:message] %></p>
4
- <% end %>
@@ -1,5 +0,0 @@
1
- <%# locals: (title:, attributes:, content:) %>
2
- <%= tag.details(**attributes) do %>
3
- <summary class="cursor-pointer font-semibold"><%= title %></summary>
4
- <div class="pt-4"><%= content %></div>
5
- <% end %>
@@ -1,4 +0,0 @@
1
- <%# Decorative: the surrounding loading region conveys state. Pass sizing via :class. %>
2
- <%= tag.span class: lc_skeleton_class(extra: local_assigns.fetch(:class, "h-4 w-full")),
3
- "aria-hidden": "true",
4
- **local_assigns.fetch(:attributes, {}) %>
@@ -1,10 +0,0 @@
1
- <%# Inherits currentColor. Pass label: nil inside an already-labelled loading region. %>
2
- <% label = local_assigns.fetch(:label, "Loading…") %>
3
-
4
- <%= tag.span class: lc_spinner_class(size: local_assigns.fetch(:size, :base), extra: local_assigns[:class]),
5
- role: (label.present? ? "status" : nil),
6
- **local_assigns.fetch(:attributes, {}) do %>
7
- <% if label.present? %>
8
- <span class="sr-only"><%= label %></span>
9
- <% end %>
10
- <% end %>
@@ -1,8 +0,0 @@
1
- <%# locals: (id:, label:, title:, controller:, role:, attributes:, content:) %>
2
- <%= tag.div(class: "relative inline-flex", data: { controller: controller }) do %>
3
- <%= tag.button label, id: "#{id}-trigger", type: "button", class: lc_button_class(variant: :secondary), aria: { haspopup: role, expanded: false, controls: id }, data: { "#{controller}_target" => "trigger", action: "#{controller}#toggle" } %>
4
- <%= tag.div(**attributes) do %>
5
- <% if title %><h2 id="<%= id %>-title" class="font-semibold"><%= title %></h2><% end %>
6
- <%= content %>
7
- <% end %>
8
- <% end %>
@@ -1,113 +0,0 @@
1
- {
2
- "avatar_class": "lc_avatar_class",
3
- "avatar_stack_class": "lc_avatar_stack_class",
4
- "avatar_stack_row_class": "lc_avatar_stack_row_class",
5
- "badge_class": "lc_badge_class",
6
- "button_class": "lc_button_class",
7
- "button_variants": "lc_button_variants",
8
- "card_class": "lc_card_class",
9
- "card_surface_class": "lc_card_surface_class",
10
- "container_class": "lc_container_class",
11
- "dialog_class": "lc_dialog_class",
12
- "dialog_kinds": "lc_dialog_kinds",
13
- "dialog_surface_class": "lc_dialog_surface_class",
14
- "dropdown_trigger_class": "lc_dropdown_trigger_class",
15
- "emoji_picker_count": "lc_emoji_picker_count",
16
- "emoji_picker_groups": "lc_emoji_picker_groups",
17
- "emoji_picker_search_labels": "lc_emoji_picker_search_labels",
18
- "emoji_picker_search_name": "lc_emoji_picker_search_name",
19
- "emoji_picker_search_text": "lc_emoji_picker_search_text",
20
- "emoji_picker_toneable?": "lc_emoji_picker_toneable?",
21
- "emoji_picker_version": "lc_emoji_picker_version",
22
- "empty_state_class": "lc_empty_state_class",
23
- "eyebrow_class": "lc_eyebrow_class",
24
- "field_class": "lc_field_class",
25
- "field_label_class": "lc_field_label_class",
26
- "form_actions_class": "lc_form_actions_class",
27
- "form_alert_class": "lc_form_alert_class",
28
- "form_alert_icon": "lc_form_alert_icon",
29
- "form_card_class": "lc_form_card_class",
30
- "glass_class": "lc_glass_class",
31
- "heading_class": "lc_heading_class",
32
- "heroicon": "lc_icon",
33
- "icon_badge_class": "lc_icon_badge_class",
34
- "icon_button_class": "lc_icon_button_class",
35
- "icon_medallion_class": "lc_icon_medallion_class",
36
- "inline_class": "lc_inline_class",
37
- "input_class": "lc_input_class",
38
- "menu_option_class": "lc_menu_option_class",
39
- "menu_popover_class": "lc_menu_popover_class",
40
- "pill_class": "lc_pill_class",
41
- "pill_row_class": "lc_pill_row_class",
42
- "popover_class": "lc_popover_class",
43
- "prose_class": "lc_prose_class",
44
- "section_header_class": "lc_section_header_class",
45
- "segmented_class": "lc_segmented_class",
46
- "segmented_tab_class": "lc_segmented_tab_class",
47
- "select_class": "lc_select_class",
48
- "sheet_chip_class": "lc_sheet_chip_class",
49
- "sheet_data": "lc_sheet_data",
50
- "sheet_datetime_input_class": "lc_sheet_datetime_input_class",
51
- "sheet_row_class": "lc_sheet_row_class",
52
- "sheet_row_label_class": "lc_sheet_row_label_class",
53
- "sheet_text_input_class": "lc_sheet_text_input_class",
54
- "skeleton_class": "lc_skeleton_class",
55
- "spinner_class": "lc_spinner_class",
56
- "stack_class": "lc_stack_class",
57
- "status_pill_class": "lc_status_pill_class",
58
- "text_class": "lc_text_class",
59
- "textarea_class": "lc_textarea_class",
60
- "tw": "lc_tw",
61
- "ui_attributes": "lc_attributes",
62
- "ui_avatar": "lc_avatar",
63
- "ui_avatar_stack": "lc_avatar_stack",
64
- "ui_badge": "lc_badge",
65
- "ui_button": "lc_button",
66
- "ui_button_attributes": "lc_button_attributes",
67
- "ui_button_content": "lc_button_content",
68
- "ui_button_link_to": "lc_button_link_to",
69
- "ui_button_to": "lc_button_to",
70
- "ui_card": "lc_card",
71
- "ui_card_surface": "lc_card_surface",
72
- "ui_container": "lc_container",
73
- "ui_copy_button": "lc_copy_button",
74
- "ui_copy_content": "lc_copy_content",
75
- "ui_dialog": "lc_dialog",
76
- "ui_disclosure": "lc_disclosure",
77
- "ui_emoji_picker": "lc_emoji_picker",
78
- "ui_empty_state": "lc_empty_state",
79
- "ui_eyebrow": "lc_eyebrow",
80
- "ui_field": "lc_field",
81
- "ui_form_actions": "lc_form_actions",
82
- "ui_form_alert": "lc_form_alert",
83
- "ui_form_card": "lc_form_card",
84
- "ui_heading": "lc_heading",
85
- "ui_icon_badge": "lc_icon_badge",
86
- "ui_icon_button": "lc_icon_button",
87
- "ui_inline": "lc_inline",
88
- "ui_input": "lc_input",
89
- "ui_label!": "lc_label!",
90
- "ui_medallion": "lc_medallion",
91
- "ui_menu": "lc_menu",
92
- "ui_menu_option": "lc_menu_option",
93
- "ui_normalize_attributes": "lc_normalize_attributes",
94
- "ui_option!": "lc_option!",
95
- "ui_pill": "lc_pill",
96
- "ui_pill_row": "lc_pill_row",
97
- "ui_popover": "lc_popover",
98
- "ui_prose": "lc_prose",
99
- "ui_render_dialog": "lc_render_dialog",
100
- "ui_render_panel": "lc_render_panel",
101
- "ui_section": "lc_section",
102
- "ui_segmented": "lc_segmented",
103
- "ui_segmented_tab": "lc_segmented_tab",
104
- "ui_select": "lc_select",
105
- "ui_sheet": "lc_sheet",
106
- "ui_skeleton": "lc_skeleton",
107
- "ui_spinner": "lc_spinner",
108
- "ui_stack": "lc_stack",
109
- "ui_status_pill": "lc_status_pill",
110
- "ui_text": "lc_text",
111
- "ui_textarea": "lc_textarea",
112
- "ui_toast": "lc_toast"
113
- }
@@ -1,136 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "json"
4
- require "find"
5
- require "optparse"
6
- require "ripper"
7
-
8
- module HelperMigration
9
- LIBRARY = File.expand_path("..", __dir__)
10
- NAMES = JSON.parse(File.read(File.join(LIBRARY, "config/helper_renames.json"))).freeze
11
- PARTIALS = %w[dialog disclosure].to_h do |name|
12
- [ "lostcoords/ui/components/ui_#{name}", "lostcoords/ui/components/lc_#{name}" ]
13
- end.freeze
14
-
15
- def self.method_locations(node, locations = {})
16
- return locations unless node.is_a?(Array)
17
-
18
- name = case node.first
19
- when :def, :vcall, :fcall, :command, :symbol then node[1]
20
- when :defs, :call, :command_call then node[3]
21
- end
22
- locations[name[2]] = true if name.is_a?(Array) && name.first == :@ident
23
- node.each { |child| method_locations(child, locations) }
24
- locations
25
- end
26
-
27
- def self.rewrite(source, program: source, prefix_lines: 0)
28
- syntax = Ripper.sexp(program)
29
- raise ArgumentError, "Cannot parse Ruby source for helper migration" unless syntax
30
-
31
- methods = method_locations(syntax)
32
- offsets = [ 0 ]
33
- source.each_line { |line| offsets << offsets.last + line.bytesize }
34
- contexts = []
35
- edits = Ripper.lex(program).filter_map do |position, type, value, _|
36
- replacement = case type
37
- when :on_ident then NAMES[value] if methods[position]
38
- when :on_tstring_content then contexts.last == :symbol ? NAMES[value] : PARTIALS[value]
39
- end
40
- case type
41
- when :on_symbeg then contexts << :symbol unless value == ":"
42
- when :on_qsymbols_beg, :on_symbols_beg then contexts << :symbol
43
- when :on_tstring_beg, :on_heredoc_beg, :on_qwords_beg, :on_words_beg, :on_regexp_beg then contexts << :string
44
- when :on_tstring_end, :on_heredoc_end, :on_regexp_end then contexts.pop
45
- end
46
- line = position[0] - prefix_lines
47
- [ offsets.fetch(line - 1) + position[1], value.bytesize, replacement ] if replacement && line.positive?
48
- end
49
- updated = source.b
50
- edits.reverse_each { |offset, length, value| updated[offset, length] = value.b }
51
- updated.force_encoding(source.encoding)
52
- end
53
-
54
- def self.ruby(source)
55
- rewrite(source)
56
- end
57
-
58
- def self.erb(source)
59
- program = source.b.gsub(/[^\r\n]/n, " ")
60
- source.to_enum(:scan, /(<%(?!%)(?:==?|-)?)(.*?)(-?%>)/m).each do
61
- match = Regexp.last_match
62
- opening, body, closing = match.captures
63
- next if body.start_with?("#")
64
-
65
- start = match.pre_match.bytesize + opening.bytesize
66
- program[start, body.bytesize] = body.b
67
- program[start + body.bytesize, closing.bytesize] = ";" + " " * (closing.bytesize - 1)
68
- end
69
- signature = source[/<%#\s*locals:\s*\((.*?)\)\s*%>/m, 1]
70
- prefix = "def __template(#{signature})\n"
71
- suffix = "\nend"
72
- rewrite(source, program: prefix + program.force_encoding(source.encoding) + suffix, prefix_lines: prefix.count("\n"))
73
- end
74
-
75
- def self.markdown(source)
76
- source = source.gsub(/(^```(ruby|rb|erb)[^\n]*\n)(.*?)(^```[ \t]*$)/m) do
77
- opening, language, body, closing = Regexp.last_match.captures
78
- "#{opening}#{language == 'erb' ? erb(body) : ruby(body)}#{closing}"
79
- end
80
- source.gsub(/`([^`\n]+)`/) { |match| NAMES.key?(Regexp.last_match[1]) ? "`#{NAMES.fetch(Regexp.last_match[1])}`" : match }
81
- end
82
-
83
- def self.changes(root)
84
- files = Find.find(root).filter_map do |path|
85
- next if File.symlink?(path)
86
- if File.directory?(path)
87
- name = File.basename(path)
88
- Find.prune if path != root && (name.start_with?(".") || %w[node_modules vendor tmp log storage pkg coverage builds dist].include?(name))
89
- next
90
- end
91
- path.delete_prefix("#{root}/")
92
- end
93
- files.sort.filter_map do |relative|
94
- path = File.join(root, relative)
95
- next if File.symlink?(path) || relative.start_with?("vendor/")
96
- next if root == LIBRARY && (relative == "README.md" || relative.start_with?("docs/", "plans/"))
97
-
98
- extension = File.extname(path)
99
- next unless %w[.rb .erb .md].include?(extension) || extension.empty?
100
-
101
- source = File.binread(path).force_encoding(Encoding::UTF_8)
102
- updated = case extension
103
- when ".rb" then ruby(source)
104
- when ".erb" then erb(source)
105
- when ".md" then markdown(source)
106
- else ruby(source) if source.lines.first&.match?(/\A#!.*ruby/)
107
- end
108
- next unless updated
109
-
110
- name = File.basename(path).sub(/\A_ui_(dialog|disclosure)\.html\.erb\z/, '_lc_\1.html.erb')
111
- destination = File.join(File.dirname(path), name)
112
- abort "Refusing to overwrite #{destination}" if destination != path && (File.exist?(destination) || File.symlink?(destination))
113
- [ path, destination, updated ] if updated != source || destination != path
114
- end
115
- end
116
- end
117
-
118
- if $PROGRAM_NAME == __FILE__
119
- write = false
120
- OptionParser.new do |options|
121
- options.banner = "Usage: ruby migrate_helpers.rb [checkout] [--write]"
122
- options.on("--write", "Apply the migration after checking every destination") { write = true }
123
- end.parse!
124
- abort "Expected one checkout path" if ARGV.size > 1
125
- root = File.realpath(ARGV.first || Dir.pwd)
126
- changes = HelperMigration.changes(root)
127
- changes.each do |path, destination, source|
128
- puts path.delete_prefix("#{root}/")
129
- next unless write
130
-
131
- File.binwrite(path, source)
132
- File.rename(path, destination) unless path == destination
133
- end
134
- puts "#{changes.size} files #{write ? 'updated' : 'would change'}"
135
- exit 1 if changes.any? && !write
136
- end