External Configuration for Standalone Ruby Script - ruby

I have a standalone ruby script that is intended to be run from a commandline.
Currently I have a section to define stuff like paths to files and such.
key_path = "C:\\OpenSSL-Win64\\keys\\"
consumer_file = "henrb028.consumer"
key_file = "henrb028.key"
I'd love to pull all of that out of the .rb file and into some external config file. Ideally it would be as lightweight as possible. For example, just copy-paste that into another ruby file and then cram it in at runtime.
I've tried both require and include and gotten varying errors. Without resorting to something like YAML, what's an easy and robust way to do this?

There are a few standard ways to realize what you describe.
Read from environmental variables
Each of those environmental stores a particular parameter. Then users can control, if they want to change some or all of the parameters.
Pros: Standard and handy way for both developers and users, providing the parameters are seldom changed. The default parameters can be easily shared in the same environment (like the same account or even platform-wide), as well as with other programs.
Cons: Somewhat obscured. For example, if a user's work with the tool is logged in a file, other people (or even the user her/himself later) cannot tell what the exact set of the parameters used was from the logfile. Or, if a user wants to change a parameter frequently, it is awkward.
An example:
key_file = ENV['KEY_FILE'] || "henrb028.key"
Read from a configuration file
Then either or both of each user and the system administrator can control and change it, when needed. How to determine the filename of the configuration file varies.
Pros: Suitable to store a large set of parameters.
Cons: Somewhat obscured. For example, if a user's work with the tool is logged in a file, other people (or even the user her/himself later) cannot tell what the exact set of the parameters used was. If a user wants to change a parameter frequently, it is very awkward.
A (crude!) example:
Suppose /etc/OUR_CONFIG.txt is as follows,
CONSUMER_FILE: henrb028.consumer
KEY_FILE: henrb028.key
Then, read them like,
opts = { 'KEY_FILE' => 'default.key' }
IO.readlines("/etc/OUR_CONFIG.txt").each do |ec|
a = ec.strip.split(/\s*:\s*/)
opts[a[0]] = a[1]
end
p opts
Specify with command-line options
If some (or all) options are not specified at the run time, it should fall back to the default value. OptionParser (as #tadaman suggested in his/her comment) is a powerful and versatile library to handle command-line options.
Pros: Standard and handy way for users, especially for a quick change at the run time. If you see the log file, you know what exactly the user did.
Cons: The default parameters cannot be shared with other tools on its own. To circumvent the problem, there are a few ways to make a fall-back routine:
Simply hard-code the default value in the main code.
Store the default value in an environmental variable.
Store the default value in a configuration file. The developer must decide how to determine the filename of the configuration file (hard-coding, environmental variable, etc).
A crude example (without using OptionParser):
opts = { 'key_file' => 'default.key' }
%w(consumer_file key_file).each do |ec|
cands = ARGV.grep(/^--#{Regexp.quote ec}=.+/)
opts[ec] = cands[0].split('=')[1] unless cands.empty?
end
p opts
I think these are the most typical ways, though there are other ways!

Related

Ruby: how to access group info from /var/db/group.db

RHEL/CentOS 8.x, Ruby 2.5.5, irb 0.9.6
We have a specialized set up that keeps user and group information in /usr/local/etc/[user|group] and a custom Makefile to add that information to /var/db/[group|passwd].db accordingly.
I am trying to get the list of groups a user belongs to, in Ruby, after login.
I'm relatively new to the language, and have just read the documentation for the Etc module but that seems to exclusively work with the /etc/ filesystem. Not outrageous tbh.
Is there an easy way to access the Berkley DB info in /var/db or am I going to have to iterate through the /usr/local/etc/group file to get this information?
I suspect that the documentation of that module is heavily simplified, heavily outdated, or both. I am almost 100% sure that Etc will use the OS-provided standard library functions instead of going off and parsing system files by itself. (Why would the Ruby developers write parsers for the configuration files of every single OS they support, instead of just calling a POSIX function?)
You can confirm this suspicion using strace.
If you look at how the Ruby Etc module is structured, it maps 1:1 to the POSIX functions:
Etc::gegtrent: gets the next group entry.
Etc::endgtrent: stops iterating the groups.
Etc::settrent: resets the iteration.
Here are the POSIX functions for comparison:
endgrent, getgrent, setgrent – group database entry functions
In my case, I am testing this on macOS (which already has specialized user handling), but furthermore, I tested it on my company laptop, which is macOS joined to a Microsoft ActiveDirectory Domain. My user account and its group aren't even mentioned in either /etc/passwd or /etc/group, and yet, I can easily read them with Etc. I am very sure that the Ruby developers did not implement ActiveDirector just to accommodate my personal weird use case, so it must use system functions.
So, if you have e.g. a uid and want to know what groups it belongs to, you need to first get its name, and then search the groups. Unfortunately, it looks like the provided iterator wrappers do not support the common idiom that not supplying the block returns an Enumerator, so you have to create one yourself.
require 'etc'
uid = 0 # for example
username = Etc.getpwuid(uid).name
groups = Etc.enum_for(:group).select {|group| group.mem.include?(username) }
p groups

Providing a meaningful default directory name in GIMP Scheme (script-Fu)

I'm a total newbie to Scheme (about 1 week).
I'm registering a script for which the second parameter is an output directory name - via SF-DIRNAME. How do I supply a meaningful default to the front-end widget that does not use host platform-specific names? Ideally, I'd like it to be '/Users/[username]' - or if possible - the Scheme equivalent of ${PWD}. As an illustration, when you create a new image and hit 'Save' for the first time, the default directory there is '/Users/[username]/Documents' - so it must be possible. How does the widget know what your user home directory is? How can this be referred to in the default field of the registration statement? Finally, it would be really nice if Gimp could 'remember' which output directory was selected last time (within the scope of the lifetime of the Gimp instance) and offer that up as the default on the second and subsequent invocations of the script. I've scoured hundreds of other people's scripts, the Gimp community pages and the Scheme documentation and I've found, literally, nothing on this requirement. Thanks in advance. VV
Gimp uses the GTKFileChooser widget, and there is nothing you can do in your script to make it different from the other instances of GTKFileChooser used in Gimp.
But what you supply as a default name can be a variable, it doesn't need to be a static string, and it can be set by any means available in to your Scheme interpreter at the time of registration (looking for the HOME environment variable, for instance).
Btw, if you are new to this, write your scripts in Python, it is both easier and more powerful.

Programmatically test whether an arbitrary user has access to an arbitrary file

I'd like to be able to write tests or specs that state that user soandso can read or edit file such_and_such. Although I would ultimately like this to be in RSpec, I'm assuming it will either all boil down to Bash commands (neatly wrapped in Ruby methods, of course), since it doesn't seem to be available in Ruby's File class, or anything else I've looked at. I've been hunting around for something in Bash to do this, and can't find anything (and nothing in the File class looks useful either). (Note: File.owned? does not do what I want, as it only tests if the process running the Ruby code is the owner, not if any arbitrary user has rights to edit or read, so it's significantly different on two counts.)
Is there any way to do this, built into Bash? (Or Ruby, or some Ruby gem, that I missed?) Or do I need to build a system myself, by getting owner and group info for the file, as well as read, write, and execute bits for each, and then looking up members of the group (in case so_and_so is not the owner), and seeing if the desired permissions are available to soandso either through ownership or group membership?
I'm currently only concerned about doing this on Unix-like systems, although something not dependent on a Unix shell that would also run on Windows would be a nice bonus.
Also, I'm interested in testing actual files, so something like FakeFS is not (as far as I can see) useful to me. I'm not trying to test how my Ruby code will interact with a file system, I'm trying to verify that all the necessary operations can be performed on actual files and directories. (Again,) I want to be able to specify that soandso can edit (or read) file such_and_such, NOT specify that file such_and_such is owned by soandso. The whole point of this is not specifying ownership (since that's an implementation detail and may need to change to accommodate other requirements, such as security) but only specify what my application and users actually need to be able to do to/in the file system.
If the user running your Ruby script has the necessary permissions, you could try running a test with the specific user with sudo.
sudo -u soandso test -r such_and_such
-r tests readability, -w writability. The return code is 0 if the test is passed.
By the way, I think that since the feature you are looking for could only be available for the super-user (since figuring out whether someone else has enough permissions would need you to have read permissions at least), it makes sense that it is not readily available in Ruby File class.
I haven't tried it, but I wonder if the FileTest methods would work as they say they operate on the "effective user id".
File.readable?(file_name) → true or false Link
Returns true if the named file is readable by the effective user id of this process.
File.writable?(file_name) → true or false Link
Returns true if the named file is writable by the effective user id of this process.
That in conjunction with seteuid might do the trick.
Process::Sys.seteuid(integer) → nil Link
Set the effective user ID of the calling process to integer. Not available on all platforms.
There is also setegid for group stuff...
Parse Permissions with File::Stat and Etc Modules
While this isn't a pre-packaged solution, you should be able to inspect a given file where you have sufficient access for stat in order to get mode, uid, and gid information. You can then parse /etc/passwd and /etc/group in order to find out whether a given user would have sufficient permissions.
Some building blocks include:
# Get the file's octal mode.
File.stat('/etc/passwd').mode.to_s(8)
#=> "100644"
# Get the group ID assigned to the file.
File.stat('/etc/passwd').gid
#=> 0
# Get the username associated with the given GID.
Etc.getpwuid(0).name
#=> "root"
You could also use the id utility to get information about a given user. For example:
%x{id root}
#=> "uid=0(root) gid=0(root) groups=0(root)\n"
but I think it would be easier to use the standard libraries rather than parse the output from id. YMMV.

