"resources"-directory for ruby gem - ruby

I'm currently experimenting with creating my own gem in Ruby. The gem requires some static resources (say an icon in ICO format). Where do I put such resources within my gem directory tree and how to I access them from code?
Also, parts of my extension are native C code and I would like the C-parts to have access to the resources too.

You can put resources anywhere you want, except in the lib directory. Since it will be will be part of Ruby's load path, the only files that should be there are the ones that you want people to require.
For example, I usually store translated text in the i18n/ directory. For icons, I'd just put them in resources/icons/.
As for how to access these resources... I ran into this problem enough that I wrote a little gem just to avoid repetition.
Basically, I was doing this all the time:
def Your::Gem.root
# Current file is /home/you/code/your/lib/your/gem.rb
File.expand_path '../..', File.dirname(__FILE__)
end
Your::Gem.root
# => /home/you/code/your/
I wrapped this up into a nice DSL, added some additional convenience stuff and ended up with this:
class Your::Gem < Jewel::Gem
root '../..'
end
root = Your::Gem.root
# => /home/you/code/your/
# No more joins!
path = root.resources.icons 'your.ico'
# => /home/you/code/your/resources/icons/your.ico
As for accessing your resources in C, path is just a Pathname. You can pass it to a C function as a string, open the file and just do what you need to do. You can even return an object to the Ruby world:
VALUE your_ico_new(VALUE klass, VALUE path) {
char * ico_file = NULL;
struct your_ico * ico = NULL;
ico_file = StringValueCStr(path);
ico = your_ico_load_from_file(ico_file); /* Implement this */
return Data_Wrap_Struct(your_ico_class, your_ico_mark, your_ico_free, ico);
}
Now you can access it from Ruby:
ico = Your::Ico.new path

Related

How do I write data binary to gcs with ruby efficiently?

I want to upload data binary directly to GCP storage, without writing the file to disk. Below is the code snippet I have created to get to the state that I am going to be at.
require 'google/cloud/storage'
bucket_name = '-----'
data = File.open('image_block.jpg', 'rb') {|file| file.read }
storage = Google::Cloud::Storage.new("project_id": "maybe-i-will-tell-u")
bucket = storage.bucket bucket_name, skip_lookup: true
Now I want to directly put this data into a file on gcs, without having to write a file to disk.
Is there an efficient way we can do that?
I tried the following code
to_send = StringIO.new(data).read
bucket.create_file to_send, "image_inder_11111.jpg"
but this throws an error saying
/google/cloud/storage/bucket.rb:2898:in `file?': path name contains null byte (ArgumentError)
from /home/inder/.gem/gems/google-cloud-storage-1.36.1/lib/google/cloud/storage/bucket.rb:2898:in `ensure_io_or_file_exists!'
from /home/inder/.gem/gems/google-cloud-storage-1.36.1/lib/google/cloud/storage/bucket.rb:1566:in `create_file'
from champa.rb:14:in `<main>'
As suggested by #stefan, It should be to_send = StringIO.new(data), i.e. without .read (which would return a string again)

Puppet: How to require an additional resource in a custom type

I am writing a custom type for Puppet and use the following code to copy a module file specified by a puppet url to the user's home directory:
def generate
if self[:source]
uri = URI.parse(self[:source])
path = File.join(Etc.getpwnam(self[:user])[:dir], File.basename(uri.path))
file_opts = {}
file_opts[:name] = File.join(Etc.getpwnam(self[:user])[:dir], File.basename(uri.path))
file_opts[:ensure] = self[:ensure] == :absent ? :absent : :file
file_opts[:source] = self[:source]
file_opts[:owner] = self[:user]
self[:source] = path
Puppet::Type.type(:file).new(file_opts)
end
end
Things are working fine so far. The resource is added to the catalog and created on the agent side. But I have a problem...
How can I specify that this additional file resource must be created before the actual type gets executed? Unfortunatley, I cannot find an example which shows how to specify a dependency on an optional resource that is defined in a generate method.

Ruby GPGME - How to encrypt large files

