mmapper 1.0.0 → 2.0.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 (6) hide show
  1. checksums.yaml +4 -4
  2. data/ext/extconf.rb +1 -27
  3. data/ext/mmapper.c +98 -0
  4. data/lib/mmapper.rb +47 -31
  5. metadata +7 -21
  6. data/ext/mmap.go +0 -127
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 624fb225f474ed8da83938adf19dd4e1a3727c8c4c0660d316f6896f620ca5d1
4
- data.tar.gz: 97607d8736e460e4f788efde73cd35652b5abe850a8dfcc0accfaf68ab29ede1
3
+ metadata.gz: 6bd38094ddc8cb154aea8cded45f5775c2b4bc873dbf037c805230635332ee23
4
+ data.tar.gz: c19ed16231032e3e11e82723aed9fca20c8d0d51b2a0c5054ec4b8ddf88ceffe
5
5
  SHA512:
6
- metadata.gz: a91ef5d57628aa47c30b5b0dd4957d41373440b7691792baa93647e57b279bcb2d466ce63f24f43285b6f93e0c711fcac882e6b54e2ebf8ddd5c043a8604a3d7
7
- data.tar.gz: a5e0e6825d701945192923dadabbe87984a04aa2a3e74031a4e5d183abfd0f43619e531e585422416fea6711e4535e562be813b657f1fc0017fd82a0808a6726
6
+ metadata.gz: 6d485802f03e104bbfaccd82c2b7e7efa0ee332dd95f41962b1a41558f4fcbe5484c8c0c50ff583d84e1e92a535ae1f51740c53bd0207b98645153b60ad8f094
7
+ data.tar.gz: e520f6a7f6ca6f718fa02093563a7752f5afd2e15525f63fbfbd2d4fb065ced6539c133e25ab6887aac5863169118eb8fa6e9a98bc66d174dc728c28cd712286
data/ext/extconf.rb CHANGED
@@ -1,29 +1,3 @@
1
1
  require 'mkmf'
2
- require 'fileutils'
3
2
 
4
- LIB_DIR = File.expand_path(File.dirname(__FILE__))
5
-
6
- # Detect OS and set correct library name
7
- LIB_NAME =
8
- case RUBY_PLATFORM
9
- when /darwin/ then "libmmapper.dylib"
10
- when /mingw|mswin/ then "mmapper.dll"
11
- else "libmmapper.so"
12
- end
13
-
14
- LIB_PATH = File.join(LIB_DIR, LIB_NAME)
15
-
16
- # Ensure Go module is initialized
17
- Dir.chdir(LIB_DIR) do
18
- unless File.exist?("go.mod")
19
- puts "Initializing Go module..."
20
- system("go mod init mmapper") || raise("Failed to initialize Go module")
21
- end
22
-
23
- puts "Building Go shared library for #{RUBY_PLATFORM}..."
24
- unless system("go build -o #{LIB_NAME} -buildmode=c-shared .")
25
- raise "Go build failed!"
26
- end
27
- end
28
-
29
- create_makefile('mmapper')
3
+ create_makefile('mmapper/mmapper')
data/ext/mmapper.c ADDED
@@ -0,0 +1,98 @@
1
+ #include "ruby.h"
2
+ #include <fcntl.h>
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+ #include <sys/mman.h>
7
+ #include <unistd.h>
8
+
9
+ typedef struct {
10
+ int fd;
11
+ size_t size;
12
+ void *map;
13
+ } mmap_file;
14
+
15
+ static VALUE cMmapFile;
16
+
17
+ static void mmap_file_free(void *ptr) {
18
+ mmap_file *m = (mmap_file *)ptr;
19
+ if (m->map)
20
+ munmap(m->map, m->size);
21
+ if (m->fd >= 0)
22
+ close(m->fd);
23
+ free(m);
24
+ }
25
+
26
+ static VALUE mmap_file_alloc(VALUE klass) {
27
+ mmap_file *m = ALLOC(mmap_file);
28
+ m->fd = -1;
29
+ m->map = NULL;
30
+ m->size = 0;
31
+ return Data_Wrap_Struct(klass, NULL, mmap_file_free, m);
32
+ }
33
+
34
+ static VALUE mmap_file_initialize(VALUE self, VALUE path) {
35
+ mmap_file *m;
36
+ const char *c_path = StringValueCStr(path);
37
+ Data_Get_Struct(self, mmap_file, m);
38
+
39
+ m->fd = open(c_path, O_RDWR);
40
+ if (m->fd < 0)
41
+ rb_sys_fail("open");
42
+
43
+ m->size = lseek(m->fd, 0, SEEK_END);
44
+ lseek(m->fd, 0, SEEK_SET);
45
+
46
+ m->map = mmap(NULL, m->size, PROT_READ | PROT_WRITE, MAP_SHARED, m->fd, 0);
47
+ if (m->map == MAP_FAILED)
48
+ rb_sys_fail("mmap");
49
+
50
+ return self;
51
+ }
52
+
53
+ static VALUE mmap_file_read(VALUE self, VALUE offset_val, VALUE length_val) {
54
+ mmap_file *m;
55
+ Data_Get_Struct(self, mmap_file, m);
56
+
57
+ long offset = NUM2LONG(offset_val);
58
+ long length = NUM2LONG(length_val);
59
+
60
+ if (offset < 0 || offset + length > (long)m->size)
61
+ rb_raise(rb_eArgError, "read out of bounds");
62
+
63
+ return rb_str_new((char *)m->map + offset, length);
64
+ }
65
+
66
+ static VALUE mmap_file_write(VALUE self, VALUE offset_val, VALUE str_val) {
67
+ mmap_file *m;
68
+ Data_Get_Struct(self, mmap_file, m);
69
+
70
+ long offset = NUM2LONG(offset_val);
71
+ StringValue(str_val);
72
+ long len = RSTRING_LEN(str_val);
73
+
74
+ if (offset < 0 || offset + len > (long)m->size)
75
+ rb_raise(rb_eArgError, "write out of bounds");
76
+
77
+ memcpy((char *)m->map + offset, RSTRING_PTR(str_val), len);
78
+ msync(m->map, m->size, MS_SYNC);
79
+
80
+ return Qtrue;
81
+ }
82
+
83
+ static VALUE mmap_file_size(VALUE self) {
84
+ mmap_file *m;
85
+ Data_Get_Struct(self, mmap_file, m);
86
+ return LONG2NUM(m->size);
87
+ }
88
+
89
+ void Init_mmapper(void) {
90
+ VALUE mMmapper = rb_define_module("Mmapper");
91
+ cMmapFile = rb_define_class_under(mMmapper, "File", rb_cObject);
92
+
93
+ rb_define_alloc_func(cMmapFile, mmap_file_alloc);
94
+ rb_define_method(cMmapFile, "initialize", mmap_file_initialize, 1);
95
+ rb_define_method(cMmapFile, "read", mmap_file_read, 2);
96
+ rb_define_method(cMmapFile, "write", mmap_file_write, 2);
97
+ rb_define_method(cMmapFile, "size", mmap_file_size, 0);
98
+ }
data/lib/mmapper.rb CHANGED
@@ -1,45 +1,61 @@
1
- require 'ffi'
1
+ require 'mmapper/mmapper'
2
2
 
