fastqr 1.0.1

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/BUILD.md +482 -0
  3. data/CHANGELOG.md +51 -0
  4. data/CMakeLists.txt +126 -0
  5. data/CONTRIBUTING.md +63 -0
  6. data/DISTRIBUTION.md +730 -0
  7. data/INSTALL.md +171 -0
  8. data/LICENSE +39 -0
  9. data/PREBUILT.md +171 -0
  10. data/README.md +312 -0
  11. data/VERSION +1 -0
  12. data/bindings/nodejs/binding.gyp +38 -0
  13. data/bindings/nodejs/fastqr_node.cpp +125 -0
  14. data/bindings/nodejs/index.d.ts +80 -0
  15. data/bindings/nodejs/index.js +187 -0
  16. data/bindings/nodejs/lib/platform.js +72 -0
  17. data/bindings/nodejs/package.json +45 -0
  18. data/bindings/nodejs/test/test.js +45 -0
  19. data/bindings/php/fastqr_php.cpp +85 -0
  20. data/bindings/php/src/FastQR.php +316 -0
  21. data/bindings/php/tests/FastQRTest.php +97 -0
  22. data/bindings/ruby/extconf.rb +29 -0
  23. data/bindings/ruby/fastqr_ruby.cpp +122 -0
  24. data/bindings/ruby/lib/fastqr/platform.rb +70 -0
  25. data/bindings/ruby/lib/fastqr/version.rb +6 -0
  26. data/bindings/ruby/lib/fastqr.rb +129 -0
  27. data/bindings/ruby/prebuilt/macos-arm64.tar.gz +1 -0
  28. data/bindings/ruby/prebuilt/macos-x86_64.tar.gz +1 -0
  29. data/build.sh +109 -0
  30. data/cmake/fastqrConfig.cmake.in +6 -0
  31. data/composer.json +36 -0
  32. data/docs/CLI_USAGE.md +478 -0
  33. data/docs/NODEJS_USAGE.md +694 -0
  34. data/docs/PHP_USAGE.md +815 -0
  35. data/docs/README.md +191 -0
  36. data/docs/RUBY_USAGE.md +537 -0
  37. data/examples/CMakeLists.txt +7 -0
  38. data/examples/basic.cpp +58 -0
  39. data/include/fastqr.h +97 -0
  40. data/include/stb_image.h +7988 -0
  41. data/include/stb_image_write.h +1724 -0
  42. data/phpunit.xml +18 -0
  43. data/prebuilt/README.md +131 -0
  44. data/scripts/README.md +248 -0
  45. data/scripts/build-binaries.sh +98 -0
  46. data/scripts/build-local.sh +18 -0
  47. data/scripts/install.sh +87 -0
  48. data/scripts/release.sh +78 -0
  49. data/scripts/update-version.sh +58 -0
  50. data/src/cli.cpp +316 -0
  51. data/src/fastqr.cpp +665 -0
  52. data/test.sh +86 -0
  53. metadata +155 -0