General Purpose Filter As You Type (aka typeahead, Incremental find, autocomplete) is it out there?

Background
Lately I've become a fanatic that everything I type while working on a computer should be compatible with "DRY". If there's anything I have to type more than once in any context, I want some kind of user-aware auto-complete option to do some of the work for me -- always -- no exceptions.
Having to work under Windows, I've looked at GUI solutions to make this insane goal a reality.
The (almost) optimal solution
If you have a moment, open up Firefox 3.0 and type a few keystrokes into the address bar. You will notice that it performs a kind of Incremental Autocomplete based on space-separated sub-strings of whatever you type. Another place in Firefox that does something similar is the about:config URL.
This is sub-optimal, because I don't want this in Firefox only. I want to use this everywhere.
The Question
Does anyone out there know of a widget or app that does nothing but insanely good incremental auto-complete that can be used as a general purpose "run everywhere" tool? Something that allows the user to: 1) maintain one or more "completion candidate files"; 2) pick one of those files as the source for Firefox 3.0 style completion; 3) return the result (or blank if the user canceled), and do those three things only?
Details
Here's how it should work:
STEP1: user saves or more csv file(s) (or other easy-edit format) somewhere in his hard-drive
STEP2: user creates a Windows Script Host script or a batch file (or whatever) instantiates the FilterAsYouType GUI
STEP3: user runs the script file, and the script file instantiates the GUI, telling it which CSV file to use as the source of all potential completions
STEP4: the user either chooses one of the completions, supplies his own text that is not in the list, or cancels out without supplying anything
STEP5: when the user is done the script saves the result to a variable and does something with it
Here is some pseudo-code for the script:
include "GenericTypeaheadWidget";
var gengui = new GenericTypaheadWidget('c:\docs\favorite_foods.csv');
var fave_food = gengui.get_user_input();
if(fave_food != ''){
alert('you chose '+fave_food+'!');
}
The rationale
The goal is to just have a way to always be able to do auto-completions from a list of arbitrary items, even if the list is a couple thousand items, and not have to rely on it being built into some IDE or standalone application that only accepts certain kinds of input or has an overly-complicated API relative to the simplicity of this task.
CSV (or text or sqlite database) would provide a way for me to self-generate "candidate lists" or "history logs" and then just use those logs as the source of the possible completions.
The disclaimer
I've tried several GUI "launcher" programs, command-line engines like power-shell and scripting shells, the regular plain old command-line history with varying degrees of satisfaction. The problem with these is they all do extra superfluous stuff like searching directories or built-in commands. I just want nothing but whatever is in the CSV file I happen to be pointing at.
I'm wondering if there is any simple tool that does nothing but what I'm describing above.
UPDATE: It looks like this question is very closely related to Graphical Command Shell, which captures the essential idea presented here.
You should really try Launchy - it's exactly what you're looking for, a "run anything" with intelligent autocompletion. It completely changes the way you interact with a Windows PC.
And it has open source-code, so you can borrow its autocompletion code if you want to roll your own interface.

