I can't find anything in the official documentation of the dnsruby gem, so I'll ask here: Is there any chance to get a parsed version of the dnsruby outputs, especially for A-Record?
When I'm performing:
def find_domain
self.domain_name = Reversed.lookup(self.ip_address)
res = Resolver.new
a_recs = res.query(self.domain_name) # Defaults to A record
end
the output of a_recs is a long string, e.g.:
;; Answer received from 192.168.178.1 (75 bytes) ;; ;; Security Level : UNCHECKED ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59802 ;; flags: qr rd ra cd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 OPT pseudo-record : payloadsize 512, xrcode 0, version 0, flags 32768 ;; QUESTION SECTION (1 record) ;; google-public-dns-b.google.com. IN A ;; ANSWER SECTION (1 record) google-public-dns-b.google.com. 86399 IN A 8.8.4.4
I just need the IP-Address (in this case: 8.8.4.4) itself and not the whole answer to my query. Is there a solution? I want to avoid doing that the "dirty" way.. Thanks in advance!
I'm seperating the whole string into single words with .split and save the values into an array. Then I'm going to loop through them and pick the last value. Something like this (note tested):
a_recs = res.query(self.domain_name).split
a_recs.each do |ip|
a_recs = ip
end
When iterating through the array the last values are getting overwritten so the last one (in my case the IP-address 8.8.4.4) should be saved to a_recs.
Related
My goal is to write a string to a file where the size of the string will vary. At the moment I have made the string very large so that there is no overflow but is there a way to make it so that the size of the string is the exact number of characters I'm placing into it? I've tried something like the code below but it gives me an error unknown identifier "address count" I think it is because address count is a variable declared in a process and address count is constantly changing. Is there any way around this?
signal address_map :string (1 to address_count);
many thanks
leo
"My goal is to write a string to a file." Hence, lets just focus on that.
Step 1: reference the file IO packages (recommended to turn on VHDL-2008):
use std.textio.all ;
-- use ieee.std_logic_textio.all ; -- include if not using VHDL-2008
Step 2: Declare your file
file MyFile : TEXT open WRITE_MODE is "MyFile.txt";
Step 3: Create a buffer:
TestProc : process
variable WriteBuf : line ;
begin
write ... -- see step 4
writeline ... -- see step 5
Step 4: Use write to write into the buffer (in the process TestProc):
write(WriteBuf, string'("State = ") ) ; -- Any VHDL version
write(WriteBuf, StateType'image(State)) ;
swrite(WriteBuf, " at time = " ); -- VHDL-2008 simplification
write(WriteBuf, NOW, RIGHT, 12) ;
Step 5: Write the buffer to the file (in the process TestProc):
writeline(MyFile, WriteBuf) ;
Alternate Steps 3-5: Use built-in VHDL Write with to_string:
Write(MyFile, "State = " & to_string(State) &
", Data = " & to_hstring(Data) &
" at time " & to_string(NOW, 1 ns) ) ;
Alternate Steps 1-5: Use OSVVM (see http://osvvm.org) (requires VHDL-2008):
library osvvm ;
use osvvm.transcriptpkg.all ; -- all printing goes to same file
. . .
TestProc : process
begin
TranscriptOpen("./results/test1.txt") ;
Print("State = " & to_string(State) &
", Data = " & to_hstring(Data) &
" at time " & to_string(NOW, 1 ns) ) ;
One hard but flexible solution is to use dynamic allocation features of VHDL (copied from ADA).
You have to use an access of string (it is roughly like a "pointer to a string" in C)
type line is access string;
you event don't have to do it because line is already declared in std.textio package.
Ok, the problem next is that you can't use an access type for a signal, so you have to use a shared variable:
shared variable address_map: line;
And finally you have to allocate, read and write to this line:
--Example in a function/procedure/process:
--free a previously allocated string:
if address_map /= NULL then
deallocate(address_map);
end if;
--allocate a new string:
address_map:=new string (1 to address_count);
address_map(1 to 3):="xyz";
--we have here:
-- address_map(1)='y'
-- address_map(2 to 3)="yz"
-- address_map.all = "xyz"
Notice the use of new/deallocate (like malloc/free in C or free/delete in C++).
It is not easy to handle this kind of code, I recommend you to read the documentation of VHDL keywords "new", "deallocate" and "access" (easily found with your favorite search engine) or feel free to ask more questions.
You can also use the READ (read the whole line into a string) and WRITE (append a string to the line) functions from std.textio package.
I'm writing an multiarchitecture assembler/disassembler in Common Lisp (SBCL 1.1.5 in 64-bit Debian GNU/Linux), currently the assembler produces correct code for a subset of x86-64. For assembling x86-64 assembly code I use a hash table in which assembly instruction mnemonics (strings) such as "jc-rel8" and "stosb" are keys that return a list of 1 or more encoding functions, like the ones below:
(defparameter *emit-function-hash-table-x64* (make-hash-table :test 'equalp))
(setf (gethash "jc-rel8" *emit-function-hash-table-x64*) (list #'jc-rel8-x86))
(setf (gethash "stosb" *emit-function-hash-table-x64*) (list #'stosb-x86))
The encoding functions are like these (some are more complicated, though):
(defun jc-rel8-x86 (arg1 &rest args)
(jcc-x64 #x72 arg1))
(defun stosb-x86 (&rest args)
(list #xaa))
Now I am trying to incorporate the complete x86-64 instruction set by using NASM's (NASM 2.11.06) instruction encoding data (file insns.dat) converted to Common Lisp CLOS syntax. This would mean replacing regular functions used for emitting binary code (like the functions above) with instances of a custom x86-asm-instruction class (a very basic class so far, some 20 slots with :initarg, :reader, :initform etc.), in which an emit method with arguments would be used for emitting the binary code for given instruction (mnemonic) and arguments. The converted instruction data looks like this (but it's more than 40'000 lines and exactly 7193 make-instance's and 7193 setf's).
;; first mnemonic + operand combination instances (:is-variant t).
;; there are 4928 such instances for x86-64 generated from NASM's insns.dat.
(eval-when (:compile-toplevel :load-toplevel :execute)
(setf Jcc-imm-near (make-instance 'x86-asm-instruction
:name "Jcc"
:operands "imm|near"
:code-string "[i: odf 0f 80+c rel]"
:arch-flags (list "386" "BND")
:is-variant t))
(setf STOSB-void (make-instance 'x86-asm-instruction
:name "STOSB"
:operands "void"
:code-string "[ aa]"
:arch-flags (list "8086")
:is-variant t))
;; then, container instances which contain (or could be refer to instead)
;; the possible variants of each instruction.
;; there are 2265 such instances for x86-64 generated from NASM's insns.dat.
(setf Jcc (make-instance 'x86-asm-instruction
:name "Jcc"
:is-container t
:variants (list Jcc-imm-near
Jcc-imm64-near
Jcc-imm-short
Jcc-imm
Jcc-imm
Jcc-imm
Jcc-imm)))
(setf STOSB (make-instance 'x86-asm-instruction
:name "STOSB"
:is-container t
:variants (list STOSB-void)))
;; thousands of objects more here...
) ; this bracket closes (eval-when (:compile-toplevel :load-toplevel :execute)
I have converted NASM's insns.dat to Common Lisp syntax (like above) using a trivial Perl script (further below, but there's nothing of interest in the script itself) and in principle it works. So it works, but compiling those 7193 objects is really really slow and commonly causes heap exhaustion. On my Linux Core i7-2760QM laptop with 16G of memory the compiling of an (eval-when (:compile-toplevel :load-toplevel :execute) code block with 7193 objects like the ones above takes more than 7 minutes and sometimes causes heap exhaustion, like this one:
;; Swank started at port: 4005.
* Heap exhausted during garbage collection: 0 bytes available, 32 requested.
Gen StaPg UbSta LaSta LUbSt Boxed Unboxed LB LUB !move Alloc Waste Trig WP GCs Mem-age
0: 0 0 0 0 0 0 0 0 0 0 0 41943040 0 0 0.0000
1: 0 0 0 0 0 0 0 0 0 0 0 41943040 0 0 0.0000
2: 0 0 0 0 0 0 0 0 0 0 0 41943040 0 0 0.0000
3: 38805 38652 0 0 49474 15433 389 416 0 2144219760 9031056 1442579856 0 1 1.5255
4: 127998 127996 0 0 45870 14828 106 143 199 1971682720 25428576 2000000 0 0 0.0000
5: 0 0 0 0 0 0 0 0 0 0 0 2000000 0 0 0.0000
6: 0 0 0 0 1178 163 0 0 0 43941888 0 2000000 985 0 0.0000
Total bytes allocated = 4159844368
Dynamic-space-size bytes = 4194304000
GC control variables:
*GC-INHIBIT* = true
*GC-PENDING* = in progress
*STOP-FOR-GC-PENDING* = false
fatal error encountered in SBCL pid 9994(tid 46912556431104):
Heap exhausted, game over.
Welcome to LDB, a low-level debugger for the Lisp runtime environment.
ldb>
I had to add --dynamic-space-size 4000 parameter for SBCL to get it compiled at all, but still after allocating 4 gigabytes of dynamic space heap sometimes gets exhausted. Even if the heap exhaustion would be solved, more than 7 minutes for compiling 7193 instances after only adding a slot in the class ('x86-asm-instruction class used for these instances) is way too much for interactive development in REPL (I use slimv, if that matters).
Here's (time (compile-file output:
; caught 18636 WARNING conditions
; insns.fasl written
; compilation finished in 0:07:11.329
Evaluation took:
431.329 seconds of real time
238.317000 seconds of total run time (234.972000 user, 3.345000 system)
[ Run times consist of 6.073 seconds GC time, and 232.244 seconds non-GC time. ]
55.25% CPU
50,367 forms interpreted
784,044 lambdas converted
1,031,842,900,608 processor cycles
19,402,921,376 bytes consed
Using OOP (CLOS) would enable incorporating the instruction mnemonic (such as jc or stosb above, :name), allowed operands of the instruction (:operands), instruction's binary encoding (such as #xaa for stosb, :code-string) and possible architecture limitations (:arch-flags) of the instruction in one object. But it seems that at least my 3-year-old computer is not efficient enough to compile around 7000 CLOS object instances quickly.
My question is: Is there some way to make SBCL's make-instance faster, or should I keep assembly code generation in regular functions like the examples further above? I'd be also very happy to know about any other possible solutions.
Here's the Perl script, just in case:
#!/usr/bin/env perl
use strict;
use warnings;
# this program converts NASM's `insns.dat` to Common Lisp Object System (CLOS) syntax.
my $firstchar;
my $line_length;
my $are_there_square_brackets;
my $mnemonic_and_operands;
my $mnemonic;
my $operands;
my $code_string;
my $flags;
my $mnemonic_of_current_mnemonic_array;
my $clos_object_name;
my $clos_mnemonic;
my $clos_operands;
my $clos_code_string;
my $clos_flags;
my #object_name_array = ();
my #mnemonic_array = ();
my #operands_array = ();
my #code_string_array = ();
my #flags_array = ();
my #each_mnemonic_only_once_array = ();
my #instruction_variants_array = ();
my #instruction_variants_for_current_instruction_array = ();
open(FILE, 'insns.dat');
$mnemonic_of_current_mnemonic_array = "";
# read one line at once.
while (<FILE>)
{
$firstchar = substr($_, 0, 1);
$line_length = length($_);
$are_there_square_brackets = ($_ =~ /\[.*\]/);
chomp;
if (($line_length > 1) && ($firstchar =~ /[^\t ;]/))
{
if ($are_there_square_brackets)
{
($mnemonic_and_operands, $code_string, $flags) = split /[\[\]]+/, $_;
$code_string = "[" . $code_string . "]";
($mnemonic, $operands) = split /[\t ]+/, $mnemonic_and_operands;
}
else
{
($mnemonic, $operands, $code_string, $flags) = split /[\t ]+/, $_;
}
$mnemonic =~ s/[\t ]+/ /g;
$operands =~ s/[\t ]+/ /g;
$code_string =~ s/[\t ]+/ /g;
$flags =~ s/[\t ]+//g;
# we don't want non-x86-64 instructions here.
unless ($flags =~ "NOLONG")
{
# ok, the content of each field is now filtered,
# let's convert them to a suitable Common Lisp format.
$clos_object_name = $mnemonic . "-" . $operands;
# in Common Lisp object names `|`, `,`, and `:` must be escaped with a backslash `\`,
# but that would get too complicated.
# so we'll simply replace them:
# `|` -> `-`.
# `,` -> `.`.
# `:` -> `.`.
$clos_object_name =~ s/\|/-/g;
$clos_object_name =~ s/,/./g;
$clos_object_name =~ s/:/./g;
$clos_mnemonic = "\"" . $mnemonic . "\"";
$clos_operands = "\"" . $operands . "\"";
$clos_code_string = "\"" . $code_string . "\"";
$clos_flags = "\"" . $flags . "\""; # add first and last double quotes.
$clos_flags =~ s/,/" "/g; # make each flag its own Common Lisp string.
$clos_flags = "(list " . $clos_flags. ")"; # convert to `list` syntax.
push #object_name_array, $clos_object_name;
push #mnemonic_array, $clos_mnemonic;
push #operands_array, $clos_operands;
push #code_string_array, $clos_code_string;
push #flags_array, $clos_flags;
if ($mnemonic eq $mnemonic_of_current_mnemonic_array)
{
# ok, same mnemonic as the previous one,
# so the current object name goes to the list.
push #instruction_variants_for_current_instruction_array, $clos_object_name;
}
else
{
# ok, this is a new mnemonic.
# so we'll mark this as current mnemonic.
$mnemonic_of_current_mnemonic_array = $mnemonic;
push #each_mnemonic_only_once_array, $mnemonic;
# we first push the old array (unless it's empty), then clear it,
# and then push the current object name to the cleared array.
if (#instruction_variants_for_current_instruction_array)
{
# push the variants array, unless it's empty.
push #instruction_variants_array, [ #instruction_variants_for_current_instruction_array ];
}
#instruction_variants_for_current_instruction_array = ();
push #instruction_variants_for_current_instruction_array, $clos_object_name;
}
}
}
}
# the last instruction's instruction variants must be pushed too.
if (#instruction_variants_for_current_instruction_array)
{
# push the variants array, unless it's empty.
push #instruction_variants_array, [ #instruction_variants_for_current_instruction_array ];
}
close(FILE);
# these objects need be created already during compilation.
printf("(eval-when (:compile-toplevel :load-toplevel :execute)\n");
# print the code to create each instruction + operands combination object.
for (my $i=0; $i <= $#mnemonic_array; $i++)
{
$clos_object_name = $object_name_array[$i];
$mnemonic = $mnemonic_array[$i];
$operands = $operands_array[$i];
$code_string = $code_string_array[$i];
$flags = $flags_array[$i];
# print the code to create a variant object.
# each object here is a variant of a single instruction (or a single mnemonic).
# actually printed as 6 lines to make it easier to read (for us humans, I mean), with an empty line in the end.
printf("(setf %s (make-instance 'x86-asm-instruction\n:name %s\n:operands %s\n:code-string %s\n:arch-flags %s\n:is-variant t))",
$clos_object_name,
$mnemonic,
$operands,
$code_string,
$flags);
printf("\n\n");
}
# print the code to create each instruction + operands combination object.
# for (my $i=0; $i <= $#each_mnemonic_only_once_array; $i++)
for my $i (0 .. $#instruction_variants_array)
{
$mnemonic = $each_mnemonic_only_once_array[$i];
# print the code to create a container object.
printf("(setf %s (make-instance 'x86-asm-instruction :name \"%s\" :is-container t :variants (list \n", $mnemonic, $mnemonic);
#instruction_variants_for_current_instruction_array = $instruction_variants_array[$i];
# for (my $j=0; $j <= $#instruction_variants_for_current_instruction_array; $j++)
for my $j (0 .. $#{$instruction_variants_array[$i]} )
{
printf("%s", $instruction_variants_array[$i][$j]);
# print 3 closing brackets if this is the last variant.
if ($j == $#{$instruction_variants_array[$i]})
{
printf(")))");
}
else
{
printf(" ");
}
}
# if this is not the last instruction, print two newlines.
if ($i < $#instruction_variants_array)
{
printf("\n\n");
}
}
# print the closing bracket to close `eval-when`.
print(")");
exit;
18636 warnings looks really bad, Start by getting rid of all the warnings.
I would start by getting rid of the EVAL-WHEN around all that. Does not make much sense to me. Either load the file directly, or compile and load the file.
Also note that SBCL does not like (setf STOSB-void ...) when the variable is undefined. New top-level variables are introduced with DEFVAR or DEFPARAMETER. SETF just sets them, but does not define them. That should help to get rid of the warnings.
Also :is-container t and :is-variant t smell like these properties should be converted into classes to inherit from (for example as a mixin). A container has variants. A variant does not have variants.
I'm trying to have a function write a database sql dump to text file from a select statement. The volume returned can be very large, and I'm interested in doing this as fast as possible.
With a large result set I also need to log every x-interval the total number of rows written and how many rows per second have been written since last x-interval. I have a (map ) that is actually doing the write during a (with-open ) so i believe the side-effect of logging rows completed should happen there. (See comments in code).
My questions are:
How do i write "rows-per-second" during the interval and "total rows so far"?
Is there anything additional I want to keep in mind while writing large jdbc result sets to a file (or named-pipe, bulk loader, etc.) ?
Does the (doall ) around the (map ) function fetch all results... making it non-lazy and potentially memory intensive?
Would fixed width be possible as an option? I believe that would be faster for a named pipe to bulk loader. The trade-off would be on disk i/o in place of CPU utilization for downstream parsing. However this might require introspection on the result set returned (with .getMetaData?)
(ns metadata.db.table-dump
[:use
[clojure.pprint]
[metadata.db.connections]
[metadata.db.metadata]
[clojure.string :only (join)]
[taoensso.timbre :only (debug info warn error set-config!)]
]
[:require
[clojure.java.io :as io ]
[clojure.java.jdbc :as j ]
[clojure.java.jdbc.sql :as sql]
]
)
(set-config! [:appenders :spit :enabled?] true)
(set-config! [:shared-appender-config :spit-filename] "log.log")
(let [
field-delim "\t"
row-delim "\n"
report-seconds 10
sql "select * from comcast_lineup "
joiner (fn [v] (str (join field-delim v ) row-delim ) )
results (rest (j/query local-postgres [sql ] :as-arrays? true :row-fn joiner ))
]
(with-open [wrtr (io/writer "test.txt")]
(doall
(map #(.write wrtr %)
; Somehow in here i want to log with (info ) rows written so
; far, and "rows per second" every 10 seconds.
results ))
) (info "Completed write") )
Couple general tips:
At the JDBC level you may need to use setFetchSize to avoid loading the entire resultset into RAM before it even gets to Clojure. See What does Statement.setFetchSize(nSize) method really do in SQL Server JDBC driver?
Make sure clojure.java.jdbc is actually returning a lazy seq (it probably is?)-- if not, consider resultset-seq
doall will indeed force the whole thing to be in RAM; try doseq instead
Consider using an atom to keep count of rows written as you go; you can use this to write rows-so-far, etc.
Sketch:
(let [ .. your stuff ..
start (System/currentTimeMillis)
row-count (atom 0)]
(with-open [^java.io.Writer wrtr (io/writer "test.txt")]
(doseq [row results]
(.write wrtr row)
(swap! row-count inc)
(when (zero? (mod #row-count 10000))
(println (format "written %d rows" #row-count))
(println (format "rows/s %.2f" (rate-calc-here)))))))
You may get some use out of my answer to Idiomatic clojure for progress reporting?
To your situation specifically
1) You could add an index to your map as the second argument to the anonymous function, then in the function you are mapping look at the index to see what row you are writing. which can be used to update an atom.
user> (def stats (atom {}))
#'user/stats
user> (let [start-time (. (java.util.Date.) getTime)]
(dorun (map (fn [line index]
(println line) ; write to log file here
(reset! stats [{:lines index
:start start-time
:end (. (java.util.Date.) getTime)}]))
["line1" "line2" "line3"]
(rest (range)))))
line1
line2
line3
nil
user> #stats
[{:lines 3, :start 1383183600216, :end 1383183600217}]
user>
The contents of stats can then be printed/logged every few seconds to update the UI
3) you most certainly want to use dorun instead of doall because as you suspect this will run out of memory on a large enough data set. dorun drops the results as they are written so you can run it on infinitely large data if you want to wait long enough.
(You are welcome to change the title to a more appropriate one!)
I got another Ruby/ERB question. I have this file:
ec2-23-22-59-32, mongoc, i-b8b44, instnum=1, Running
ec2-54-27-11-46, mongod, i-43f9f, instnum=2, Running
ec2-78-62-192-20, mongod, i-02fa4, instnum=3, Running
ec2-24-47-51-23, mongos, i-546c4, instnum=4, Running
ec2-72-95-64-22, mongos, i-5d634, instnum=5, Running
ec2-27-22-219-75, mongoc, i-02fa6, instnum=6, Running
And I can process the file to create an array like this:
irb(main):007:0> open(inFile).each { |ln| puts ln.split(',').map(&:strip)[0..1] }
ec2-23-22-59-32
mongoc
ec2-54-27-11-46
mongod
....
....
But what I really want is the occurrence number concatenated to the "mongo-type" so that it becomes:
ec2-23-22-59-32
mongoc1
ec2-54-27-11-46
mongod1
ec2-78-62-192-20
mongod2
ec2-24-47-51-23
mongos1
ec2-72-95-64-22
mongos2
ec2-27-22-219-75
mongoc2
The number of each mongo-type is not fixed and it changes over time. Any help with how can I do that? Thanks in advance. Cheers!!
Quick answer (maybe could be optimized):
data = 'ec2-23-22-59-32, mongoc, i-b8b44, instnum=1, Running
ec2-54-27-11-46, mongod, i-43f9f, instnum=2, Running
ec2-78-62-192-20, mongod, i-02fa4, instnum=3, Running
ec2-24-47-51-23, mongos, i-546c4, instnum=4, Running
ec2-72-95-64-22, mongos, i-5d634, instnum=5, Running
ec2-27-22-219-75, mongoc, i-02fa6, instnum=6, Running'
# a hash where we will save mongo types strings as keys
# and number of occurence as values
mtypes = {}
data.lines.each do |ln|
# get first and second element of given string to inst and mtype respectively
inst, mtype = ln.split(',').map(&:strip)[0..1]
# check if mtypes hash has a key that equ current mtype
# if yes -> add 1 to current number of occurence
# if not -> create new key and assign 1 as a value to it
# this is a if ? true : false -- ternary operator
mtypes[mtype] = mtypes.has_key?(mtype) ? mtypes[mtype] + 1 : 1
# combine an output string (everything in #{ } is a variables
# so #{mtype}#{mtypes[mtype]} means take current value of mtype and
# place after it current number of occurence stored into mtypes hash
p "#{inst} : #{mtype}#{mtypes[mtype]}"
end
Output:
# "ec2-23-22-59-32 : mongoc1"
# "ec2-54-27-11-46 : mongod1"
# "ec2-78-62-192-20 : mongod2"
# "ec2-24-47-51-23 : mongos1"
# "ec2-72-95-64-22 : mongos2"
# "ec2-27-22-219-75 : mongoc2"
Quite strightforward I think. If you don't understand something -- let me know.
I may be in the minority here, but I very much enjoy Perl's formats. I especially like being able to wrap a long piece of text within a column ("~~ ^<<<<<<<<<<<<<<<<" type stuff). Are there any other programming languages that have similar features, or libraries that implement similar features? I am especially interested in any libraries that implement something similar for Ruby, but I'm also curious about any other options.
I seem to recall something similar in Fortran when I used it many years ago (however, it may well have have been a third-party library).
As for other options in Perl, have a look at Perl6::Form.
The form function replaces format in Perl6. Damian Conway in "Perl Best Practices" recommends using Perl6::Form with Perl5 citing the following issues with format....
statically defined
rely on global variables for configuration and pkg variables for data they format on
uses named filehandles (only)
not recursive or re-entrant
Here is a Perl6::Form variation on the Ruby example by Robert Gamble....
use Perl6::Form;
my ( $month, $day, $year ) = qw'Sep 18 2001';
my ( $num, $numb, $location, $toe_size );
for ( "Market", "Home", "Eating Roast Beef", "Having None", "On the way home" ) {
push #$numb, ++$num;
push #$location, $_;
push #$toe_size, $num * 3.5;
}
print form
' Piggy Locations for {>>>}{>>}, {<<<<}',
$month, $day, $year ,
"",
' Number: location toe size',
' --------------------------------------',
'{]}) {[[[[[[[[[[[[[[[} {].0} ',
$numb, $location, $toe_size;
FormatR provides Perl-like formats for Ruby.
Here is an example from the documentation:
require "formatr"
include FormatR
top_ex = <<DOT
Piggy Locations for #<< ##, ####
month, day, year
Number: location toe size
-------------------------------------------
DOT
ex = <<TOD
#) #<<<<<<<<<<<<<<<< ##.##
num, location, toe_size
TOD
body_fmt = Format.new (top_ex, ex)
body_fmt.setPageLength(10)
num = 1
month = "Sep"
day = 18
year = 2001
["Market", "Home", "Eating Roast Beef", "Having None", "On the way home"].each {|location|
toe_size = (num * 3.5)
body_fmt.printFormat(binding)
num += 1
}
Which produces:
Piggy Locations for Sep 18, 2001
Number: location toe size
-------------------------------------------
1) Market 3.50
2) Home 7.00
3) Eating Roast Beef 10.50
4) Having None 14.00
5) On the way home 17.50
There is the Lisp (format ...) function. It supports looping, conditionals, and a whole bunch of other fun stuff.
for example (copied from above link):
(defparameter *english-list*
"~{~#[~;~a~;~a and ~a~:;~#{~a~#[~;, and ~:;, ~]~}~]~}")
(format nil *english-list* '()) ;' ==> ""
(format nil *english-list* '(1)) ;' ==> "1"
(format nil *english-list* '(1 2)) ;' ==> "1 and 2"
(format nil *english-list* '(1 2 3)) ;' ==> "1, 2, and 3"
(format nil *english-list* '(1 2 3 4));' ==> "1, 2, 3, and 4"