shellfie 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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +21 -0
- data/README.md +334 -0
- data/Rakefile +8 -0
- data/assets/logo-header.svg +28 -0
- data/examples/animation.yml +33 -0
- data/examples/colored.yml +20 -0
- data/examples/demo.gif +0 -0
- data/examples/demo.png +0 -0
- data/examples/demo_animation.yml +31 -0
- data/examples/headless.png +0 -0
- data/examples/headless.yml +16 -0
- data/examples/scrolling.yml +48 -0
- data/examples/simple.yml +21 -0
- data/examples/theme_macos.png +0 -0
- data/examples/theme_ubuntu.png +0 -0
- data/examples/theme_windows.png +0 -0
- data/exe/shellfie +6 -0
- data/exe/shf +5 -0
- data/lib/shellfie/ansi_parser.rb +174 -0
- data/lib/shellfie/cli.rb +228 -0
- data/lib/shellfie/config.rb +65 -0
- data/lib/shellfie/errors.rb +15 -0
- data/lib/shellfie/gif_generator.rb +151 -0
- data/lib/shellfie/parser.rb +90 -0
- data/lib/shellfie/renderer.rb +193 -0
- data/lib/shellfie/themes/base.rb +72 -0
- data/lib/shellfie/themes/macos.rb +51 -0
- data/lib/shellfie/themes/ubuntu.rb +57 -0
- data/lib/shellfie/themes/windows_terminal.rb +64 -0
- data/lib/shellfie/version.rb +5 -0
- data/lib/shellfie.rb +13 -0
- data/shellfie.gemspec +32 -0
- metadata +94 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# animation.yml - Animated GIF example
|
|
2
|
+
theme: macos
|
|
3
|
+
title: "Terminal — zsh"
|
|
4
|
+
|
|
5
|
+
window:
|
|
6
|
+
width: 600
|
|
7
|
+
padding: 20
|
|
8
|
+
|
|
9
|
+
animation:
|
|
10
|
+
typing_speed: 50
|
|
11
|
+
command_delay: 500
|
|
12
|
+
cursor_blink: true
|
|
13
|
+
loop: true
|
|
14
|
+
|
|
15
|
+
frames:
|
|
16
|
+
- prompt: "$ "
|
|
17
|
+
type: "echo Hello, World!"
|
|
18
|
+
delay: 500
|
|
19
|
+
|
|
20
|
+
- output: "Hello, World!"
|
|
21
|
+
delay: 1000
|
|
22
|
+
|
|
23
|
+
- prompt: "$ "
|
|
24
|
+
type: "ls -la"
|
|
25
|
+
delay: 500
|
|
26
|
+
|
|
27
|
+
- output: |
|
|
28
|
+
total 24
|
|
29
|
+
drwxr-xr-x 5 user staff 160 Jan 10 10:00 .
|
|
30
|
+
drwxr-xr-x 3 user staff 96 Jan 10 09:00 ..
|
|
31
|
+
-rw-r--r-- 1 user staff 1024 Jan 10 10:00 README.md
|
|
32
|
+
-rw-r--r-- 1 user staff 512 Jan 10 10:00 Gemfile
|
|
33
|
+
delay: 2000
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# colored.yml - Example with ANSI color codes
|
|
2
|
+
theme: ubuntu
|
|
3
|
+
title: "user@ubuntu: ~/project"
|
|
4
|
+
|
|
5
|
+
window:
|
|
6
|
+
width: 700
|
|
7
|
+
padding: 20
|
|
8
|
+
|
|
9
|
+
lines:
|
|
10
|
+
- prompt: "\e[32muser@ubuntu\e[0m:\e[34m~/project\e[0m$ "
|
|
11
|
+
command: "bundle exec rspec"
|
|
12
|
+
|
|
13
|
+
- output: |
|
|
14
|
+
\e[32m.\e[0m\e[32m.\e[0m\e[32m.\e[0m\e[32m.\e[0m\e[32m.\e[0m\e[32m.\e[0m\e[32m.\e[0m\e[32m.\e[0m\e[32m.\e[0m\e[32m.\e[0m
|
|
15
|
+
|
|
16
|
+
- output: "Finished in 0.12 seconds (files took 0.5 seconds to load)"
|
|
17
|
+
- output: "\e[32m10 examples, 0 failures\e[0m"
|
|
18
|
+
|
|
19
|
+
- prompt: "\e[32muser@ubuntu\e[0m:\e[34m~/project\e[0m$ "
|
|
20
|
+
command: ""
|
data/examples/demo.gif
ADDED
|
Binary file
|
data/examples/demo.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
theme: macos
|
|
2
|
+
title: "Terminal — zsh"
|
|
3
|
+
|
|
4
|
+
window:
|
|
5
|
+
width: 550
|
|
6
|
+
padding: 18
|
|
7
|
+
visible_lines: 7
|
|
8
|
+
|
|
9
|
+
animation:
|
|
10
|
+
typing_speed: 35
|
|
11
|
+
command_delay: 400
|
|
12
|
+
loop: true
|
|
13
|
+
|
|
14
|
+
frames:
|
|
15
|
+
- prompt: "$ "
|
|
16
|
+
type: "shellfie generate config.yml -o demo.png"
|
|
17
|
+
delay: 600
|
|
18
|
+
|
|
19
|
+
- output: "Generated: demo.png"
|
|
20
|
+
delay: 1200
|
|
21
|
+
|
|
22
|
+
- prompt: "$ "
|
|
23
|
+
type: "shellfie themes"
|
|
24
|
+
delay: 500
|
|
25
|
+
|
|
26
|
+
- output: |
|
|
27
|
+
Available themes:
|
|
28
|
+
macos - macOS Terminal style
|
|
29
|
+
ubuntu - Ubuntu Terminal style
|
|
30
|
+
windows - Windows Terminal style
|
|
31
|
+
delay: 2000
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# headless.yml - Terminal output without window decoration
|
|
2
|
+
theme: macos
|
|
3
|
+
headless: true
|
|
4
|
+
|
|
5
|
+
window:
|
|
6
|
+
width: 500
|
|
7
|
+
padding: 15
|
|
8
|
+
|
|
9
|
+
lines:
|
|
10
|
+
- prompt: "$ "
|
|
11
|
+
command: "ruby -v"
|
|
12
|
+
|
|
13
|
+
- output: "ruby 3.3.0 (2024-12-25 revision 5124f9ac75) [arm64-darwin24]"
|
|
14
|
+
|
|
15
|
+
- prompt: "$ "
|
|
16
|
+
command: ""
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# scrolling.yml - Animation with fixed visible lines and scrolling
|
|
2
|
+
theme: macos
|
|
3
|
+
title: "Terminal — zsh"
|
|
4
|
+
|
|
5
|
+
window:
|
|
6
|
+
width: 600
|
|
7
|
+
padding: 20
|
|
8
|
+
visible_lines: 6 # Number of lines visible in the terminal window
|
|
9
|
+
|
|
10
|
+
animation:
|
|
11
|
+
typing_speed: 40
|
|
12
|
+
command_delay: 300
|
|
13
|
+
loop: true
|
|
14
|
+
|
|
15
|
+
frames:
|
|
16
|
+
- prompt: "$ "
|
|
17
|
+
type: "for i in {1..10}; do echo \"Line $i\"; done"
|
|
18
|
+
delay: 500
|
|
19
|
+
|
|
20
|
+
- output: "Line 1"
|
|
21
|
+
delay: 200
|
|
22
|
+
|
|
23
|
+
- output: "Line 2"
|
|
24
|
+
delay: 200
|
|
25
|
+
|
|
26
|
+
- output: "Line 3"
|
|
27
|
+
delay: 200
|
|
28
|
+
|
|
29
|
+
- output: "Line 4"
|
|
30
|
+
delay: 200
|
|
31
|
+
|
|
32
|
+
- output: "Line 5"
|
|
33
|
+
delay: 200
|
|
34
|
+
|
|
35
|
+
- output: "Line 6"
|
|
36
|
+
delay: 200
|
|
37
|
+
|
|
38
|
+
- output: "Line 7"
|
|
39
|
+
delay: 200
|
|
40
|
+
|
|
41
|
+
- output: "Line 8"
|
|
42
|
+
delay: 200
|
|
43
|
+
|
|
44
|
+
- output: "Line 9"
|
|
45
|
+
delay: 200
|
|
46
|
+
|
|
47
|
+
- output: "Line 10"
|
|
48
|
+
delay: 1500
|
data/examples/simple.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# simple.yml - Basic terminal screenshot example
|
|
2
|
+
theme: macos
|
|
3
|
+
title: "Terminal — zsh"
|
|
4
|
+
|
|
5
|
+
window:
|
|
6
|
+
width: 600
|
|
7
|
+
padding: 20
|
|
8
|
+
|
|
9
|
+
lines:
|
|
10
|
+
- prompt: "$ "
|
|
11
|
+
command: "gem install shellfie"
|
|
12
|
+
|
|
13
|
+
- output: |
|
|
14
|
+
Fetching shellfie-0.1.0.gem
|
|
15
|
+
Successfully installed shellfie-0.1.0
|
|
16
|
+
1 gem installed
|
|
17
|
+
|
|
18
|
+
- prompt: "$ "
|
|
19
|
+
command: "shellfie --version"
|
|
20
|
+
|
|
21
|
+
- output: "shellfie 0.1.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/exe/shellfie
ADDED
data/exe/shf
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
module Shellfie
|
|
6
|
+
Segment = Struct.new(:text, :foreground, :background, :bold, :italic, :underline, keyword_init: true)
|
|
7
|
+
|
|
8
|
+
class AnsiParser
|
|
9
|
+
ANSI_REGEX = /\e\[([0-9;]*)m/
|
|
10
|
+
|
|
11
|
+
COLORS = {
|
|
12
|
+
30 => :black,
|
|
13
|
+
31 => :red,
|
|
14
|
+
32 => :green,
|
|
15
|
+
33 => :yellow,
|
|
16
|
+
34 => :blue,
|
|
17
|
+
35 => :magenta,
|
|
18
|
+
36 => :cyan,
|
|
19
|
+
37 => :white,
|
|
20
|
+
90 => :bright_black,
|
|
21
|
+
91 => :bright_red,
|
|
22
|
+
92 => :bright_green,
|
|
23
|
+
93 => :bright_yellow,
|
|
24
|
+
94 => :bright_blue,
|
|
25
|
+
95 => :bright_magenta,
|
|
26
|
+
96 => :bright_cyan,
|
|
27
|
+
97 => :bright_white
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
BG_COLORS = {
|
|
31
|
+
40 => :black,
|
|
32
|
+
41 => :red,
|
|
33
|
+
42 => :green,
|
|
34
|
+
43 => :yellow,
|
|
35
|
+
44 => :blue,
|
|
36
|
+
45 => :magenta,
|
|
37
|
+
46 => :cyan,
|
|
38
|
+
47 => :white,
|
|
39
|
+
100 => :bright_black,
|
|
40
|
+
101 => :bright_red,
|
|
41
|
+
102 => :bright_green,
|
|
42
|
+
103 => :bright_yellow,
|
|
43
|
+
104 => :bright_blue,
|
|
44
|
+
105 => :bright_magenta,
|
|
45
|
+
106 => :bright_cyan,
|
|
46
|
+
107 => :bright_white
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
def initialize
|
|
50
|
+
reset_state
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parse(text)
|
|
54
|
+
segments = []
|
|
55
|
+
scanner = StringScanner.new(text)
|
|
56
|
+
current_text = +""
|
|
57
|
+
|
|
58
|
+
while !scanner.eos?
|
|
59
|
+
if scanner.scan(ANSI_REGEX)
|
|
60
|
+
unless current_text.empty?
|
|
61
|
+
segments << create_segment(current_text)
|
|
62
|
+
current_text = +""
|
|
63
|
+
end
|
|
64
|
+
process_codes(scanner[1])
|
|
65
|
+
else
|
|
66
|
+
current_text << scanner.getch
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
segments << create_segment(current_text) unless current_text.empty?
|
|
71
|
+
segments
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def reset_state
|
|
77
|
+
@foreground = nil
|
|
78
|
+
@background = nil
|
|
79
|
+
@bold = false
|
|
80
|
+
@italic = false
|
|
81
|
+
@underline = false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def create_segment(text)
|
|
85
|
+
Segment.new(
|
|
86
|
+
text: text,
|
|
87
|
+
foreground: @foreground,
|
|
88
|
+
background: @background,
|
|
89
|
+
bold: @bold,
|
|
90
|
+
italic: @italic,
|
|
91
|
+
underline: @underline
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def process_codes(codes_str)
|
|
96
|
+
return reset_state if codes_str.empty?
|
|
97
|
+
|
|
98
|
+
codes = codes_str.split(";").map(&:to_i)
|
|
99
|
+
i = 0
|
|
100
|
+
|
|
101
|
+
while i < codes.length
|
|
102
|
+
code = codes[i]
|
|
103
|
+
|
|
104
|
+
case code
|
|
105
|
+
when 0
|
|
106
|
+
reset_state
|
|
107
|
+
when 1
|
|
108
|
+
@bold = true
|
|
109
|
+
when 3
|
|
110
|
+
@italic = true
|
|
111
|
+
when 4
|
|
112
|
+
@underline = true
|
|
113
|
+
when 22
|
|
114
|
+
@bold = false
|
|
115
|
+
when 23
|
|
116
|
+
@italic = false
|
|
117
|
+
when 24
|
|
118
|
+
@underline = false
|
|
119
|
+
when 30..37, 90..97
|
|
120
|
+
@foreground = COLORS[code]
|
|
121
|
+
when 38
|
|
122
|
+
i, @foreground = parse_extended_color(codes, i)
|
|
123
|
+
when 39
|
|
124
|
+
@foreground = nil
|
|
125
|
+
when 40..47, 100..107
|
|
126
|
+
@background = BG_COLORS[code]
|
|
127
|
+
when 48
|
|
128
|
+
i, @background = parse_extended_color(codes, i)
|
|
129
|
+
when 49
|
|
130
|
+
@background = nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
i += 1
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def parse_extended_color(codes, i)
|
|
138
|
+
return [i, nil] if codes[i + 1].nil?
|
|
139
|
+
|
|
140
|
+
case codes[i + 1]
|
|
141
|
+
when 5
|
|
142
|
+
color_index = codes[i + 2]
|
|
143
|
+
[i + 2, color_256(color_index)]
|
|
144
|
+
when 2
|
|
145
|
+
r, g, b = codes[i + 2], codes[i + 3], codes[i + 4]
|
|
146
|
+
[i + 4, format("#%02x%02x%02x", r, g, b)]
|
|
147
|
+
else
|
|
148
|
+
[i, nil]
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def color_256(index)
|
|
153
|
+
return nil unless index
|
|
154
|
+
|
|
155
|
+
if index < 16
|
|
156
|
+
standard_colors = %i[
|
|
157
|
+
black red green yellow blue magenta cyan white
|
|
158
|
+
bright_black bright_red bright_green bright_yellow
|
|
159
|
+
bright_blue bright_magenta bright_cyan bright_white
|
|
160
|
+
]
|
|
161
|
+
standard_colors[index]
|
|
162
|
+
elsif index < 232
|
|
163
|
+
index -= 16
|
|
164
|
+
r = (index / 36) * 51
|
|
165
|
+
g = ((index % 36) / 6) * 51
|
|
166
|
+
b = (index % 6) * 51
|
|
167
|
+
format("#%02x%02x%02x", r, g, b)
|
|
168
|
+
else
|
|
169
|
+
gray = (index - 232) * 10 + 8
|
|
170
|
+
format("#%02x%02x%02x", gray, gray, gray)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
data/lib/shellfie/cli.rb
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "../shellfie"
|
|
5
|
+
|
|
6
|
+
module Shellfie
|
|
7
|
+
class CLI
|
|
8
|
+
COMMANDS = %w[generate init themes validate version help].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(args)
|
|
11
|
+
@args = args
|
|
12
|
+
@options = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
return show_help if @args.empty?
|
|
17
|
+
|
|
18
|
+
command = @args.shift
|
|
19
|
+
|
|
20
|
+
case command
|
|
21
|
+
when "generate", "g"
|
|
22
|
+
run_generate
|
|
23
|
+
when "init"
|
|
24
|
+
run_init
|
|
25
|
+
when "themes"
|
|
26
|
+
run_themes
|
|
27
|
+
when "validate"
|
|
28
|
+
run_validate
|
|
29
|
+
when "version", "-v", "--version"
|
|
30
|
+
run_version
|
|
31
|
+
when "help", "-h", "--help"
|
|
32
|
+
show_help
|
|
33
|
+
else
|
|
34
|
+
puts "Unknown command: #{command}"
|
|
35
|
+
puts "Run 'shellfie help' for usage information."
|
|
36
|
+
exit 1
|
|
37
|
+
end
|
|
38
|
+
rescue Shellfie::Error => e
|
|
39
|
+
puts "Error: #{e.message}"
|
|
40
|
+
exit determine_exit_code(e)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def run_generate
|
|
46
|
+
parser = OptionParser.new do |opts|
|
|
47
|
+
opts.banner = "Usage: shellfie generate INPUT_FILE [options]"
|
|
48
|
+
|
|
49
|
+
opts.on("-o", "--output PATH", "Output file path (required)") do |path|
|
|
50
|
+
@options[:output] = path
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
opts.on("-t", "--theme NAME", "Override theme (macos, ubuntu, windows)") do |theme|
|
|
54
|
+
@options[:theme] = theme
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
opts.on("-a", "--animate", "Generate animated GIF") do
|
|
58
|
+
@options[:animate] = true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
opts.on("-s", "--scale FACTOR", "Output scale (1, 2, 3)") do |scale|
|
|
62
|
+
@options[:scale] = scale.to_i
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
opts.on("-w", "--width PIXELS", Integer, "Override width") do |width|
|
|
66
|
+
@options[:width] = width
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
opts.on("--no-shadow", "Disable shadow effect") do
|
|
70
|
+
@options[:shadow] = false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
opts.on("--transparent", "Transparent background") do
|
|
74
|
+
@options[:transparent] = true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
opts.on("--no-header", "Disable window header (headless mode)") do
|
|
78
|
+
@options[:headless] = true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
parser.parse!(@args)
|
|
83
|
+
|
|
84
|
+
input_file = @args.shift
|
|
85
|
+
raise ConfigError, "Input file is required" unless input_file
|
|
86
|
+
raise ConfigError, "Output file is required (use -o option)" unless @options[:output]
|
|
87
|
+
|
|
88
|
+
config = Parser.parse(input_file)
|
|
89
|
+
|
|
90
|
+
if @options[:theme] || @options[:width] || @options[:headless]
|
|
91
|
+
config = Config.new(
|
|
92
|
+
theme: @options[:theme] || config.theme,
|
|
93
|
+
title: config.title,
|
|
94
|
+
window: config.window.merge(@options[:width] ? { width: @options[:width] } : {}),
|
|
95
|
+
font: config.font,
|
|
96
|
+
lines: config.lines,
|
|
97
|
+
animation: config.animation,
|
|
98
|
+
frames: config.frames,
|
|
99
|
+
headless: @options[:headless] || config.headless
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if @options[:animate] || config.animated?
|
|
104
|
+
generator = GifGenerator.new(config)
|
|
105
|
+
output = generator.generate(
|
|
106
|
+
@options[:output],
|
|
107
|
+
scale: @options[:scale] || 1,
|
|
108
|
+
shadow: @options[:shadow] != false
|
|
109
|
+
)
|
|
110
|
+
else
|
|
111
|
+
renderer = Renderer.new(config)
|
|
112
|
+
output = renderer.render(
|
|
113
|
+
@options[:output],
|
|
114
|
+
scale: @options[:scale] || 1,
|
|
115
|
+
shadow: @options[:shadow] != false,
|
|
116
|
+
transparent: @options[:transparent] || false
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
puts "Generated: #{output}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def run_init
|
|
124
|
+
sample_config = <<~YAML
|
|
125
|
+
# Shellfie configuration file
|
|
126
|
+
theme: macos
|
|
127
|
+
title: "Terminal — zsh"
|
|
128
|
+
|
|
129
|
+
window:
|
|
130
|
+
width: 600
|
|
131
|
+
padding: 20
|
|
132
|
+
|
|
133
|
+
lines:
|
|
134
|
+
- prompt: "$ "
|
|
135
|
+
command: "gem install shellfie"
|
|
136
|
+
|
|
137
|
+
- output: |
|
|
138
|
+
Fetching shellfie-#{VERSION}.gem
|
|
139
|
+
Successfully installed shellfie-#{VERSION}
|
|
140
|
+
1 gem installed
|
|
141
|
+
|
|
142
|
+
- prompt: "$ "
|
|
143
|
+
command: "shellfie --version"
|
|
144
|
+
|
|
145
|
+
- output: "shellfie #{VERSION}"
|
|
146
|
+
YAML
|
|
147
|
+
|
|
148
|
+
puts sample_config
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def run_themes
|
|
152
|
+
puts "Available themes:"
|
|
153
|
+
puts
|
|
154
|
+
puts " macos - macOS Terminal style (red/yellow/green buttons, left side)"
|
|
155
|
+
puts " ubuntu - Ubuntu Terminal style (buttons on right side)"
|
|
156
|
+
puts " windows - Windows Terminal style (square corners, icons)"
|
|
157
|
+
puts
|
|
158
|
+
puts "Use: shellfie generate config.yml -o output.png -t THEME_NAME"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def run_validate
|
|
162
|
+
input_file = @args.shift
|
|
163
|
+
raise ConfigError, "Input file is required" unless input_file
|
|
164
|
+
|
|
165
|
+
config = Parser.parse(input_file)
|
|
166
|
+
puts "✓ Configuration is valid"
|
|
167
|
+
puts " Theme: #{config.theme}"
|
|
168
|
+
puts " Title: #{config.title}"
|
|
169
|
+
puts " Lines: #{config.lines.size}"
|
|
170
|
+
puts " Mode: #{config.animated? ? "animated" : "static"}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def run_version
|
|
174
|
+
puts "shellfie #{VERSION}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def show_help
|
|
178
|
+
puts <<~HELP
|
|
179
|
+
Shellfie - Terminal screenshot-style image generator
|
|
180
|
+
|
|
181
|
+
Usage: shellfie <command> [options]
|
|
182
|
+
shf <command> [options]
|
|
183
|
+
|
|
184
|
+
Commands:
|
|
185
|
+
generate Generate image from configuration file
|
|
186
|
+
init Output sample configuration
|
|
187
|
+
themes List available themes
|
|
188
|
+
validate Validate configuration file
|
|
189
|
+
version Show version
|
|
190
|
+
help Show this help
|
|
191
|
+
|
|
192
|
+
Generate Options:
|
|
193
|
+
-o, --output PATH Output file path (required)
|
|
194
|
+
-t, --theme NAME Override theme (macos, ubuntu, windows)
|
|
195
|
+
-a, --animate Generate animated GIF
|
|
196
|
+
-s, --scale FACTOR Output scale (1, 2, 3)
|
|
197
|
+
-w, --width PIXELS Override width
|
|
198
|
+
--no-shadow Disable shadow effect
|
|
199
|
+
--no-header Disable window header (headless mode)
|
|
200
|
+
--transparent Transparent background
|
|
201
|
+
|
|
202
|
+
Examples:
|
|
203
|
+
shellfie generate config.yml -o terminal.png
|
|
204
|
+
shellfie generate config.yml -o demo.gif --animate
|
|
205
|
+
shellfie generate config.yml -o retina.png --scale 2
|
|
206
|
+
shellfie init > my-config.yml
|
|
207
|
+
shellfie themes
|
|
208
|
+
|
|
209
|
+
# Short form
|
|
210
|
+
shf generate config.yml -o terminal.png
|
|
211
|
+
shf init > config.yml
|
|
212
|
+
HELP
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def determine_exit_code(error)
|
|
216
|
+
case error
|
|
217
|
+
when ParseError, ValidationError
|
|
218
|
+
2
|
|
219
|
+
when RenderError, ImageError
|
|
220
|
+
3
|
|
221
|
+
when DependencyError
|
|
222
|
+
4
|
|
223
|
+
else
|
|
224
|
+
1
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shellfie
|
|
4
|
+
class Config
|
|
5
|
+
DEFAULTS = {
|
|
6
|
+
theme: "macos",
|
|
7
|
+
window: {
|
|
8
|
+
width: 600,
|
|
9
|
+
padding: 20,
|
|
10
|
+
opacity: 1.0,
|
|
11
|
+
visible_lines: nil
|
|
12
|
+
},
|
|
13
|
+
font: {
|
|
14
|
+
family: "Monaco",
|
|
15
|
+
size: 14,
|
|
16
|
+
line_height: 1.4
|
|
17
|
+
},
|
|
18
|
+
animation: {
|
|
19
|
+
typing_speed: 80,
|
|
20
|
+
command_delay: 500,
|
|
21
|
+
cursor_blink: true,
|
|
22
|
+
loop: false
|
|
23
|
+
}
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
attr_reader :theme, :title, :window, :font, :lines, :animation, :frames, :headless
|
|
27
|
+
|
|
28
|
+
def initialize(options = {})
|
|
29
|
+
merged = merge_defaults(options)
|
|
30
|
+
@theme = merged[:theme]
|
|
31
|
+
@title = merged[:title] || "Terminal"
|
|
32
|
+
@window = merged[:window]
|
|
33
|
+
@font = merged[:font]
|
|
34
|
+
@lines = merged[:lines] || []
|
|
35
|
+
@animation = merged[:animation]
|
|
36
|
+
@frames = merged[:frames] || []
|
|
37
|
+
@headless = options[:headless] || false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def static?
|
|
41
|
+
@frames.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def animated?
|
|
45
|
+
!static?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def merge_defaults(options)
|
|
51
|
+
result = {}
|
|
52
|
+
DEFAULTS.each do |key, value|
|
|
53
|
+
result[key] = if value.is_a?(Hash) && options[key].is_a?(Hash)
|
|
54
|
+
value.merge(options[key])
|
|
55
|
+
else
|
|
56
|
+
options.key?(key) ? options[key] : value
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
result[:title] = options[:title]
|
|
60
|
+
result[:lines] = options[:lines]
|
|
61
|
+
result[:frames] = options[:frames]
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shellfie
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class ConfigError < Error; end
|
|
7
|
+
class ParseError < ConfigError; end
|
|
8
|
+
class ValidationError < ConfigError; end
|
|
9
|
+
|
|
10
|
+
class RenderError < Error; end
|
|
11
|
+
class FontError < RenderError; end
|
|
12
|
+
class ImageError < RenderError; end
|
|
13
|
+
|
|
14
|
+
class DependencyError < Error; end
|
|
15
|
+
end
|