What are the best practices for building multi-lingual applications on win32?

I have to build a GUI application on Windows Mobile, and would like it to be able user to choose the language she wants, or application to choose the language automatically. I consider using multiple dlls containing just required resources.
1) What is the preferred (default?) way to get the application choose the proper resource language automatically, without user intervention? Any samples?
2) What are my options to allow user / application control what language should it display?
3) If possible, how do I create a dll that would contain multiple language resources and then dynamically choose the language?
For #1, you can use the GetSystemDefaultLangID function to get the language identifier for the machine.
For #2, you could list languages you support and when the user selects one, write the selection into a text file or registry (is there a registry on Windows Mobile?). On startup, use the function in #1 only if there is no selection in the file or registry.
For #3, the way we do it is to have one resource DLL per language, each of which contains the same resource IDs. Once you figure out the language, load the DLL for that language and the rest just works.
Re 1: The previous GetSystemDefuaultLangID suggestion is a good one.
Re 2: You can ask as a first step in your installation. Or you can package different installers for each language.
Re 3:
In theory the DLL method mentioned above sounds great, however in practice it didn't work very well at all for me personally.
A better method is to surround all of the strings in your program with either: Localize or NoLocalize.
MessageBox(Localize("Hello"), Localize("Title"), MB_OK);
RegOpenKey(NoLocalize("\\SOFTWARE\\RegKey"), ...);
Localize is just a function that converts your english text to a the selected language. NoLocalize does nothing.
You want to surround your strings with these values though because you can build a couple of useful scripts in your scripting language of choice.
1) A script that searches for all the Localize(" prefixes and outputs a .ini file with english=otherlangauge name value pairs. If the output .ini file already contains a mapping you don't add it again. You never re-create the ini file completely, your script just adds the missing ones each time you run your script.
2) A script that searches all the strings and makes sure they are surrounded by either Localize(" or NoLocalize(". If not it tells you which strings you still need to localize.
The reason #2 is important is because you need to make sure all of your strings are actually consciously marked as needing localization or not. Otherwise it is absolutely impossible to make sure you have proper localization.
The reason for #1 instead of loading from a DLL is because it takes no work to maintain this solution and you can add new strings that need to be translated on the fly.
You ship the ini files that are output with your program. You also give these ini files to your translators so they can convert the english=otherlanguage pairs. When they send it back to you, you simply replace your checked in .ini file with the one given by your translator. Running your script as mentioned in #1 will re-add any missing translations if any were done while the translator was translating.

Resources