@@ -0,0 +1,316 @@
1
+ <?php
2
+
3
+ /**
4
+ * FastQR - Fast QR Code Generator
5
+ * PHP Binding using FFI
6
+ *
7
+ * Copyright (C) 2025 FastQR Project
8
+ * Licensed under LGPL-2.1
9
+ */
10
+
11
+ namespace FastQR;
12
+
13
+ use FFI;
14
+ use RuntimeException;
15
+
16
+ class FastQR
17
+ {
18
+ private static ?FFI $ffi = null;
19
+ private static bool $initialized = false;
20
+
21
+ /**
22
+ * Initialize FFI
23
+ */
24
+ private static function init(): void
25
+ {
26
+ if (self::$initialized) {
27
+ return;
28
+ }
29
+
30
+ // Detect platform
31
+ $os = PHP_OS_FAMILY === 'Darwin' ? 'macos' : (PHP_OS_FAMILY === 'Linux' ? 'linux' : 'unknown');
32
+ $arch = php_uname('m');
33
+ if ($arch === 'x86_64' || $arch === 'amd64') {
34
+ $arch = 'x86_64';
35
+ } elseif ($arch === 'aarch64' || $arch === 'arm64') {
36
+ $arch = 'arm64';
37
+ }
38
+ $platform = "$os-$arch";
39
+ $ext = $os === 'macos' ? 'dylib' : 'so';
40
+
41
+ // Try to find the library (pre-built first, then system)
42
+ $libPaths = [
43
+ __DIR__ . "/../../prebuilt/$platform/lib/libfastqr.$ext", // Pre-built binary
44
+ '/usr/local/lib/libfastqr.' . $ext, // System install
45
+ '/usr/lib/libfastqr.' . $ext,
46
+ __DIR__ . '/../../../build/libfastqr.' . $ext, // Local build
47
+ ];
48
+
49
+ $libPath = null;
50
+ foreach ($libPaths as $path) {
51
+ if (file_exists($path)) {
52
+ $libPath = $path;
53
+ break;
54
+ }
55
+ }
56
+
57
+ if ($libPath === null) {
58
+ throw new RuntimeException(
59
+ "FastQR library not found for platform: $platform\n" .
60
+ "Please install fastqr or build from source."
61
+ );
62
+ }
63
+
64
+ // Define C interface
65
+ $header = '
66
+ typedef struct {
67
+ int size;
68
+ int optimize_size;
69
+ unsigned char foreground_r;
70
+ unsigned char foreground_g;
71
+ unsigned char foreground_b;
72
+ unsigned char background_r;
73
+ unsigned char background_g;
74
+ unsigned char background_b;
75
+ int ec_level;
76
+ const char* logo_path;
77
+ int logo_size_percent;
78
+ const char* format;
79
+ int quality;
80
+ } QROptions;
81
+
82
+ bool fastqr_generate(const char* data, const char* output_path, QROptions* options);
83
+ const char* fastqr_version(void);
84
+ ';
85
+
86
+ self::$ffi = FFI::cdef($header, $libPath);
87
+ self::$initialized = true;
88
+ }
89
+
90
+ /**
91
+ * Generate QR code
92
+ *
93
+ * @param string $data Data to encode (UTF-8 supported)
94
+ * @param string $outputPath Path to save the QR code image
95
+ * @param array $options Generation options
96
+ * @return bool true if successful
97
+ *
98
+ * @throws RuntimeException if generation fails
99
+ *
100
+ * @example
101
+ * ```php
102
+ * use FastQR\FastQR;
103
+ *
104
+ * // Basic usage
105
+ * FastQR::generate('Hello World', 'qr.png');
106
+ *
107
+ * // With options
108
+ * FastQR::generate('Hello', 'qr.png', [
109
+ * 'size' => 500,
110
+ * 'optimizeSize' => true,
111
+ * 'foreground' => [255, 0, 0],
112
+ * 'background' => [255, 255, 200],
113
+ * 'errorLevel' => 'H'
114
+ * ]);
115
+ *
116
+ * // With logo
117
+ * FastQR::generate('Company', 'qr.png', [
118
+ * 'size' => 600,
119
+ * 'logo' => 'logo.png',
120
+ * 'logoSize' => 25
121
+ * ]);
122
+ *
123
+ * // UTF-8 support
124
+ * FastQR::generate('Xin chào Việt Nam! 🇻🇳', 'vietnamese.png');
125
+ * FastQR::generate('こんにちは日本', 'japanese.png');
126
+ * ```
127
+ */
128
+ public static function generate(string $data, string $outputPath, array $options = []): bool
129
+ {
130
+ self::init();
131
+
132
+ if (empty($data)) {
133
+ throw new RuntimeException('Data cannot be empty');
134
+ }
135
+ if (empty($outputPath)) {
136
+ throw new RuntimeException('Output path cannot be empty');
137
+ }
138
+
139
+ // Create options struct
140
+ $opts = self::$ffi->new('QROptions');
141
+
142
+ // Size (preferred) or width/height (backward compatibility)
143
+ if (isset($options['size'])) {
144
+ $opts->size = $options['size'];
145
+ } elseif (isset($options['width']) || isset($options['height'])) {
146
+ $opts->size = $options['width'] ?? $options['height'] ?? 300;
147
+ } else {
148
+ $opts->size = 300;
149
+ }
150
+
151
+ // Optimize size
152
+ $opts->optimize_size = $options['optimizeSize'] ?? false ? 1 : 0;
153
+
154
+ // Foreground color
155
+ $fg = $options['foreground'] ?? [0, 0, 0];
156
+ $opts->foreground_r = $fg[0] ?? 0;
157
+ $opts->foreground_g = $fg[1] ?? 0;
158
+ $opts->foreground_b = $fg[2] ?? 0;
159
+
160
+ // Background color
161
+ $bg = $options['background'] ?? [255, 255, 255];
162
+ $opts->background_r = $bg[0] ?? 255;
163
+ $opts->background_g = $bg[1] ?? 255;
164
+ $opts->background_b = $bg[2] ?? 255;
165
+
166
+ // Error correction level
167
+ $ecLevel = $options['errorLevel'] ?? 'M';
168
+ $opts->ec_level = match($ecLevel) {
169
+ 'L' => 0,
170
+ 'M' => 1,
171
+ 'Q' => 2,
172
+ 'H' => 3,
173
+ default => 1
174
+ };
175
+
176
+ // Logo
177
+ $opts->logo_path = $options['logo'] ?? null;
178
+ $opts->logo_size_percent = $options['logoSize'] ?? 20;
179
+
180
+ // Format and quality
181
+ $opts->format = $options['format'] ?? 'png';
182
+ $opts->quality = $options['quality'] ?? 95;
183
+
184
+ // Call C function
185
+ $result = self::$ffi->fastqr_generate($data, $outputPath, FFI::addr($opts));
186
+
187
+ if (!$result) {
188
+ throw new RuntimeException('Failed to generate QR code');
189
+ }
190
+
191
+ return true;
192
+ }
193
+
194
+ /**
195
+ * Get library version
196
+ *
197
+ * @return string Version string
198
+ */
199
+ public static function version(): string
200
+ {
201
+ self::init();
202
+ return self::$ffi->fastqr_version();
203
+ }
204
+
205
+ /**
206
+ * Generate multiple QR codes in batch mode (7x faster!)
207
+ *
208
+ * @param array $dataArray Array of strings to encode
209
+ * @param string $outputDir Directory to save QR codes (will be created if it doesn't exist)
210
+ * @param array $options Generation options (same as generate)
211
+ * @return array Result with 'success' and 'failed' counts
212
+ *
213
+ * @throws RuntimeException if generation fails
214
+ *
215
+ * @example
216
+ * ```php
217
+ * use FastQR\FastQR;
218
+ *
219
+ * // Batch generation
220
+ * $data = ['QR 1', 'QR 2', 'QR 3'];
221
+ * FastQR::generateBatch($data, 'output_dir/', ['size' => 500]);
222
+ * // Creates: output_dir/1.png, output_dir/2.png, output_dir/3.png
223
+ * ```
224
+ */
225
+ public static function generateBatch(array $dataArray, string $outputDir, array $options = []): array
226
+ {
227
+ if (empty($dataArray)) {
228
+ throw new RuntimeException('Data array cannot be empty');
229
+ }
230
+ if (empty($outputDir)) {
231
+ throw new RuntimeException('Output directory cannot be empty');
232
+ }
233
+
234
+ // Create output directory
235
+ if (!is_dir($outputDir)) {
236
+ mkdir($outputDir, 0755, true);
237
+ }
238
+
239
+ // Create temporary batch file
240
+ $tempFile = tempnam(sys_get_temp_dir(), 'fastqr_batch_') . '.txt';
241
+ try {
242
+ file_put_contents($tempFile, implode("\n", $dataArray));
243
+
244
+ // Find CLI binary
245
+ $os = PHP_OS_FAMILY === 'Darwin' ? 'macos' : (PHP_OS_FAMILY === 'Linux' ? 'linux' : 'unknown');
246
+ $arch = php_uname('m');
247
+ if ($arch === 'x86_64' || $arch === 'amd64') {
248
+ $arch = 'x86_64';
249
+ } elseif ($arch === 'aarch64' || $arch === 'arm64') {
250
+ $arch = 'arm64';
251
+ }
252
+ $platform = "$os-$arch";
253
+
254
+ $cliPaths = [
255
+ __DIR__ . "/../../prebuilt/$platform/bin/fastqr",
256
+ '/usr/local/bin/fastqr',
257
+ __DIR__ . '/../../../build/fastqr',
258
+ ];
259
+
260
+ $cliPath = null;
261
+ foreach ($cliPaths as $path) {
262
+ if (file_exists($path)) {
263
+ $cliPath = $path;
264
+ break;
265
+ }
266
+ }
267
+
268
+ if ($cliPath === null) {
269
+ throw new RuntimeException("FastQR CLI not found for platform: $platform");
270
+ }
271
+
272
+ // Build command
273
+ $cmd = escapeshellarg($cliPath) . ' -F ' . escapeshellarg($tempFile) . ' ' . escapeshellarg($outputDir);
274
+
275
+ if (isset($options['size'])) {
276
+ $cmd .= ' -s ' . (int)$options['size'];
277
+ }
278
+ if (!empty($options['optimizeSize'])) {
279
+ $cmd .= ' -o';
280
+ }
281
+ if (isset($options['foreground'])) {
282
+ $fg = $options['foreground'];
283
+ $cmd .= ' -f ' . implode(',', $fg);
284
+ }
285
+ if (isset($options['background'])) {
286
+ $bg = $options['background'];
287
+ $cmd .= ' -b ' . implode(',', $bg);
288
+ }
289
+ if (isset($options['errorLevel'])) {
290
+ $cmd .= ' -e ' . escapeshellarg($options['errorLevel']);
291
+ }
292
+ if (isset($options['logo'])) {
293
+ $cmd .= ' -l ' . escapeshellarg($options['logo']);
294
+ }
295
+ if (isset($options['logoSize'])) {
296
+ $cmd .= ' -p ' . (int)$options['logoSize'];
297
+ }
298
+ if (isset($options['quality'])) {
299
+ $cmd .= ' -q ' . (int)$options['quality'];
300
+ }
301
+
302
+ exec($cmd . ' 2>&1', $output, $returnCode);
303
+
304
+ if ($returnCode !== 0) {
305
+ throw new RuntimeException('Batch generation failed: ' . implode("\n", $output));
306
+ }
307
+
308
+ return ['success' => count($dataArray), 'failed' => 0];
309
+ } finally {
310
+ if (file_exists($tempFile)) {
311
+ unlink($tempFile);
312
+ }
313
+ }
314
+ }
315
+ }
316
+
@@ -0,0 +1,97 @@
1
+ <?php
2
+
3
+ /**
4
+ * FastQR PHP Tests
5
+ */
6
+
7
+ namespace FastQR\Tests;
8
+
9
+ use FastQR\FastQR;
10
+ use PHPUnit\Framework\TestCase;
11
+
12
+ class FastQRTest extends TestCase
13
+ {
14
+ private string $outputDir;
15
+
16
+ protected function setUp(): void
17
+ {
18
+ $this->outputDir = __DIR__ . '/output';
19
+ if (!is_dir($this->outputDir)) {
20
+ mkdir($this->outputDir, 0755, true);
21
+ }
22
+ }
23
+
24
+ public function testVersion(): void
25
+ {
26
+ $version = FastQR::version();
27
+ $this->assertNotEmpty($version);
28
+ $this->assertMatchesRegularExpression('/^\d+\.\d+\.\d+$/', $version);
29
+ }
30
+
31
+ public function testBasicGeneration(): void
32
+ {
33
+ $output = $this->outputDir . '/basic.png';
34
+ $result = FastQR::generate('Hello, FastQR from PHP!', $output);
35
+
36
+ $this->assertTrue($result);
37
+ $this->assertFileExists($output);
38
+ }
39
+
40
+ public function testColoredQR(): void
41
+ {
42
+ $output = $this->outputDir . '/colored.png';
43
+ $result = FastQR::generate('Colored QR', $output, [
44
+ 'width' => 500,
45
+ 'height' => 500,
46
+ 'foreground' => [255, 0, 0],
47
+ 'background' => [255, 255, 200]
48
+ ]);
49
+
50
+ $this->assertTrue($result);
51
+ $this->assertFileExists($output);
52
+ }
53
+
54
+ public function testVietnameseText(): void
55
+ {
56
+ $output = $this->outputDir . '/vietnamese.png';
57
+ $result = FastQR::generate('Xin chào Việt Nam! 🇻🇳', $output);
58
+
59
+ $this->assertTrue($result);
60
+ $this->assertFileExists($output);
61
+ }
62
+
63
+ public function testJapaneseText(): void
64
+ {
65
+ $output = $this->outputDir . '/japanese.png';
66
+ $result = FastQR::generate('こんにちは日本', $output, [
67
+ 'foreground' => [0, 0, 255]
68
+ ]);
69
+
70
+ $this->assertTrue($result);
71
+ $this->assertFileExists($output);
72
+ }
73
+
74
+ public function testHighErrorCorrection(): void
75
+ {
76
+ $output = $this->outputDir . '/high_ec.png';
77
+ $result = FastQR::generate('High Error Correction', $output, [
78
+ 'errorLevel' => 'H'
79
+ ]);
80
+
81
+ $this->assertTrue($result);
82
+ $this->assertFileExists($output);
83
+ }
84
+
85
+ public function testEmptyDataThrowsException(): void
86
+ {
87
+ $this->expectException(\RuntimeException::class);
88
+ FastQR::generate('', 'output.png');
89
+ }
90
+
91
+ public function testEmptyOutputPathThrowsException(): void
92
+ {
93
+ $this->expectException(\RuntimeException::class);
94
+ FastQR::generate('Hello', '');
95
+ }
96
+ }
97
+
@@ -0,0 +1,29 @@
1
+ require 'mkmf'
2
+
3
+ # Check for required libraries
4
+ unless have_library('qrencode')
5
+ abort "ERROR: libqrencode is required. Install it first."
6
+ end
7
+
8
+ unless have_library('vips')
9
+ abort "ERROR: libvips is required. Install it first."
10
+ end
11
+
12
+ # Check for headers
13
+ unless have_header('qrencode.h')
14
+ abort "ERROR: qrencode.h not found"
15
+ end
16
+
17
+ unless have_header('vips/vips.h')
18
+ abort "ERROR: vips/vips.h not found"
19
+ end
20
+
21
+ # Add C++14 support
22
+ $CXXFLAGS << " -std=c++14"
23
+
24
+ # Set source directory
25
+ $srcs = ['fastqr_ruby.cpp', '../../src/fastqr.cpp']
26
+ $INCFLAGS << " -I$(srcdir)/../../include"
27
+
28
+ create_makefile('fastqr/fastqr')
29
+
@@ -0,0 +1,122 @@
1
+ /*
2
+ * FastQR Ruby Binding
3
+ * Copyright (C) 2025 FastQR Project
4
+ */
5
+
6
+ #include "fastqr.h"
7
+ #include <ruby.h>
8
+
9
+ static VALUE rb_mFastQR;
10
+ static VALUE rb_cQROptions;
11
+
12
+ // Convert Ruby hash to QROptions
13
+ static fastqr::QROptions hash_to_options(VALUE opts) {
14
+ fastqr::QROptions options;
15
+
16
+ if (NIL_P(opts)) {
17
+ return options;
18
+ }
19
+
20
+ VALUE val;
21
+
22
+ // Size (preferred) or width/height (backward compatibility)
23
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("size")));
24
+ if (!NIL_P(val)) {
25
+ options.size = NUM2INT(val);
26
+ } else {
27
+ // Backward compatibility: if width or height specified, use them
28
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("width")));
29
+ if (!NIL_P(val)) options.size = NUM2INT(val);
30
+
31
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("height")));
32
+ if (!NIL_P(val)) options.size = NUM2INT(val);
33
+ }
34
+
35
+ // Optimize size
36
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("optimize_size")));
37
+ if (!NIL_P(val)) options.optimize_size = RTEST(val);
38
+
39
+ // Foreground color
40
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("foreground")));
41
+ if (!NIL_P(val) && TYPE(val) == T_ARRAY) {
42
+ options.foreground.r = NUM2INT(rb_ary_entry(val, 0));
43
+ options.foreground.g = NUM2INT(rb_ary_entry(val, 1));
44
+ options.foreground.b = NUM2INT(rb_ary_entry(val, 2));
45
+ }
46
+
47
+ // Background color
48
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("background")));
49
+ if (!NIL_P(val) && TYPE(val) == T_ARRAY) {
50
+ options.background.r = NUM2INT(rb_ary_entry(val, 0));
51
+ options.background.g = NUM2INT(rb_ary_entry(val, 1));
52
+ options.background.b = NUM2INT(rb_ary_entry(val, 2));
53
+ }
54
+
55
+ // Error correction level
56
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("error_level")));
57
+ if (!NIL_P(val)) {
58
+ const char* level = StringValueCStr(val);
59
+ if (strcmp(level, "L") == 0) options.ec_level = fastqr::ErrorCorrectionLevel::LOW;
60
+ else if (strcmp(level, "M") == 0) options.ec_level = fastqr::ErrorCorrectionLevel::MEDIUM;
61
+ else if (strcmp(level, "Q") == 0) options.ec_level = fastqr::ErrorCorrectionLevel::QUARTILE;
62
+ else if (strcmp(level, "H") == 0) options.ec_level = fastqr::ErrorCorrectionLevel::HIGH;
63
+ }
64
+
65
+ // Logo path
66
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("logo")));
67
+ if (!NIL_P(val)) {
68
+ options.logo_path = StringValueCStr(val);
69
+ }
70
+
71
+ // Logo size
72
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("logo_size")));
73
+ if (!NIL_P(val)) {
74
+ options.logo_size_percent = NUM2INT(val);
75
+ }
76
+
77
+ // Quality
78
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("quality")));
79
+ if (!NIL_P(val)) {
80
+ options.quality = NUM2INT(val);
81
+ }
82
+
83
+ // Format
84
+ val = rb_hash_aref(opts, ID2SYM(rb_intern("format")));
85
+ if (!NIL_P(val)) {
86
+ options.format = StringValueCStr(val);
87
+ }
88
+
89
+ return options;
90
+ }
91
+
92
+ // FastQR.generate(data, output_path, options = {})
93
+ static VALUE rb_fastqr_generate(int argc, VALUE *argv, VALUE self) {
94
+ VALUE data, output_path, opts;
95
+ rb_scan_args(argc, argv, "21", &data, &output_path, &opts);
96
+
97
+ std::string data_str = StringValueCStr(data);
98
+ std::string output_str = StringValueCStr(output_path);
99
+ fastqr::QROptions options = hash_to_options(opts);
100
+
101
+ bool result = fastqr::generate(data_str, output_str, options);
102
+
103
+ return result ? Qtrue : Qfalse;
104
+ }
105
+
106
+ // FastQR.version
107
+ static VALUE rb_fastqr_version(VALUE self) {
108
+ return rb_str_new_cstr(fastqr::version());
109
+ }
110
+
111
+ extern "C" void Init_fastqr() {
112
+ rb_mFastQR = rb_define_module("FastQR");
113
+
114
+ rb_define_module_function(rb_mFastQR, "generate",
115
+ RUBY_METHOD_FUNC(rb_fastqr_generate), -1);
116
+ rb_define_module_function(rb_mFastQR, "version",
117
+ RUBY_METHOD_FUNC(rb_fastqr_version), 0);
118
+
119
+ // Constants
120
+ rb_define_const(rb_mFastQR, "VERSION", rb_str_new_cstr(fastqr::version()));
121
+ }
122
+
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FastQR
4
+ # Platform detection and pre-built binary management
5
+ module Platform
6
+ class << self
7
+ # Detects the current platform
8
+ # @return [String] Platform identifier (e.g., 'macos-arm64', 'linux-x86_64')
9
+ def detect
10
+ os = RbConfig::CONFIG['host_os']
11
+ arch = RbConfig::CONFIG['host_cpu']
12
+
13
+ case os
14
+ when /darwin/
15
+ case arch
16
+ when /arm64|aarch64/
17
+ 'macos-arm64'
18
+ when /x86_64|x64/
19
+ 'macos-x86_64'
20
+ else
21
+ raise "Unsupported macOS architecture: #{arch}"
22
+ end
23
+ when /linux/
24
+ case arch
25
+ when /x86_64|x64/
26
+ 'linux-x86_64'
27
+ when /arm64|aarch64/
28
+ 'linux-arm64'
29
+ else
30
+ raise "Unsupported Linux architecture: #{arch}"
31
+ end
32
+ else
33
+ raise "Unsupported platform: #{os}"
34
+ end
35
+ end
36
+
37
+ # Extracts pre-built binary from tarball
38
+ # @param tarball_path [String] Path to the tarball
39
+ # @param dest_dir [String] Destination directory
40
+ def extract_binary(tarball_path, dest_dir)
41
+ FileUtils.mkdir_p(dest_dir)
42
+ system("tar -xzf '#{tarball_path}' -C '#{dest_dir}'") or raise "Failed to extract #{tarball_path}"
43
+ end
44
+
45
+ # Finds the fastqr binary
46
+ # @return [String] Path to fastqr binary
47
+ def find_binary
48
+ platform = detect
49
+ prebuilt_dir = File.expand_path("../../prebuilt/#{platform}", __dir__)
50
+ binary_path = File.join(prebuilt_dir, 'fastqr')
51
+
52
+ return binary_path if File.exist?(binary_path)
53
+
54
+ # Try to extract from tarball
55
+ tarball_path = File.expand_path("../../prebuilt/#{platform}.tar.gz", __dir__)
56
+ if File.exist?(tarball_path)
57
+ puts "Extracting pre-built binary from #{tarball_path}..."
58
+ extract_binary(tarball_path, prebuilt_dir)
59
+
60
+ if File.exist?(binary_path)
61
+ File.chmod(0755, binary_path)
62
+ return binary_path
63
+ end
64
+ end
65
+
66
+ raise "Pre-built binary not found for #{platform}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FastQR
4
+ VERSION = "1.0.1"
5
+ end
6
+