architext 0.2.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +84 -0
- data/LICENSE +21 -0
- data/README.md +196 -0
- data/bin/architext +6 -0
- data/bin/setup +27 -0
- data/lib/architext/bundle.rb +20 -0
- data/lib/architext/cli.rb +444 -0
- data/lib/architext/clipboard.rb +58 -0
- data/lib/architext/obsidian.rb +84 -0
- data/lib/architext/picker.rb +39 -0
- data/lib/architext/search_results.rb +67 -0
- data/lib/architext/selection_parser.rb +32 -0
- data/lib/architext/settings.rb +40 -0
- data/lib/architext/terminal.rb +130 -0
- data/lib/architext/tui.rb +512 -0
- data/lib/architext/version.rb +5 -0
- data/lib/architext.rb +13 -0
- metadata +133 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Architext
|
|
4
|
+
class SelectionParser
|
|
5
|
+
def initialize(paths)
|
|
6
|
+
@paths = paths
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def parse(input)
|
|
10
|
+
normalized = input.to_s.strip.downcase
|
|
11
|
+
return @paths if %w[a all *].include?(normalized)
|
|
12
|
+
return [] if normalized.empty?
|
|
13
|
+
|
|
14
|
+
indexes = normalized.split(',').flat_map { |part| expand_part(part.strip) }
|
|
15
|
+
indexes.uniq.filter_map { |index| @paths[index - 1] }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def expand_part(part)
|
|
21
|
+
if part.match?(/\A\d+\z/)
|
|
22
|
+
[part.to_i]
|
|
23
|
+
elsif (match = part.match(/\A(\d+)\s*-\s*(\d+)\z/))
|
|
24
|
+
first = match[1].to_i
|
|
25
|
+
last = match[2].to_i
|
|
26
|
+
first <= last ? (first..last).to_a : (last..first).to_a
|
|
27
|
+
else
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Architext
|
|
6
|
+
class Settings
|
|
7
|
+
attr_reader :config_path
|
|
8
|
+
|
|
9
|
+
def initialize(config_path: default_config_path)
|
|
10
|
+
@config_path = config_path
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def default_vault
|
|
14
|
+
return nil unless File.file?(@config_path)
|
|
15
|
+
|
|
16
|
+
value = File.read(@config_path).strip
|
|
17
|
+
value.empty? ? nil : value
|
|
18
|
+
rescue StandardError
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def default_vault=(vault)
|
|
23
|
+
value = vault.to_s.strip
|
|
24
|
+
raise ArgumentError, 'default vault cannot be blank' if value.empty?
|
|
25
|
+
|
|
26
|
+
FileUtils.mkdir_p(File.dirname(@config_path))
|
|
27
|
+
File.write(@config_path, "#{value}\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def clear_default_vault
|
|
31
|
+
File.delete(@config_path) if File.file?(@config_path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def default_config_path
|
|
37
|
+
File.join(Dir.home, '.config', 'architext', 'default_vault')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Architext
|
|
4
|
+
module Terminal
|
|
5
|
+
RESET = "\e[0m"
|
|
6
|
+
HIDE_CURSOR = "\e[?25l"
|
|
7
|
+
SHOW_CURSOR = "\e[?25h"
|
|
8
|
+
CLEAR = "\e[2J"
|
|
9
|
+
CLEAR_TO_END = "\e[J"
|
|
10
|
+
CLEAR_LINE = "\e[2K"
|
|
11
|
+
HOME = "\e[H"
|
|
12
|
+
ALT_SCREEN = "\e[?1049h"
|
|
13
|
+
MAIN_SCREEN = "\e[?1049l"
|
|
14
|
+
|
|
15
|
+
PALETTE = {
|
|
16
|
+
ink: "\e[38;2;221;231;239m",
|
|
17
|
+
white: "\e[38;2;255;255;255m",
|
|
18
|
+
dim: "\e[38;2;126;140;155m",
|
|
19
|
+
faint: "\e[38;2;83;96;112m",
|
|
20
|
+
cyan: "\e[38;2;91;220;255m",
|
|
21
|
+
blue: "\e[38;2;98;151;255m",
|
|
22
|
+
green: "\e[38;2;91;232;184m",
|
|
23
|
+
amber: "\e[38;2;245;196;94m",
|
|
24
|
+
red: "\e[38;2;255;104;112m",
|
|
25
|
+
violet: "\e[38;2;190;142;255m",
|
|
26
|
+
bold: "\e[1m",
|
|
27
|
+
inverse: "\e[7m"
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
TAGS = PALETTE.transform_keys(&:to_s).merge('/' => RESET).freeze
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
def enabled?(io = $stdout)
|
|
35
|
+
io.respond_to?(:tty?) && io.tty? && ENV['NO_COLOR'].nil? && !dumb_terminal?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def alt_screen_supported?(io = $stdout)
|
|
39
|
+
enabled?(io) && ENV['ARCHITEXT_NO_ALT_SCREEN'].nil?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def paint(text, *styles, enabled: true)
|
|
43
|
+
return text.to_s unless enabled
|
|
44
|
+
|
|
45
|
+
styles.map { |style| PALETTE.fetch(style) }.join + text.to_s + RESET
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Tiny pretext-style renderer: "[cyan]text[/]" keeps visual markup readable.
|
|
49
|
+
def render(markup, enabled: true)
|
|
50
|
+
return strip_markup(markup) unless enabled
|
|
51
|
+
|
|
52
|
+
markup.to_s.gsub(%r{\[(/|[a-z_]+)\]}) { TAGS.fetch(Regexp.last_match(1), Regexp.last_match(0)) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def strip_markup(markup)
|
|
56
|
+
markup.to_s.gsub(%r{\[(/|[a-z_]+)\]}, '')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def visible_length(text)
|
|
60
|
+
display_width(strip_ansi(text))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def truncate(text, width)
|
|
64
|
+
plain = text.to_s
|
|
65
|
+
return '' if width <= 0
|
|
66
|
+
return plain if visible_length(plain) <= width
|
|
67
|
+
|
|
68
|
+
truncate_plain(strip_ansi(plain), width)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def strip_ansi(text)
|
|
72
|
+
text.to_s.gsub(/\e\[[0-9;?]*[A-Za-z]/, '')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def truncate_plain(text, width)
|
|
76
|
+
return '' if width <= 0
|
|
77
|
+
return text.to_s if display_width(text) <= width
|
|
78
|
+
return text.to_s.each_char.first.to_s if width <= 1
|
|
79
|
+
|
|
80
|
+
out = +''
|
|
81
|
+
used = 0
|
|
82
|
+
text.to_s.each_char do |char|
|
|
83
|
+
char_width = char_display_width(char)
|
|
84
|
+
break if used + char_width > width - 3
|
|
85
|
+
|
|
86
|
+
out << char
|
|
87
|
+
used += char_width
|
|
88
|
+
end
|
|
89
|
+
"#{out}..."
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def display_width(text)
|
|
93
|
+
text.to_s.each_char.sum { |char| char_display_width(char) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def char_display_width(char)
|
|
97
|
+
codepoint = char.ord
|
|
98
|
+
return 0 if codepoint < 32 || codepoint == 0x7F
|
|
99
|
+
return 0 if combining_mark?(codepoint)
|
|
100
|
+
return 2 if wide_codepoint?(codepoint)
|
|
101
|
+
|
|
102
|
+
1
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def combining_mark?(codepoint)
|
|
106
|
+
(0x0300..0x036F).cover?(codepoint) ||
|
|
107
|
+
(0x1AB0..0x1AFF).cover?(codepoint) ||
|
|
108
|
+
(0x1DC0..0x1DFF).cover?(codepoint) ||
|
|
109
|
+
(0x20D0..0x20FF).cover?(codepoint) ||
|
|
110
|
+
(0xFE00..0xFE0F).cover?(codepoint)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def wide_codepoint?(codepoint)
|
|
114
|
+
(0x1100..0x115F).cover?(codepoint) ||
|
|
115
|
+
(0x2329..0x232A).cover?(codepoint) ||
|
|
116
|
+
(0x2E80..0xA4CF).cover?(codepoint) ||
|
|
117
|
+
(0xAC00..0xD7A3).cover?(codepoint) ||
|
|
118
|
+
(0xF900..0xFAFF).cover?(codepoint) ||
|
|
119
|
+
(0xFE10..0xFE19).cover?(codepoint) ||
|
|
120
|
+
(0xFE30..0xFE6F).cover?(codepoint) ||
|
|
121
|
+
(0xFF00..0xFF60).cover?(codepoint) ||
|
|
122
|
+
(0xFFE0..0xFFE6).cover?(codepoint) ||
|
|
123
|
+
(0x1F000..0x1FAFF).cover?(codepoint)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def dumb_terminal?
|
|
127
|
+
ENV.fetch('TERM', '').downcase == 'dumb'
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|