3
3
  module Mmapper
4
- extend FFI::Library
5
-
6
- LIB_DIR = File.expand_path("../../ext", __FILE__)
4
+ class File
5
+ def find_matching_line(prefix)
6
+ low = 0
7
+ high = size
7
8
 
8
- LIB_NAME =
9
- case RUBY_PLATFORM
10
- when /darwin/ then "libmmapper.dylib"
11
- when /mingw|mswin/ then "mmapper.dll"
12
- else "libmmapper.so"
13
- end
9
+ while low < high
10
+ mid = (low + high) / 2
11
+ line_start = find_line_start(mid)
12
+ line = read_line_at(line_start)
14
13
 
15
- LIB_PATH = File.join(LIB_DIR, LIB_NAME)
14
+ return nil if line.nil?
16
15
 
17
- unless File.exist?(LIB_PATH)
18
- Dir.chdir(LIB_DIR) do
19
- puts "Compiling Go shared library for #{RUBY_PLATFORM}..."
20
- system("go build -o #{LIB_NAME} -buildmode=c-shared .") || raise("Go build failed")
21
- end
22
- end
16
+ if line < prefix
17
+ low = mid + 1
18
+ else
19
+ high = mid
20
+ end
23
21
 
24
- ffi_lib LIB_PATH
22
+ end
25
23
 
26
- attach_function :create_mmapper, :CreateMmapper, [:string], :int
27
- attach_function :find_matching_line, :FindMatchingLine, [:int, :string], :string
24
+ final_line_start = find_line_start(low)
25
+ line = read_line_at(final_line_start)
28
26
 
29
- class Instance
30
- def initialize(filename)
31
- @mmapper_id = Mmapper.create_mmapper(filename)
32
- raise "Failed to load file: #{filename}" if @mmapper_id < 0
27
+ if line&.start_with?(prefix)
28
+ line
29
+ else
30
+ nil
31
+ end
33
32
  end
34
33
 
35
- def find_matching_line(prefix)
36
- result = Mmapper.find_matching_line(@mmapper_id, prefix)
37
- return nil if result.nil? || result.empty?
38
- result
34
+ private
35
+
36
+ def find_line_start(pos)
37
+ pos = [pos, size - 1].min
38
+ pos -= 1 while pos > 0 && read(pos, 1) != "\n"
39
+ pos += 1 if pos != 0
40
+ pos
39
41
  end
40
- end
41
42
 