I'm having difficulty to Encrypt large files (bigger than available memory) using GPGME in Ruby.
#!/usr/bin/ruby
require 'gpgme'
def gpgfile(localfile)
crypto = GPGME::Crypto.new
filebasename = File.basename(localfile)
filecripted = crypto.encrypt File.read(localfile), :recipients => "info#address.com", :always_trust => true
File.open("#{localfile}.gpg", 'w') { |file| file.write(filecripted) }
end
gpgpfile("/home/largefile.data")
In this case I got an error of memory allocation:
"read: failed to allocate memory (NoMemoryError)"
Someone can explain me how to read the source file chunk by chunk (of 100Mb for example) and write them passing by the crypting?
The most obvious problem is that you're reading the entire file into memory with File.read(localfile). The Crypto#encrypt method will take an IO object as its input, so instead of File.read(localfile) (which returns the contents of the file as a string) you can pass it a File object. Likewise, you can give an IO object as the :output option, letting you write the output directly to a file instead of in memory:
def gpgfile(localfile)
infile = File.open(localfile, 'r')
outfile = File.open("#{localfile}.gpg", 'w')
crypto = GPGME::Crypto.new
crypto.encrypt(infile, recipients: "info#address.com",
output: outfile,
always_trust: true)
ensure
infile.close
outfile.close
end
I've never used ruby-gpgme, so I'm not 100% sure this will solve your problem since it depends a bit on what ruby-gpgme does behind the scenes, but from the docs and the source I've peeked at it seems like a sanely-built gem so I'm guessing this will do the trick.

Convert long filename to short filename (8.3)

I want to convert long filenames/path to short filenames (8.3).
I'm developing a script that calls a command line tool that only accepts short filenames.
So i need to convert
C:\Ruby193\bin\test\New Text Document.txt
to
C:\Ruby193\bin\test\NEWTEX~1.TXT
So far i found How to get long filename from ARGV which uses WIN32API to convert short to long filenames (the opposite of what I want to achieve).
Is there any way to get the short filename in Ruby?
You can do this using FFI; there's actually an example that covers your exact scenario in their wiki under the heading "Convert a path to 8.3 style pathname":
require 'ffi'
module Win
extend FFI::Library
ffi_lib 'kernel32'
ffi_convention :stdcall
attach_function :path_to_8_3, :GetShortPathNameA, [:pointer, :pointer, :uint], :uint
end
out = FFI::MemoryPointer.new 256 # bytes
Win.path_to_8_3("c:\\program files", out, out.length)
p out.get_string # be careful, the path/file you convert to 8.3 must exist or this will be empty
This ruby code uses getShortPathName and don't need additional modules to be installed.
def get_short_win32_filename(long_name)
require 'win32api'
win_func = Win32API.new("kernel32","GetShortPathName","PPL"," L")
buf = 0.chr * 256
buf[0..long_name.length-1] = long_name
win_func.call(long_name, buf, buf.length)
return buf.split(0.chr).first
end
The windows function you require is GetShortPathName. You could use that in the same manner as described in your linked post.
EDIT: sample usage of GetShortPathName (just as a quick example) - shortname will contain "C:\LONGFO~1\LONGFI~1.TXT" and returned value is 24.
TCHAR* longname = "C:\\long folder name\\long file name.txt";
TCHAR* shortname = new TCHAR[256];
GetShortPathName(longname,shortname,256);

Is there an easy way of referring to the public directory in Sinatra?

Imagine this structure:
/project
/templates
view1.haml
view2.haml
misc_views/
view3.haml
view4.haml
even_deeper/
view5.haml
/public
script1.js
The depth of the templates can vary, and I would like to refer to the public directory if I want to include some files from it. Is there a helper or some other gimmick that takes me to the public directory? It seems bad to have something like ../../../public/script1.js , or ../../public/script1.js in my views. Surely there must be a better way.
You can use the settings.public configuration to refer to the public directory. For example:
get "/" do
js = File.join( settings.public, 'script1.js' )
File.exists?( js ) #=> true
end
As this is an absolute path, there is no need to make it relative to your view to get the file. You could reach out to this from your view, but I would personally set the path as an instance variable from the controller.
get "/" do
# If you really only need the directory
#public = settings.public
# If you actually need the path to a specific file
#main = File.join( settings.public, 'main.js' )
# If you actually need the contents of a file
#js = IO.read( File.join( settings.public, 'main.js' ) )
end
You must include the static resources by the root of the web address or a relative request path, as it will be the browser who requests it, not your server-side code. Unless I am mistaken in your case?
<script type="text/javascript" src="/script1.js"></script>

Resources