42
- def self.load_file(filename)
43
- Instance.new(filename)
43
+ def read_line_at(pos)
44
+ buf = +''
45
+ chunk_size = 64
46
+ while pos < size
47
+ safe_len = [size - pos, chunk_size].min
48
+ chunk = read(pos, safe_len)
49
+ newline_idx = chunk.index("\n")
50
+ if newline_idx
51
+ buf << chunk[0..newline_idx]
52
+ break
53
+ else
54
+ buf << chunk
55
+ pos += chunk_size
56
+ end
57
+ end
58
+ buf.empty? ? nil : buf.chomp
59
+ end
44
60
  end
45
61
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mmapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Dawson
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-31 00:00:00.000000000 Z
11
- dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: ffi
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '1.15'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '1.15'
26
- description: Wraps a Go extension for mmap-ing files.
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Wraps a C extension for mmap-ing files.
27
13
  email:
28
14
  - email@carldaws.com
29
15
  executables: []
@@ -33,7 +19,7 @@ extra_rdoc_files: []
33
19
  files:
34
20
  - README.md
35
21
  - ext/extconf.rb
36
- - ext/mmap.go
22
+ - ext/mmapper.c
37
23
  - lib/mmapper.rb
38
24
  homepage: https://github.com/carldaws/mmapper
39
25
  licenses:
@@ -53,7 +39,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
41
  requirements: []
56
- rubygems_version: 3.6.2
42
+ rubygems_version: 3.6.7
57
43
  specification_version: 4
58
- summary: Mmap-ed files using Go and FFI.
44
+ summary: Mmap-ed files in Ruby using a C native extension
59
45
  test_files: []
data/ext/mmap.go DELETED
@@ -1,127 +0,0 @@
1
- package main
2
-
3
- /*
4
- #include <stdlib.h>
5
- */
6
- import "C"
7
-
8
- import (
9
- "bytes"
10
- "os"
11
- "sync"
12
- "syscall"
13
- )
14
-
15
- // Mmapper holds the mmap data for a file
16
- type Mmapper struct {
17
- mmapData []byte
18
- fileSize int
19
- }
20
-
21
- // Store instances
22
- var (
23
- mu sync.Mutex
24
- mmappers = make(map[int]*Mmapper)
25
- nextID = 1
26
- )
27
-
28
- // mmapFile maps a file into memory.
29
- func mmapFile(filename string) (*Mmapper, error) {
30
- file, err := os.Open(filename)
31
- if err != nil {
32
- return nil, err
33
- }
34
- defer file.Close()
35
-
36
- fi, err := file.Stat()
37
- if err != nil {
38
- return nil, err
39
- }
40
-
41
- size := fi.Size()
42
- if size == 0 {
43
- return nil, os.ErrInvalid
44
- }
45
-
46
- data, err := syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
47
- if err != nil {
48
- return nil, err
49
- }
50
-
51
- return &Mmapper{mmapData: data, fileSize: int(size)}, nil
52
- }
53
-
54
- // findLineStart moves backward to find the start of a line.
55
- func findLineStart(data []byte, pos int) int {
56
- for pos > 0 && data[pos-1] != '\n' {
57
- pos--
58
- }
59
- return pos
60
- }
61
-
62
- // readLine reads a full line starting from a given position.
63
- func readLine(data []byte, start int, fileSize int) string {
64
- end := start
65
- for end < fileSize && data[end] != '\n' {
66
- end++
67
- }
68
- return string(data[start:end])
69
- }
70
-
71
- // binarySearchPrefix performs a binary search for a prefix.
72
- func binarySearchPrefix(m *Mmapper, prefix string) string {
73
- low, high := 0, m.fileSize-1
74
- var match string
75
-
76
- for low <= high {
77
- mid := (low + high) / 2
78
- mid = findLineStart(m.mmapData, mid)
79
-
80
- line := readLine(m.mmapData, mid, m.fileSize)
81
- if bytes.HasPrefix([]byte(line), []byte(prefix)) {
82
- match = line
83
- high = mid - 1
84
- } else if line < prefix {
85
- low = mid + len(line) + 1
86
- } else {
87
- high = mid - 1
88
- }
89
- }
90
- return match
91
- }
92
-
93
- //export CreateMmapper
94
- func CreateMmapper(filename *C.char) C.int {
95
- m, err := mmapFile(C.GoString(filename))
96
- if err != nil {
97
- return -1 // Error case
98
- }
99
-
100
- mu.Lock()
101
- id := nextID
102
- nextID++
103
- mmappers[id] = m
104
- mu.Unlock()
105
-
106
- return C.int(id)
107
- }
108
-
109
- //export FindMatchingLine
110
- func FindMatchingLine(mmapperID C.int, prefix *C.char) *C.char {
111
- mu.Lock()
112
- m, exists := mmappers[int(mmapperID)]
113
- mu.Unlock()
114
-
115
- if !exists {
116
- return nil
117
- }
118
-
119
- match := binarySearchPrefix(m, C.GoString(prefix))
120
- if match == "" {
121
- return nil
122
- }
123
-
124
- return C.CString(match)
125
- }
126
-
127
- func main() {}