OpenTBS is a plug-in for the TinyButStrong Template Engine.
TinyButStrong is a PHP Template Engine which has special template syntax and allows you to design templates in their natural editing tools. But it normally works only for Text files, including XML and HTML.
With TinyButStrong and its plug-in OpenTBS, you can use the template engine to merge OpenOffice documents and Ms Office documents with lot of facilities. All OpenDocument Format (ODF) and Office Open XML (OOXML) can be merged with OpenTBS, and also XPS files (XPS is a PDF competitor provided by Microsoft). In fact, all zip archives containing Xml/Html/Text files can be merged with OpenTBS.
You should know Template Engines and more specifically TinyButStrong to use OpenTBS.
Just put the file "tbs_plugin_opentbs.php" with your PHP scripts.
It is important to figure out that OpenOffice and Ms Office (since version 2007) documents are technically zip archives containing XML files, even if the extension of the document is not ".zip". Those zip archives can contain other file types like pictures or sounds, but the document structure and the text contents are saved as XML files. The XML Synopsis summarizes the key entities of XML sub-files contained in OpenOffice and Ms Office documents.
TinyButStrong can merge XML files, but cannot read zip archives by itself. The plug-in OpenTBS extends the TinyButStrong methods LoadTemplate() and Show() to make them working with zip archives. But you do not have to bother with it because OpenTBS is managing archives in a way that is invisible for you.
When the OpenTBS plugin is installed, the LoadTemplate() method becomes able to first load a zip archive (an OpenOffice or Ms Office document), and then to load the contents of any XML or Text files stored in the archive. You can then merge the contents of XML or Text files with all features of the TinyButStrong template engine. At the end, the Show() method does render the entire zip archive including modified stored files. The render can be done as an HTTP download, a news file on the server's disk, or in a PHP string.
Since OpenTBS version 1.3, you can also add and delete files in the archive. Before this version you could only modify existing files in the archive.
OpenTBS has automatic extension recognition. When you load a document which has one of the following extensions { odt, odg, ods, odf, odp, odm, docx, xlsx, pptx }, then the main XML file of the archive are automatically loaded, and some special character conversion are preset. For example, for all OpenDocument files, the stored file "content.xml" is automatically loaded.
Since version 1.6.0,
if the extension is not recognized then OpenTBS also try to find the document by the sub-file presence. And if all fails, then you can force the document type using a special command.
The archive must be previously loaded.
If the file is stored in a subfolder, then indicate the full path. For example: 'word/document.xml'.
OpenTBS manages XML files that are UTF8 encoded. But by default, it assumes that all the data to merge (which can come from PHP or SQL) is Ascii encoded, and thus it performs conversions. If you want to define the data conversion, then you can use one of the following constants:
Please note that if you need to change the data conversion for one or few fields only in your template, then you can use parameter "htmlconv" (see the TBS documentation for more details).
Render options for OpenTBS:
When you use OPENTBS_STRING then there is no output for the client. But instead, the binary source of the archive is placed into property $TBS->Source. This feature can be useful, for example, when you want to place the merged document into an email as an attached file.
This command changes the values of a series in a Chart of the document. The chart will be automatically actualized when the merged document is opened because OpenTBS also breaks the link between the chart and its cached view.
The result is true if the series is modified with success, otherwise the result is false.
| Argument | Description |
|---|---|
| $CharteNameOrNum | Internal name of the XML file that contains the chart definition (with or without the extension), or the order number of the chart in the document (first is number 1). You can use the command OPENTBS_DEBUG_CHART_LIST in order to view all chart internal names in the document. $CarteNameOrNum is typically 'chart1' or 1. |
| $SeriesNumOrName | Exact caption of the series in the chart, or its number (first is number 1). Typically 'Series 1' or 1. |
| $NewValues | The new data of the series. Must be an array, or value false if you want to delete the series. The array can store data with 2 possibilities: $NewValues = array( array('cat1', 'cat2', 'cat3', ...), array(val1, val2, val3, ...) ); or $NewValues = array('cat1'=>val1, 'cat2'=>val2, 'cat3'=>val3, ...); If the chart type is "X Y (Scatter)" then you must use only the first data store type. |
| $NewLegend | Optional. The new caption of the series. |
I the example above, $x is a PHP global variable containing the name or the path for an external picture file. But the feature works simillary with items data merged with a TBS block.
When a TBS field having "ope=changepic" is merged in the template, then OpenTBS will search the first picture located before the field (to be more precise, the TBS field must be located somewhere inside or after the opening tag of the picture in the template source code), and then it will change the picture assuming that the value of the field is the path for a picture file on the server. You don't have to care about loading the picture file in the document, OpenTBS will manage this for you.
Note that parameter "ope=changepic" is a feature provided by the OpenTBS plug-in, which extends the "ope" parameter natively present with TBS.
In order to
simplify your coding, they are other complementary parameters that are provided for the changepic parameter:
| Parameter | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| from | Reformulate the path of the picture to insert. The parameter's value can contain the [val] keyword or any [var] fields, they work the same way as with parameter "file". Parameter "from" is optional. | ||||||||
| as | Reformulate name of the picture that it will take inside the document. It is rare to need it, but it can help in some cases. Note that the external picture file is not renamed. The new name must be defined without path. The parameter's value can contain the [val] keyword or any [var] fields, they work the same way as with parameter "file". Parameter "as" is optional. | ||||||||
| default | Define the picture that should be used when the expected one is not found. The parameter's value must be the path of a file on the server, or the keyword "current". If you've set "default=current" then OpenTBS will let the picture of the template if the expected one is not found. | ||||||||
| adjust | Adjust the size of the picture in the document. This parameter requires that PHP is configured with the GD extension, which is usually the case. Values can be on of the followings:
|
The following commands are supported since OpenTBS version 1.7.0:
| Command | Desciption |
|---|---|
| $TBS->PlugIn(OPENTBS_SELECT_MAIN) | Select and load the main sub-file in the opened template. For example in a Writer document, or an Ms Word document, this command can bring you back from the merging of a header to the main body. |
| $TBS->PlugIn(OPENTBS_SELECT_SHEET, $Sheet) | Select and load the sub-file corresponding to $Sheet. |
| $TBS->PlugIn(OPENTBS_DISPLAY_SHEETS, $Sheets[, $Visible]) | Make one or several sheets visible or hidden. This command will raise an error if the opened template is not a Workbook. $Sheets must be an array of sheet identifier, or even a single sheet identifier. See command OPENTBS_SELECT_SHEET for more details about sheet identifiers. $Visible must be a boolean, default value is true. |
| $TBS->PlugIn(OPENTBS_DELETE_SHEETS, $Sheets[, $Delete]) | Make on or several sheets deleted or not. This command will raise an error if the opened template is not a Workbook. $Sheets must be an array of sheet identifier, or even a single sheet identifier. See command OPENTBS_SELECT_SHEET for more details about sheet identifiers. $Delete must be a boolean, default value is true. Please note that for now, you must not delete a sheet that contains a Pivot Table because this will produce an error when the workbook is opened. |
| $TBS->PlugIn(OPENTBS_DELETE_COMMENTS) | Delete all usual user comments in the opened template. |
| $TBS->PlugIn(OPENTBS_DELETE_ELEMENTS, $Elements) | Delete XML elements in the current sub-file. $Elements must be an array of strings. For example: $Elements = array('w:bookmarkStart', 'w:bookmarkEnd') This will delete all bookmarks in an Ms Word document. |
Return true or false. $Name must include the inner path.
For example : $Name = 'META-INF/manifest.xml';
If $Data is false then the previously add file with the given name is canceled if any.
$DataType must be OPENTBS_STRING if $Data is the content to add ; it must be OPENTBS_FILE if $Data is the path of the external file to insert.
$Compress can be true, false or an array with keys ('meth','len_u','crc32') which means that the data is already previously compressed.
The arguments are the same as command OPENTBS_ADDFILE.
Please note that any TBS
merge on a file in the archive will cancel previous or future replacements.
Delete the existing file in the archive, or a file previously added using the OPENTBS_ADDFILE command.
The automatic extension recognition is also applied as it was applied for the first load of the archive.
Both OpenOffice and Ms Office may automatically convert single quotes (') into typographic apostrophes (’), depending to the auto-correction options. This may be annoying when you need to code a TBS fields that have a single quote. That's why OpenTBS automatically convert by default all (’) back to single quotes (') in documents.
If you want to stop this conversion, you can set $TBS->OtbsConvertApostrophes = false; and no apostrophes will be converted. Note that you can avoid the auto-correction of single quotes (') in Ms Word using keys[ctrl]+[z], and in OpenOffice using the cancel button.
You can force the document type recognition using command OPENTBS_FORCE_DOCTYPE. Example:
This command is supported since OpenTBS version 1.6.0.
Property $TBS->tbsCurrFile indicates the name of the current file loaded from the archive. The value is false if no file is loaded yet from the archive.
Other TinyButStrong methods and properties stay unchanged and are available for merging your template.
The OpenTBS package includes a full set of runnable templates. Some templates can contain useful complementary information for designing.
Run the following demo under PHP: OpenTBS demo
Since OpenTBS version 1.6.0, there are several commands for debugging. Please note that those commands do not exit the process.
| Command | Desciption |
|---|---|
| $TBS->PlugIn(OPENTBS_DEBUG_INFO [, $Exit]) | Display technical information about the current loaded template, including sheet information if the template is a workbook, and chart information if the template have some. $Exit must be a boolean, default value is true. |
| $TBS->PlugIn(OPENTBS_DEBUG_XML_CURRENT) | Display XML contents of sub-files already opened and modified for merging. XML is indented in order to improve reading. |
| $TBS->PlugIn(OPENTBS_DEBUG_XML_SHOW) | Ends the merge process as if the final document was created. But instead of creating the document, displays the XML contents of sub-files modified for merging. XML is indented in order to improve reading. |
There is also deprecated debug options:
| Command | Desciption |
|---|---|
| $TBS->PlugIn(OPENTBS_DEBUG_XML) | Does the same as $TBS->PlugIn(OPENTBS_DEBUG_XML_SHOW); Supported since OpenTBS version 1.3.2. |
| $TBS->PlugIn(OPENTBS_DEBUG_XML+OPENTBS_DEBUG_AVOIDAUTOFIELDS) | Avoid merging of [onload], [onshow] and [var].Supported since OpenTBS version 1.3.2. |
| $TBS->Render = OPENTBS_DEBUG_AVOIDAUTOFIELDS; | Work also in property Render.Supported since OpenTBS version 1.3.2. |
| $TBS->PlugIn(OPENTBS_DEBUG_CHART_LIST) | Does the same as $TBS->PlugIn(OPENTBS_DEBUG_INFO);Supported since OpenTBS version 1.6.0. |
Otherwise, here are some indications that may help for the issues you can met with merging:
The most likely causes are:
• You've chosen the OPENTBS_DOWNLOAD render option but a php error message or any other unexpected content has been output before by PHP.
Activate the debug mode using the command OPENTBS_DEBUG_XML_SHOW, it helps to check PHP error message and other unexpected content.
• The merging has produced an invalid document or an invalid XML content in an XML file of the document.
Activate the debug mode using it helps to check the XML contents of merged files.
See section (b) below for more information in the XML structure of the files.
First, you can have a look the demo templates, they contain examples and advices for each type of document.
And to go further: even if you can edit your template using directly OpenOffice or Ms Office, you will probably need to understand the XML tags and attributes to complete your merge. The file xml_synopsis.txt is a small synopsis of the XML structure you can found in the inner source of those documents. Have a look to it if you feel lost.
You can view the inner source of a document using a zip software like 7-Zip. It allows you to open an archive even if the extension is not ".zip".
OpenTBS uses Zlib functions in order to automatically uncompress and recompress files stored in the zip archive. If Zlib is not enabled, then you have to use your own uncompress/compress tool, or to prepare the template to have files uncompressed in the zip archive.
Example to uncompress the "content.xml" file in an ODT document using 7-Zip:
1) open the ODT file with 7-Zip
2) extract the "content.xml" file from the ODT file in the same folder than the ODT file
3)
close 7-Zip
4)
open 7-Zip, and change current directory to be the same as the ODT file
5) select the
"content.xml" file and click on button [Add], or menu [File][7-Zip][Add to archive...]
6) A new window named "Add to archive" is opened,
- replace the archive name with the ODT file name,
- set the Compression level to "None".
7) Click on [Ok]
If you re-open the ODT file with 7-Zip, you can notice that the size and the uncompressed size are the same.
If the file should be placed in a sub-folder of the archive, then open the archive and rename the file in order to move it in a folder. For example rename "manifest.xml" to "META-INF\manifest.xml" will move it into META-INF. But moving the file will no delete the one which has the same name in the target folder. You have to go and delete the old one.
version 1.7.4, on 2011-10-20
- parameter "defaut=current" does not work and may build invalid documents when the target image is missing.
- new command OPENTBS_REPLACEFILE
- new command OPENTBS_FILEEXISTS
version 1.7.3, on 2011-10-13
- fixed bug: in Ms Word documents, automatic fields (onload, onshow) placed in headers and footers with parameter "ope=changepic" are producing an erroneous merge. In Word 2010 the picture may by missing, in Word 2007 the docx file may be considered as corrupted.
version 1.7.1, on 2011-10-07
- fixed bug: first non-empty cell of an Excel Spreadsheet is never merged if it contains a TBS field.
- minor internal improvements.
version 1.7.0, on 2011-08-21
- new parameter 'adjust' for changing picture size
- new command OPENTBS_DEBUG_INFO
- new command OPENTBS_SELECT_MAIN
- new command OPENTBS_SELECT_SHEET
- new command OPENTBS_DISPLAY_SHEETS
- new command OPENTBS_DELETE_SHEETS
- new command OPENTBS_DELETE_COMMENTS
- new command OPENTBS_DELETE_ELEMENTS
- parameter 'changepic' is optimized
version 1.6.2, on 2011-07-12
- fixed bug: Ms Excel cells could consider as error some formatted values such as '0.00000000000000'.
version 1.6.1, on 2011-06-08
- fixed bug: some documents may be corrupted when created using OPENTBS_DOWNLOAD because of a PHP error "supplied argument is not a valid stream resource" or "Undefined property: clsOpenTBS::$OutputHandle".
- fixed bug: using keyword "xlsxNum", "xlsxDate" or "xlsxBool" inside a cell that is not merged can make a corrupted XLSX spreadsheet.
- improvement: updated templates in the demo.
- based on a TbsZip v2.8
version 1.6.0, on 2011-06-07
- new feature: merge charts in Ms Word documents.
- new feature: merge rows and columns Ms Excel workbooks.
- new feature: new "ope" parameters for forcing cells type in Ms Excel (Numeric, Date and Boolean).
- new feature: debug mode enhanced.
- new feature: force the type of document using command OPENTBS_FORCE_DOCTYPE.
- new property: deal with apostrophes using property OtbsConvertApostrophes.
- improvement: if the document extension is not recognized, then try to recognize document type by sub-file presence.
- improvement: can use the Direct Command feature of TBS 3.7.0.
- based on a TbsZip v2.6
version 1.5.0, on 2011-03-20
- new feature: headers and footers are automatically loaded for OpenOffice & MsOffice.
- new feature: automatically cleans up spelling and change trackings information in MsWord templates (such information may
deconstruct the TBS tags). This feature can be disabled.
- new constant OPENTBS_DEBUG_AVOIDAUTOFIELDS
- improvement: Debug doesn't stopped if an OpenTBS alert occurs.
- improvement: OpenTBS alerts say if the process will be stopped.
- fixed bug: in debug mode: "warning function.str-repeat: Second argument has to be greater than or equal to 0"
- fixed bug: when using OPENTBS_RESET: "Warning: Missing argument 2 for clsOpenTBS::OnCommand() in ... on line 225"
- fixed bug: DML images were not found when using parameter "ope=changepic" in a DOCX document
- fixed bug: the script ends and display the XML contents when a when using parameter "ope=changepic" with a new image type in a DOCX document
version 1.4.1, on 2010-10-28
- major bug fixed: due to TbsZip, some added or modified files can be saved the document with a wrong CRC control code. This could make softwares to consider the document as corrupted, but were often easily fixed by OpenOffice and Ms Office. Only few CRC codes are wrongly saved, thus the bug is rare and can seem to appear randomly on few documents.
version 1.4.0, on 2010-10-05
- new parameters "changepic" and "default"
version 1.3.3, on 2010-08-05
- property Version of OpenTBS version 1.3.2 was saying 1.3.1
version 1.3.2, on 2010-07-23
- possibility to change de default data conversion using the new constants OPENTBS_DEFAULT, OPENTBS_ALREADY_XML or OPENTBS_ALREADY_UTF8
- enhanced debug mode: listing of added, deleted and modified files ; and show XML formated contents of files merged with OpenTBS.
version 1.3.1, on 2010-07-01
- based on TbsZip version 2.1: fixes a bug that saved a bad time of modification file was added, and saved time modification when a file content is replaced.
- the addpic operator now automatically updates the "fanifest.xml" file on OpenOffice document. Without this fix, an ODP merged document could be open with an error message with OpenOffice >= 3.2
version 1.3, on 2010-06-01
- a new plugin command that add a new file in the archive
- a new plugin command that delete a new file in the archive
- a parameter 'ope=addpic' that add a new picture in the archive directly from the template
- based on a TbsZip v2 (modify/delete/add files in a zip archive, )
version 1.1, on 2009-11-19
- New render option : OPENTBS_STRING
- New feature: can reset changes in the current archive using $TBS->Plugin(OPENTBS_PLUGIN, OPENTBS_RESET);
- New behavior: extension of the archive is ignored by LoadTemplate() if the name is ended with '#'
- Bug fixed: in case of several files to take from the archive in one shot, then only the last one had [onload] fields merged.
OpenTBS is under LGPL (Lesser General Public License)


FROM: MRS. ELIZABETH OGALI DEPARTMENT OF PETROLEUM RESOURCES 7 KOFO ABAYOMI STREET, VICTORIA ISLAND LAGOS - NIGERIA. Dear Friend, This letter may come to you as a surprise but it was borne out of my sincere desire to share a mutual business relationship with you. First, your strictest confidence in this transaction is highly solicited. This is by virtue of its nature as being utterly confidential and top secret. We are top government official with a statutory corporation and members of an ad hoc committee set up by the Federal Government of Nigeria to review contract awarded by past administration. In the course of identifying, scrutinizing and recommending for the payment of all valid contract executed, we discovered a huge sum of money amounting to USD41.5M (Forty One Million five Hundred Thousand US Dollars) on grossly over invoiced contract already awarded and executed for the Department of Petroleum Resources. Having Cleaned the 'AUGEAN STABLE' we intend to transfer the balance of USD41.5M presently floating in our apex bank of Nigeria to our own benefit and advantage. However, we request for your unwavering assistance in this regard because as civil servants we are prohibited under the civil service code of conduct bureau from operating a foreign account or running a foreign company unless after retirement in this vain we want you to front for us as partner to enable us lodge the funds speedily into your account. Bear in mind that no risk is attached to this project and all logistics are in place and modalities worked out for the smooth conclusion within a stipulated time. This is in accordance with the fact that you must never betray the trust already reposed on you. We have decided to compensate you with 30% of the total sum for your support, 60% for us while 10% for miscellaneous expenses (local and international). Please, provide your confidential phone and fax number to enable my boss contact you for further discussion on this matter to his private email address<EMAIL_ADDRESS>Please advise in your return e-mail if any time is confidential enough to call you. Looking forward to hearing from you. Best regards, Mrs Elizabeth Ogali (Secretary of the Committe)


How do we learn?
New positive habits that arrived with new technologies
No, I was never the kind of person who reads a lot, reading was not the funniest activity of my childhood by far. I was too active to sit down and spend some hours of my day just absorbing information from a static object called a book. As some of us saw Prince Ea. video: “I SUED THE SCHOOL SYSTEM !!!", and if you havent you should, we should accept that people are different and understand they have way different needs to absorb knowledge.
Since I was young I have heard people saying we can learn by three different methods, reading, hearing and writing and by that time, it made a lot of sense, but let’s break it down for our generation. In 19XX, going to class and taking notes in your personal paper notebook was the only way people could study, books were not accessible, expensive and too difficult to produce in scale, newspaper was the best option for our daily base information. Moving forward in history, when radio arrives, news could be more accessible, but at that time it was too hard to develop some kind of mass education program by using radio, it would be expensive and not as personalized as it needed to be, radios was also too expensive.
Looking backwards, when print machines became more accessible, book prices dramatically went down and now millions of people had the acquisitive power to buy “knowledge”. This made the reading method of learning way popular, I like to think of this moment as a “knowledge independence day”. When TV arrived, entertainment was already a competency of learning and our attention was in the early attacks by monetization, suddenly people’s time was expended by watching something on the screen like soccer or football or comedy shows and soap operas. This moment was enabled by the Industrial Revolution that unlocked many other things in our society.
Let’s jump to recent days. When we deeply analyse what we have learned in the past, learning methods have a drastic increase in possibilities. With a computer in almost everybody’s hands, knowledge becomes accessible in tons of different shapes which makes me amazed by the inclusion. We have audiobooks with Audible, we have apps like 12 Minutes for a short version of books, we have many online schools running courses online live or recorded, from Udemy and Coursera to Harvard, MIT and Stanford, we also have YouTube, where anybody can give a shot and try to teach us something. Yes, they’re actually good @ in TikTok and Instagram to learn Chinese and English and many other things enabled by world Digital Transformation.
Now the challenge is absolutely just one. How can we plan our time so this massive amount of content becomes positive and relevant in your lifes? I would like it to be an easy answer, but it’s not. The only thing we know is, we desperately need to control our screen time because our attention is, more than ever, the new way of doing business. Look at The Social Dilemma in on Netflix to connect some of the dots. Time is money, and now, more than ever, Our time is somebody else’s money.
With all these new paths to learn, we can have many other articles to discuss. But some of the polemic questions it still remains:
-
Why do we still have school as an obsolete model of learning? Worse than that, some companies still select people by only looking at their school background.
-
Why do I still feel people addicted to reading are prejudiced over people learning by other methods?
-
I’ll keep this conversation alive in further posts! Thank you for all that has followed me in this writing journey!
Read this article on Medium


| Hour | Description | Save & Delete |
|---|---|---|
| 9:00 | Check Email Inbox: Review every email in the inbox and make sure to give immediate response to every one. Remember to separate those you can solve to adjust today's agenda and get the best of time. | |
| 10:00 | Status with ARG: Daily checkup with the team to manage the pending tasks. Make sure you get last results from the 'importer' and compare its analytics with the databases. | |
| 11:00 | Status with EXT: Share with the team the results from the 'importer' and assign tasks to correct potential warnings and errors. If there are no tasks, then assign a new database to prepare it for migration. | |
| 12:00 | Google Search Console: Review the warnings and errors from the console. Start solving those with faster solutions and separate the more complex for the internal team. | |
| 13:00 | Advertisement: Make sure the publicity is available for all the websites, for WEB and AMP versions. Review the Units, positions, and sizes. If a space is empty, send a message to the team to solve the problem. | |
| 14:00 | Work Time: Now it's time to improve and solve those emails with more complex solutions. Try to make a plan, solve them if possible and make sure you separate those tasks you need to solve individually from those with a collaborative approach. Eat something!!!. | |
| 15:00 | Enter text... | |
| 16:00 | Send the last emails of the day, and make sure they have all the possible information. Leave no room for doubt. | |
| 17:00 | Go home and drive safe :D | |
| 18:00 | BOOTCAMP: The best part of the day. Ask questions, even if the question seems dumb. Get the best of time. DON'T THINK ABOUT WORK! | |
| 19:00 | Enter text... | |
| 20:00 | Enter text... | |
| 21:00 | Enter text... | |
| 22:00 | STUDY: Spend time for yourself, you need to study and/or solve the homework. |


Govt not giving Asif Zardari access to specialist doctors, personal physician: Sherry Rehman
by https://www.facebook.com/dunyanewsISLAMABAD (Dunya News) – Pakistan Peoples Party (PPP) parliamentary leader in the Senate Sherry Rehman said government is not giving former president Asif Ali Zadrari access to specialist doctors and his personal physician, Dunya News reported on Monday.
While expressing concerns, the PPP leader maintained that Asif Zardari’s health is deteriorating with every passing day. No private medical board is being formed for the erstwhile president’s health, she added.
Sherry Rehman further said that the incumbent government is playing with Asif Zardari’s health. The medical reports of the former president and PPP co-chairman are also not being shared with anyone, she told.


Allows attributes to be declared as lazy, meaning that they won’t be computed until they are asked for.
Works by delegating each lazy_reader to a cached lazy_source method.
class Person
lazy_reader :eyes def lazy_source OpenStruct.new(:eyes => 2) end
end
>> Person.new.eyes
> 2
Classes and Modules
- MODULE Lazy


Waits for selected processors to finish processing of all pending messages in their mailboxes.
Waits for selected processors to finish processing of all pending messages in their mailboxes.
ids of registered processors to wait for. Default value is the set of all registered processor ids.
maximum duration to wait for processing to complete.
Creates and registers a org.eligosource.eventsourced.core.Channel.
Creates and registers a org.eligosource.eventsourced.core.Channel. The channel is
registered under the id specified by props.id
. If props.name
is defined it is also
registered under that name.
channel configuration object.
org.eligosource.eventsourced.core.Channel ref factory.
a channel ref.
if name
is defined and already in use
in the underlying actor system.
org.eligosource.eventsourced.core.DefaultChannelProps org.eligosource.eventsourced.core.ReliableChannelProps
Map of registered org.eligosource.eventsourced.core.Channels.
Map of registered org.eligosource.eventsourced.core.Channels. Mapping key is the channel id.
Returns a Future
that will be completed when selected processors have finished
processing all pending messages in their mailboxes.
Returns a Future
that will be completed when selected processors have finished
processing all pending messages in their mailboxes.
ids of registered processors to wait for. Default value is the set of all registered processor ids.
Enables the specified channels and starts delivery of pending messages.
Enables the channel registered under channelId
and starts delivery of pending messages.
Enables all registered channels and starts delivery of pending messages.
Journal for this extension.
Map of registered named org.eligosource.eventsourced.core.Channels.
Map of registered named org.eligosource.eventsourced.core.Channels. Mapping key is the channel name.
Registers an org.eligosource.eventsourced.core.Eventsourced processor.
Registers an org.eligosource.eventsourced.core.Eventsourced processor.
This method obtains the id from the created processor with a blocking operation.
Use the overloaded processorOf(ProcessorProps)
method if you want to avoid
blocking.
actor ref configuration object.
optional processor name.
org.eligosource.eventsourced.core.Eventsourced ref factory.
a processor ref.
if name
is defined and already in use
in the underlying actor system.
Registers an org.eligosource.eventsourced.core.Eventsourced processor.
Registers an org.eligosource.eventsourced.core.Eventsourced processor.
processor configuration object.
a processor ref.
if props.name
is defined and already
in use in the underlying actor system.
Map of registered org.eligosource.eventsourced.core.Eventsourced processors.
Map of registered org.eligosource.eventsourced.core.Eventsourced processors. Mapping key is the processor id.
Recovers selected processors and all channels registered at this extension by
sequentially executing replay(f)
and then deliver()
.
Recovers selected processors and all channels registered at this extension by
sequentially executing replay(f)
and then deliver()
.
This method waits for replayed messages being added to processor mailboxes but does not wait for replayed input messages being processed. However, any new message sent to any of the selected processors, after this method successfully returned, will be processed after the replayed event messages.
Clients that want to wait for replayed messages being processed should call the
awaitProcessing
method after this method successfully returned.
function called for each processor id. If the function returns None
no message replay will be done for that processor. If it returns Some(snr)
message replay will start from sequence number snr
for that processor.
wait for the specified duration for the replay to complete.
if replay doesn't complete within the specified duration.
Recovers all processors and channels registered at this extension by
sequentially executing replay(_ => Some(0))
and then deliver()
.
Recovers all processors and channels registered at this extension by
sequentially executing replay(_ => Some(0))
and then deliver()
.
Replay is done with no lower bound (i.e. with all messages in the journal).
This method waits for replayed messages being added to processor mailboxes but does not wait for replayed input messages being processed. However, any new message sent to any of the selected processors, after this method successfully returned, will be processed after the replayed event messages.
Clients that want to wait for replayed messages being processed should call the
awaitProcessing
method after this method successfully returned.
wait for the specified duration for the replay to complete.
if replay doesn't complete within the specified duration.
Replays input messages to selected processors.
Replays input messages to selected processors. The returned Future
will be
completed when the replayed messages have been added to the selected processor's
mailboxes. Any new message sent to any of the selected processors, after successful
completion of the returned Future
, will be processed after the replayed input
messages.
Clients that want to wait for replayed messages being processed should call the
awaitProcessing
method after the returned Future
successfully completed.
function called for each processor id. If the function returns None
no message replay will be done for that processor. If it returns Some(snr)
message replay will start from sequence number snr
for that processor.
Event-sourcing extension for Akka. Used by applications to create and register org.eligosource.eventsourced.core.Eventsourced processors and event message org.eligosource.eventsourced.core.Channels and to recover them from journaled event org.eligosource.eventsourced.core.Messages.


Creating Conda Recipes¶
This document presents conda-forge information about creating recipes overall. Details of modifying files inside of a recipe are present on other pages in this documentation.
Getting Started¶
There are two ways to get started:
- If it is a python package you can generate a skeleton as a starting point with
conda skeleton pypi your_package_name
. You do not have to use skeleton, and the recipes produced by skeleton will need to be edited. - Look at one of these examples in this repository and modify it as necessary.
Your final recipe should have no comments and follow the order in the example.
If there are details you are not sure about please open a pull request. The conda-forge team will be happy to answer your questions.
Avoid Dependencies Outside of Conda-Forge¶
Do all of my package’s dependencies have to be in conda(-forge) already?
Short answer: yes. Long answer: In principle, as long as your dependencies are in at least one of your user’s conda channels they will be able to install your package. In practice, that is difficult to manage, and we strive to have all dependencies built in conda-forge.
Building all of the dependecies in conda-forge allow us greater assurance of ABI compatability for the conda-forge packages. Only in extreme cases should you rely on a dependecy outside of conda-forge.
Optional: bld.bat
and/or build.sh
¶
In many cases, bld.bat
and/or build.sh
files are not required. Pure Python packages almost never need it.
If the build can be executed with one line, you may put this line in the script
entry of the build
section of
the meta.yaml
file.
Maintainer Role¶
The maintainers “job” is to:
- keep the feedstock updated by merging eventual maintenance PRs from conda-forge’s bots;
- keep the package updated by bumping the version whenever there is a new release;
- answer eventual question about the package on the feedstock issue tracker.
Other Recipes in staged-recipes
¶
When a PR of a recipe to the staged-recipes
repo is ready to go, it is merged into master
. This will trigger
a CI build specially designed to convert the recipe. However, for any number of reasons, the recipe may not be converted
right away. In the interim, the recipe will remain in master
until they can be converted.
There is no action required on the part of recipe contributors to resolve this. Also it should have no impact on any other
PRs being proposed. If these recipe pending conversion do cause issues for your submission, please ping @conda-forge/core
for help.


--- layout: post title: "Recurse" description: "How to refactor from a loop using mutable state to a recursive implementation." date: 2015-12-01 9:12 UTC tags: [F#, Languages, Functional Programming] --- {% include JB/setup %}
{{ page.description }}
One of the most compelling reasons to adopt Functional Programming (FP) is the emphasis on immutable values. All the dread and angst associated with state that can implicitly change while you're not looking, is gone.
One of the most frustrating aspects of FP for people coming from other paradigms is the emphasis on immutable values. You know that you ought to be able to implement a given algorithm using immutable values, but given your background in Object-Oriented Programming, you can't quite figure out how to do it.
In FP, loops are implemented with recursion, and mutable values are replaced with accumulator arguments. This article describes how to refactor from a procedural, mutable loop implementation to a pure, tail-recursive implementation.
You want your loop implementation to be 'Functional' instead of procedural. There can be many reasons for this. Perhaps you want to learn FP. Perhaps you want to eliminate mutable state in order to make your implementation thread-safe. Perhaps you think that getting rid of mutation will make the code more readable and maintainable. Perhaps you want to port your implementation to a language that doesn't support mutability at all (like Haskell).
Start with a procedural implementation, using a mutable loop variable. This obviously only works in multi-paradigmatic languages where mutable variables are possible, even if they're not ideal. Examples include F#, Scala, and Clojure, but not Haskell, which isn't multi-paradigmatic.
Imagine that you receive a stream of characters from someone typing on a keyboard. Sometimes, the typist mistypes and uses the backspace key, which sends the character '\b'. Whenever you encounter '\b', you should remove the preceding character, as well as the backspace character itself. This example is based on this Stack Overflow question.
The original F# implementation is procedural, using a for loop and a single mutable variable:
open System open System.Collections.Generic let handleBackspaces textToProcess : string = let stack = Stack<char>() for c in textToProcess do if c = '\b' then stack.Pop() |> ignore else stack.Push c stack |> Seq.rev |> Seq.toArray |> String
While this implementation doesn't explicitly use the mutable
keyword, the stack
variable is mutable because Stack<T> is mutable. Since textToProcess
is a string, and string implements IEnumerable<char>, you can loop over each char value, pushing the value on the stack unless it's a backspace character; in that case, the previous value is instead popped and thrown away.
According to the rules of the Recurse refactoring, you should introduce a recursive function instead of the loop, and add an argument that will replace the stack. To make it easy, call the recursive function imp
, and the function argument acc
. The name acc
is popular; it's short for accumulator. This argument is used to accumulate the final value, just like stack
in the above example.
let handleBackspaces' textToProcess : string = let rec imp acc = function | [] -> acc | '\b'::cs -> imp (acc |> List.tail) cs | c::cs -> imp (c::acc) cs textToProcess |> Seq.toList |> imp [] |> List.rev |> List.toArray |> String
The imp
function is declared with the rec
keyword to mark it as a recursive function. It has the type char list -> char list -> char list
. acc
is a char list
, which is also the case for the second argument implied by the function
keyword. The function returns the accumulator if the input list is empty; otherwise, it matches on the head of the list. If the head is explicitly the backspace character, the imp
function calls itself recursively, but replaces acc
with the tail of acc
; it uses List.tail to get the tail. This effectively removes the most recent character from the accumulator. In all other cases, the function also calls itself by consing c
on acc
.
The imp
function is tail-recursive, since all other values are already computed when a recursive call to imp
takes place.
You can further refactor this implementation to use a fold instead of a recursive function.
This second example is a more complex example that further illustrates how to apply the Recurse refactoring. If you already feel that you understand how to apply this refactoring, you can skip reading the following example.
Some time ago, I was attempting to implement the Graham Scan algorithm to find the convex hull for a set of points. As I've described before, this turned out to be quite difficult for me. One of my problems was that I was trying to implement the algorithm in Haskell, and while I understood the algorithm, I couldn't figure out how to implement it in a functional way - and when you can't do it the functional way, you can't do it at all in Haskell.
In F#, on the other hand, you can implement algorithms using procedural code if you must, so I decided to implement the algorithm in F# first, using a procedural approach, and then subsequently figure out how to refactor to immutable values. Once I had a pure F# implementation, I could always back-port it to Haskell.
This is one of the reasons I think F# is a better language for learning FP if you come from an Object-Oriented background: you can gradually refactor towards more Functional implementations as you become better at FP.
The part that caused me particular difficulty was the scan part, where the algorithm examines all points to identify which points to discard from the hull (because they're in the interior of the hull, and not on the hull).
After sorting all candidates according to special rules, the algorithm must consider each point in turn. If that new point is 'to the right' of the previous two points, the previous point is in the interior of the hull and should be discarded. The previous-to-previous point could also be 'to the right' of the new point, so the algorithm needs to check again, and so on.
My imperative solution looked like this:
let inline hullPoints points = let mutable ps = [] for p in points do ps <- ps @ [p] let mutable shouldCheck = true while shouldCheck do let wasDiscarded, newPoints = check ps shouldCheck <- wasDiscarded if wasDiscarded then ps <- newPoints ps
(You can see the full code base on GitHub. The start is at 5290abd3c31c162ee6c4b21b82494ce97ecf7fa5, and the end state that this post describes is at e3efd1b457a46112cff6f06b8cbb100d153f0ef1.)
Due to the inline
keyword, the hullPoints function has a complex type, but for practical purposes, think of it as having the type (int * int) seq -> (int * int) list
. The points
argument is a sequence of coordinates: (int * int) seq
.
As you can see, this implementation has a nested loop. The outer loop traverses all points
and appends the point in consideration to the mutable list variable ps
. At this stage, p
is only a candidate. What the algorithm must now determine is whether p
is in the interior or might be a hull point.
In order to do that, it calls another function called check
. The check function is another inline function, but you can think about it as having the type (int * int) list -> bool * (int * int) list
. The return type is peculiar, but the idea is that it returns true in the first tuple element if points were discarded from the input, and false if no points were discarded. The second tuple element contains the points (that may or may not have had points removed compared to the input points). (I later refactored this function to a function called tryDiscard with the type (int * int) list -> (int * int) list option
.)
If points were discarded, the algorithm must check again, as there may be more points to discard. Only when no more points were discarded can the outer loop move on to the next candidate.
According to the Recurse refactoring, you need to define a recursive function for each loop. There are two loops here, but do the inner loop first. Each mutable variable should be replaced with a function argument, but fortunately there's only one:
let inline hullPoints points = let rec update candidates = let wasDiscarded, newCandidates = check candidates if wasDiscarded then update newCandidates else candidates let mutable candidates = [] for p in points do candidates <- candidates @ [p] candidates <- update candidates candidates
The new update
function calls the check
function. If wasDiscarded
is true, it calls itself recursively with the new candidates; otherwise, it returns the input candidates.
The update function is now a recursive function without mutable variables, but the containing hullPoints function still has a mutable candidates
variable. You'll need to apply the Recursive refactoring again:
let hullPoints points = let rec update candidates = let wasDiscarded, newCandidates = check candidates if wasDiscarded then update newCandidates else candidates let rec hpImp candidates = function | [] -> candidates | p :: tail -> let cs = candidates @ [p] let updatedCandidates = update cs hpImp updatedCandidates tail hpImp [] points
The hpImp function replaces the remaining loop, and candidates
is now a function argument instead of mutable variable.
As long as candidates
has contents, the head of the list p
is appended to candidates
, and update
is invoked. Subsequently, hpImp is invoked recursively with the updated candidates and the tail of the list.
The hullPoints function returns the value of calling hpImp with empty hull candidates, and the points
argument. This implementation has no mutable variables.
You can refactor this implementation to make it more readable, but that's not the point of this article. You can see what I then did in the GitHub repository.
(Now that I had a pure implementation, I could also port it to Haskell, which met my original goal.)
For programmers used to imperative programming, it can be difficult to apply pure Functional techniques. When you have loops that update a mutable variable in each step of the loop, you can refactor to use a recursive function; the mutable variable is replaced with a function argument. When you've tried it a couple of times, you'll get the hang of it.
Once you have a recursive function with an accumulator argument, you can often further refactor it to use a fold, instead of a recursive function.
(This post is the December 1st entry in the 2015 F# Advent Calendar.)


Panorama Home
House for sale
635 W 42nd St, New York, NY 10036
Best selection of home designs & customizing. Style, quality, functionality
$370,000
Learn More
Open space, floor-to-ceiling windows. The best appliances. A great variety of light schemes and amazing functionality of each section. Style, quality. functionality
Square feet
193.7
Floor
17
Year built
2013
Rooms
5
Parking Places
2
Technical Parameters
Open space, floor-to-ceiling windows. The best appliances. A great variety of light schemes and amazing functionality of each section.
Year built
Footage
Wall material
Conditioning
Parking places
Number of rooms
Terrace area
Security System
Floor
2013
193.7 m2
blocks
available
2
5
39.5 m2
available
17
Flooring
Floor heating
Elevator
Alarm
Security system
Bedroom
Bathroom
Children playground
Number of rooms
tile Versace
available
available
available
available
2
2
No
5
Open space, floor-to-ceiling windows. The best appliances. A great variety of light schemes and amazing functionality of each section. Style, quality. functionality
Bathroom
Bedroom
Kitchen
Living Room
All
Open space, floor-to-ceiling windows. The best appliances. A great variety of light schemes and amazing functionality of each section. Style, quality. functionality
Open space, floor-to-ceiling windows. The best appliances. A great variety of light schemes and amazing functionality of each section. Style, quality. functionality
Do you have any questions? Write to us! Today you have the opportunity to acquire the best realty of the capital.


DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.
Debugging Rails Applications
This guide introduces techniques for debugging Ruby on Rails
applications.
After reading this guide, you will know:
- The purpose of debugging.
- How to track down problems and issues in your application that your tests aren't identifying.
- The different ways of debugging.
- How to analyze the stack trace.
View Helpers for Debugging
One common task is to inspect the contents of a variable. Rails
provides three different ways to do this:
debug
to_yaml
inspect
debug
The debug
helper will return a
 tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view:
<%= debug @article %>

Title:
<%= @article.title %>

You'll see something like this:
--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
title: Rails debugging guide
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}
Title: Rails debugging guide
to_yaml
Alternatively, calling to_yaml
on any object converts it to YAML. You can pass this converted object into the simple_format
helper method to format the output. This is how debug
does its magic.
<%= simple_format @article.to_yaml %>

Title:
<%= @article.title %>

The above code will render something like this:
--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
title: Rails debugging guide
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}
Title: Rails debugging guide
inspect
Another useful method for displaying object values is inspect
, especially when working with arrays or hashes. This will print the object value as a string. For example:
<%= [1, 2, 3, 4, 5].inspect %>

Title:
<%= @article.title %>

Will render:
[1, 2, 3, 4, 5]
Title: Rails debugging guide
The Logger
It can also be useful to save information to log files at runtime. Rails maintains a separate log file for each runtime environment.
What is the Logger?
Rails makes use of the ::ActiveSupport::Logger
class to write log information. Other loggers, such as Log4r
, may also be substituted.
You can specify an alternative logger in config/application.rb
or any other environment file, for example:
config.logger = Logger.new(STDOUT)
config.logger = Log4r::Logger.new("Application Log")
Or in the Initializer
section, add any of the following
Rails.logger = Logger.new(STDOUT)
Rails.logger = Log4r::Logger.new("Application Log")
TIP: By default, each log is created under Rails.root/log/
and the log file is named after the environment in which the application is running.
Log Levels
When something is logged, it's printed into the corresponding log if the log
level of the message is equal to or higher than the configured log level. If you
want to know the current log level, you can call the Rails.logger.level
method.
The available log levels are: :debug
, :info
, :warn
, :error
, :fatal
,
and :unknown
, corresponding to the log level numbers from 0 up to 5,
respectively. To change the default log level, use
config.log_level = :warn # In any environment initializer, or
Rails.logger.level = 0 # at any time
This is useful when you want to log under development or staging without flooding your production log with unnecessary information.
TIP: The default Rails log level is debug
in all environments.
Sending Messages
To write in the current log use the logger.(debug|info|warn|error|fatal)
method from within a controller, model or mailer:
logger.debug "Person attributes hash<EMAIL_ADDRESS>logger.info "Processing the request..."
logger.fatal "Terminating application, raised unrecoverable error!!!"
Here's an example of a method instrumented with extra logging:
class ArticlesController < ApplicationController
# ...
def create
@article = Article.new(article_params)
logger.debug "New article<EMAIL_ADDRESS>logger.debug "Article should be valid: #{@article.valid?}"
if @article.save
logger.debug "The article was saved and now the user is going to be redirected..."
redirect_to @article, notice: 'Article was successfully created.'
else
render :new
end
end
# ...
private
def article_params
params.require(:article).permit(:title, :body, :published)
end
end
Here's an example of the log generated when this controller action is executed:
Started POST "/articles" for <IP_ADDRESS> at 2017-08-20 20:53:10 +0900
Processing by ArticlesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"xhuIbSBFytHCE1agHgvrlKnSVIOGD6jltW2tO+P6a/ACjQ3igjpV4OdbsZjIhC98QizWH9YdKokrqxBCJrtoqQ==", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, "commit"=>"Create Article"}
New article: {"id"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>false, "created_at"=>nil, "updated_at"=>nil}
Article should be valid: true
(0.1ms) BEGIN
SQL (0.4ms) INSERT INTO "articles" ("title", "body", "published", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["title", "Debugging Rails"], ["body", "I'm learning how to print in logs!!!"], ["published", "f"], ["created_at", "2017-08-20 11:53:10.010435"], ["updated_at", "2017-08-20 11:53:10.010435"]]
(0.3ms) COMMIT
The article was saved and now the user is going to be redirected...
Redirected to http://localhost:3000/articles/1
Completed 302 Found in 4ms (ActiveRecord: 0.8ms)
Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels to avoid filling your production logs with useless trivia.
Tagged Logging
When running multi-user, multi-account applications, it's often useful
to be able to filter the logs using some custom rules. TaggedLogging
in Active Support helps you do exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged("BCX") { logger.info "Stuff" } # Logs "[BCX] Stuff"
logger.tagged("BCX", "Jason") { logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
Impact of Logs on Performance
Logging will always have a small impact on the performance of your Rails app, particularly when logging to disk. Additionally, there are a few subtleties:
Using the :debug
level will have a greater performance penalty than :fatal
,
as a far greater number of strings are being evaluated and written to the
log output (e.g. disk).
Another potential pitfall is too many calls to Logger
in your code:
logger.debug "Person attributes hash<EMAIL_ADDRESS>In the above example, there will be a performance impact even if the allowed
output level doesn't include debug. The reason is that Ruby has to evaluate
these strings, which includes instantiating the somewhat heavy String
object
and interpolating the variables.
Therefore, it's recommended to pass blocks to the logger methods, as these are
only evaluated if the output level is the same as — or included in — the allowed level
(i.e. lazy loading). The same code rewritten would be:
logger.debug {"Person attributes hash<EMAIL_ADDRESS>The contents of the block, and therefore the string interpolation, are only evaluated if debug is enabled. This performance savings are only really noticeable with large amounts of logging, but it's a good practice to employ.
Debugging with the byebug
gem
When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion.
The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and use this guide to learn how to move from the code you have written into the underlying Rails code.
Setup
You can use the byebug
gem to set breakpoints and step through live code in
Rails. To install it, just run:
$ gem install byebug
Inside any Rails application you can then invoke the debugger by calling the
byebug
method.
Here's an example:
class PeopleController < ApplicationController
def new
byebug
@person = Person.new
end
end
The Shell
As soon as your application calls the byebug
method, the debugger will be
started in a debugger shell inside the terminal window where you launched your
application server, and you will be placed at the debugger's prompt (byebug)
.
Before the prompt, the code around the line that is about to be run will be
displayed and the current line will be marked by '=>', like this:
[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
3:
4: # GET /articles
5: # GET /articles.json
6: def index
7: byebug
#=> 8: @articles = Article.find_recent
9:
10: respond_to do |format|
11: format.html # index.html.erb
12: format.json { render json: @articles }
(byebug)
If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request.
For example:
=> Booting Puma
=> Rails 5.1.0 application starting in development on http://<IP_ADDRESS>:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.4.0 (ruby 2.3.1-p112), codename: Owl Bowl Brawl
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
Started GET "/" for <IP_ADDRESS> at 2014-04-11 13:11:48 +0200
ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by ArticlesController#index as HTML
[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
3:
4: # GET /articles
5: # GET /articles.json
6: def index
7: byebug
=> 8: @articles = Article.find_recent
9:
10: respond_to do |format|
11: format.html # index.html.erb
12: format.json { render json: @articles }
(byebug)
Now it's time to explore your application. A good place to start is
by asking the debugger for help. Type: help
(byebug) help
break -- Sets breakpoints in the source code
catch -- Handles exception catchpoints
condition -- Sets conditions on breakpoints
continue -- Runs until program ends, hits a breakpoint or reaches a line
debug -- Spawns a subdebugger
delete -- Deletes breakpoints
disable -- Disables breakpoints or displays
display -- Evaluates expressions every time the debugger stops
down -- Moves to a lower frame in the stack trace
edit -- Edits source files
enable -- Enables breakpoints or displays
finish -- Runs the program until frame returns
frame -- Moves to a frame in the call stack
help -- Helps you using byebug
history -- Shows byebug's history of commands
info -- Shows several informations about the program being debugged
interrupt -- Interrupts the program
irb -- Starts an IRB session
kill -- Sends a signal to the current process
list -- Lists lines of source code
method -- Shows methods of an object, class or module
next -- Runs one or more lines of code
pry -- Starts a Pry session
quit -- Exits byebug
restart -- Restarts the debugged program
save -- Saves current byebug session to a file
set -- Modifies byebug settings
show -- Shows byebug settings
source -- Restores a previously saved byebug session
step -- Steps into blocks or methods one or more times
thread -- Commands to manipulate threads
tracevar -- Enables tracing of a global variable
undisplay -- Stops displaying all or some expressions when program stops
untracevar -- Stops tracing a global variable
up -- Moves to a higher frame in the stack trace
var -- Shows variables and its values
where -- Displays the backtrace
(byebug)
To see the previous ten lines you should type list-
(or l-
).
(byebug) l-
[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
1 class ArticlesController < ApplicationController
2 before_action :set_article, only: [:show, :edit, :update, :destroy]
3
4 # GET /articles
5 # GET /articles.json
6 def index
7 byebug
8 @articles = Article.find_recent
9
10 respond_to do |format|
This way you can move inside the file and see the code above the line where you
added the byebug
call. Finally, to see where you are in the code again you can
type list=
(byebug) list=
[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
3:
4: # GET /articles
5: # GET /articles.json
6: def index
7: byebug
#=> 8: @articles = Article.find_recent
9:
10: respond_to do |format|
11: format.html # index.html.erb
12: format.json { render json: @articles }
(byebug)
The Context
When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack.
The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables the debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and know the place where the debugged program is stopped.
At any time you can call the backtrace
command (or its alias where
) to print
the backtrace of the application. This can be very helpful to know how you got
where you are. If you ever wondered about how you got somewhere in your code,
then backtrace
will supply the answer.
(byebug) where
--> #0 ArticlesController.index
at /PathToProject/app/controllers/articles_controller.rb:8
#1 ActionController::BasicImplicitRender.send_action(method#String, *args#Array)
at /PathToGems/actionpack-5.1.0/lib/action_controller/metal/basic_implicit_render.rb:4
#2 AbstractController::Base.process_action(action#NilClass, *args#Array)
at /PathToGems/actionpack-5.1.0/lib/abstract_controller/base.rb:181
#3 ActionController::Rendering.process_action(action, *args)
at /PathToGems/actionpack-5.1.0/lib/action_controller/metal/rendering.rb:30
#...
The current frame is marked with -->
. You can move anywhere you want in this
trace (thus changing the context) by using the frame n
command, where n is
the specified frame number. If you do that, byebug
will display your new
context.
(byebug) frame 2
[176, 185] in /PathToGems/actionpack-5.1.0/lib/abstract_controller/base.rb
176: # is the intended way to override action dispatching.
177: #
178: # Notice that the first argument is the method to be dispatched
179: # which is *not* necessarily the same as the action name.
180: def process_action(method_name, *args)
#=> 181: send_action(method_name, *args)
182: end
183:
184: # Actually call the method associated with the action. Override
185: # this method if you wish to change how action methods are called,
(byebug)
The available variables are the same as if you were running the code line by line. After all, that's what debugging is.
You can also use up [n]
and down [n]
commands in order to change the context
n frames up or down the stack respectively. n defaults to one. Up in this
case is towards higher-numbered stack frames, and down is towards lower-numbered
stack frames.
Threads
The debugger can list, stop, resume and switch between running threads by using
the thread
command (or the abbreviated th
). This command has a handful of
options:
thread
: shows the current thread.thread list
: is used to list all threads and their statuses. The current thread is marked with a plus (+) sign.thread stop n
: stops thread n.thread resume n
: resumes thread n.thread switch n
: switches the current thread context to n.
This command is very helpful when you are debugging concurrent threads and need to verify that there are no race conditions in your code.
Inspecting Variables
Any expression can be evaluated in the current context. To evaluate an expression, just type it!
This example shows how you can print the instance variables defined within the current context:
[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
3:
4: # GET /articles
5: # GET /articles.json
6: def index
7: byebug
#=> 8: @articles = Article.find_recent
9:
10: respond_to do |format|
11: format.html # index.html.erb
12: format.json { render json: @articles }
(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context,
:@_action_name, :@_response_body, :@marked_for_same_origin_verification,
:@_config]
As you may have figured out, all of the variables that you can access from a
controller are displayed. This list is dynamically updated as you execute code.
For example, run the next line using next
(you'll learn more about this
command later in this guide).
(byebug) next
[5, 14] in /PathTo/project/app/controllers/articles_controller.rb
5 # GET /articles.json
6 def index
7 byebug
8 @articles = Article.find_recent
9
#=> 10 respond_to do |format|
11 format.html # index.html.erb
12 format.json { render json: @articles }
13 end
14 end
15
(byebug)
And then ask again for the instance_variables:
(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context,
:@_action_name, :@_response_body, :@marked_for_same_origin_verification,
:@_config, :@articles]
Now @articles
is included in the instance variables, because the line defining
it was executed.
TIP: You can also step into irb mode with the command irb
(of course!).
This will start an irb session within the context you invoked it.
The var
method is the most convenient way to show variables and their values.
Let's have byebug
help us with it.
(byebug) help var
[v]ar <subcommand>
Shows variables and its values
var all -- Shows local, global and instance variables of self.
var args -- Information about arguments of the current scope
var const -- Shows constants of an object.
var global -- Shows global variables.
var instance -- Shows instance variables of self or a specific object.
var local -- Shows local variables in current scope.
This is a great way to inspect the values of the current context variables. For example, to check that we have no local variables currently defined:
(byebug) var local
(byebug)
You can also inspect for an object method this way:
(byebug) var instance Article.new
@_start_transaction_state = {}
@aggregation_cache = {}
@association_cache = {}
@attributes = #<ActiveRecord::AttributeSet:0x007fd0682a9b18 @attributes={"id"=>#<ActiveRecord::Attribute::FromDatabase:0x007fd0682a9a00 @name="id", @value_be...
@destroyed = false
@destroyed_by_association = nil
@marked_for_destruction = false
@new_record = true
@readonly = false
@transaction_state = nil
You can also use display
to start watching variables. This is a good way of
tracking the values of a variable while the execution goes on.
(byebug) display @articles
1: @articles = nil
The variables inside the displayed list will be printed with their values after
you move in the stack. To stop displaying a variable use undisplay n
where
n is the variable number (1 in the last example).
Step by Step
Now you should know where you are in the running trace and be able to print the available variables. But let's continue and move on with the application execution.
Use step
(abbreviated s
) to continue running your program until the next
logical stopping point and return control to the debugger. next
is similar to
step
, but while step
stops at the next line of code executed, doing just a
single step, next
moves to the next line without descending inside methods.
For example, consider the following situation:
Started GET "/" for <IP_ADDRESS> at 2014-04-11 13:39:23 +0200
Processing by ArticlesController#index as HTML
[1, 6] in /PathToProject/app/models/article.rb
1: class Article < ApplicationRecord
2: def self.find_recent(limit = 10)
3: byebug
#=> 4: where('created_at > ?', 1.week.ago).limit(limit)
5: end
6: end
(byebug)
If we use next
, we won't go deep inside method calls. Instead, byebug
will
go to the next line within the same context. In this case, it is the last line
of the current method, so byebug
will return to the next line of the caller
method.
(byebug) next
[4, 13] in /PathToProject/app/controllers/articles_controller.rb
4: # GET /articles
5: # GET /articles.json
6: def index
7: @articles = Article.find_recent
8:
#=> 9: respond_to do |format|
10: format.html # index.html.erb
11: format.json { render json: @articles }
12: end
13: end
(byebug)
If we use step
in the same situation, byebug
will literally go to the next
Ruby instruction to be executed -- in this case, Active Support's week
method.
(byebug) step
[49, 58] in /PathToGems/activesupport-5.1.0/lib/active_support/core_ext/numeric/time.rb
49:
50: # Returns a Duration instance matching the number of weeks provided.
51: #
52: # 2.weeks # => 14 days
53: def weeks
#=> 54: ActiveSupport::Duration.weeks(self)
55: end
56: alias :week :weeks
57:
58: # Returns a Duration instance matching the number of fortnights provided.
(byebug)
This is one of the best ways to find bugs in your code.
TIP: You can also use step n
or next n
to move forward n
steps at once.
Breakpoints
A breakpoint makes your application stop whenever a certain point in the program is reached. The debugger shell is invoked in that line.
You can add breakpoints dynamically with the command break
(or just b
).
There are 3 possible ways of adding breakpoints manually:
break n
: set breakpoint in line number n in the current source file.break file:n [if expression]
: set breakpoint in line number n inside file named file. If an expression is given it must evaluated to true to fire up the debugger.break class(.|\#)method [if expression]
: set breakpoint in method (. and # for class and instance method respectively) defined in class. The expression works the same way as with file:n.
For example, in the previous situation
[4, 13] in /PathToProject/app/controllers/articles_controller.rb
4: # GET /articles
5: # GET /articles.json
6: def index
7: @articles = Article.find_recent
8:
#=> 9: respond_to do |format|
10: format.html # index.html.erb
11: format.json { render json: @articles }
12: end
13: end
(byebug) break 11
Successfully created breakpoint with id 1
Use info breakpoints
to list breakpoints. If you supply a number, it lists
that breakpoint. Otherwise it lists all breakpoints.
(byebug) info breakpoints
Num Enb What
1 y at /PathToProject/app/controllers/articles_controller.rb:11
To delete breakpoints: use the command delete n
to remove the breakpoint
number n. If no number is specified, it deletes all breakpoints that are
currently active.
(byebug) delete 1
(byebug) info breakpoints
No breakpoints.
You can also enable or disable breakpoints:
enable breakpoints [n [m [...]]]
: allows a specific breakpoint list or all breakpoints to stop your program. This is the default state when you create a breakpoint.disable breakpoints [n [m [...]]]
: make certain (or all) breakpoints have no effect on your program.
Catching Exceptions
The command catch exception-name
(or just cat exception-name
) can be used to
intercept an exception of type exception-name when there would otherwise be no
handler for it.
To list all active catchpoints use catch
.
Resuming Execution
There are two ways to resume execution of an application that is stopped in the debugger:
continue [n]
: resumes program execution at the address where your script last stopped; any breakpoints set at that address are bypassed. The optional argumentn
allows you to specify a line number to set a one-time breakpoint which is deleted when that breakpoint is reached.finish [n]
: execute until the selected stack frame returns. If no frame number is given, the application will run until the currently selected frame returns. The currently selected frame starts out the most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been performed. If a frame number is given it will run until the specified frame returns.
Editing
Two commands allow you to open code from the debugger into an editor:
edit [file:n]
: edit file named file using the editor specified by the EDITOR environment variable. A specific line n can also be given.
Quitting
To exit the debugger, use the quit
command (abbreviated to q
). Or, type q!
to bypass the Really quit? (y/n)
prompt and exit unconditionally.
A simple quit tries to terminate all threads in effect. Therefore your server will be stopped and you will have to start it again.
Settings
byebug
has a few available options to tweak its behavior:
(byebug) help set
set <setting> <value>
Modifies byebug settings
Boolean values take "on", "off", "true", "false", "1" or "0". If you
don't specify a value, the boolean setting will be enabled. Conversely,
you can use "set no<setting>" to disable them.
You can see these environment settings with the "show" command.
List of supported settings:
autosave -- Automatically save command history record on exit
autolist -- Invoke list command on every stop
width -- Number of characters per line in byebug's output
autoirb -- Invoke IRB on every stop
basename -- <file>:<line> information after every stop uses short paths
linetrace -- Enable line execution tracing
autopry -- Invoke Pry on every stop
stack_on_error -- Display stack trace when `eval` raises an exception
fullpath -- Display full file names in backtraces
histfile -- File where cmd history is saved to. Default: ./.byebug_history
listsize -- Set number of source lines to list by default
post_mortem -- Enable/disable post-mortem mode
callstyle -- Set how you want method call parameters to be displayed
histsize -- Maximum number of commands that can be stored in byebug history
savefile -- File where settings are saved to. Default: ~/.byebug_save
TIP: You can save these settings in an .byebugrc
file in your home directory.
The debugger reads these global settings when it starts. For example:
set callstyle short
set listsize 25
Debugging with the web-console
gem
Web Console is a bit like byebug
, but it runs in the browser. In any page you
are developing, you can request a console in the context of a view or a
controller. The console would be rendered next to your HTML content.
Console
Inside any controller action or view, you can invoke the console by
calling the console
method.
For example, in a controller:
class PostsController < ApplicationController
def new
console
@post = Post.new
end
end
Or in a view:
<% console %>

New Post

This will render a console inside your view. You don't need to care about the
location of the console
call; it won't be rendered on the spot of its
invocation but next to your HTML content.
The console executes pure Ruby code: You can define and instantiate custom classes, create new models and inspect variables.
NOTE: Only one console can be rendered per request. Otherwise web-console
will raise an error on the second console
invocation.
Inspecting Variables
You can invoke instance_variables
to list all the instance variables
available in your context. If you want to list all the local variables, you can
do that with local_variables
.
Settings
config.web_console.whitelisted_ips
: Authorized list of IPv4 or IPv6 addresses and networks (defaults:<IP_ADDRESS>/8, ::1
).config.web_console.whiny_requests
: Log a message when a console rendering is prevented (defaults:true
).
Since web-console
evaluates plain Ruby code remotely on the server, don't try
to use it in production.
Debugging Memory Leaks
A Ruby application (on Rails or not), can leak memory — either in the Ruby code or at the C code level.
In this section, you will learn how to find and fix such leaks by using tool such as Valgrind.
Valgrind
Valgrind is an application for detecting C-based memory leaks and race conditions.
There are Valgrind tools that can automatically detect many memory management
and threading bugs, and profile your programs in detail. For example, if a C
extension in the interpreter calls malloc()
but doesn't properly call
free()
, this memory won't be available until the app terminates.
For further information on how to install Valgrind and use with Ruby, refer to Valgrind and Ruby by Evan Weaver.
Plugins for Debugging
There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging:
- Footnotes Every Rails page has footnotes that give request information and link back to your source via TextMate.
- Query Trace Adds query origin tracing to your logs.
- Query Reviewer This Rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed.
- Exception Notifier Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application.
- Better Errors Replaces the standard Rails error page with a new one containing more contextual information, like source code and variable inspection.
- RailsPanel Chrome extension for Rails development that will end your tailing of development.log. Have all information about your Rails app requests in the browser — in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more.
- Pry An IRB alternative and runtime developer console.


{{ partial "header.html" . }} Sorry, but the page you were trying to view does not exist. {{ partial "footer.html" }}


We cannot disable neccessary cookies as these are required to remember your preferences for non-essential cookies.
List of Necessary Cookies:
| Cookie Name | Usage | {% for cookie in site.google_analytics.cookies.necessary %}
|---|---|
| {{cookie.name}} | {{cookie.description | markdownify}} |
Toggle this switch to enabled the collection of anonymous analytics data that helps us improve our site. Toggle this switch to disable the collection of anonymous analytics data that helps us improve our site.
List of Analytics Cookies:
| Cookie Name | Usage | {% for cookie in site.google_analytics.cookies.marketing %}
|---|---|
| {{cookie.name}} | {{cookie.description | markdownify}} |


Follow these steps to set up email notifications:
- In the live deployment wizard, enable at least one of the following:
- Order Events: Be notified when order events occur.
- Insights: Be notified when insights are emitted.
- Click Email.
- Enter an email address.
- Enter a subject.
- Click Add.
If you want to add more email notifications, click Add Notification and then continue from step 2.


Interswitch Documentation Portal
This portal hold documentation for the current sets of Interswitch products.
For now, we’re launching a beta test of the current draft.
Documentation Portal Directory
| Product Name | Documentation Links |
|---|---|
| Interswitch Payment Gateway | Quickstart Guide Full Docs |


2013-11-10. To T.
Leuven, 11th of October of 2013.
Greetings T.,
How is everything going? I hope that well and without too many frights.
I attach a letter explaining some of the thoughts I have had in my mind during these months.
I mentioned my intention to call you during my stay in Barcelona but, as I explain in the letter, I preferred to use writing as a means of communication.
My trip to Barcelona has left me in a rather strange state of longing, melancholy, and loss of comfort… I do not know if these beautiful three days I have spent there have been beneficial or harmful.
Greetings and thanks,
Carlos
Leuven, 10th of November of 2013.
Greetings T.,
I hope everything’s going well. Remember that the time we have is to try to live as best we can. Knowing our limits and trying not to exceed them.
The first thing is to apologize. You’re going to read something that, I suppose, will cause you discomfort and disgust. It’ll probably influence you emotionally and intellectually. My ideas, let’s hope, can’t influence you, but I sense my mood will affect you. Empathy is within our human condition, as our compassion for our neighbor, which many of us forget and is left only in exceptionality. Like yours. I’m so sorry that you have to read this letter. I’m really sorry. But the situation is becoming more complicated at an unexpected and alarmingly speed. I feel pushed to ask you for help; to take on the role of psychologist and priest-confessor, (ironic considering how much you don’t like priests…).
T., please take your time to read and answer this letter. My problems are not in a hurry. I know your advice will always be of incredible quality, so it will be worth the wait. I also don’t want my problems to get in the way, more than they already do, and they’re going to do it, in your pace of life, in your priority issues, much more important than mine.
Regarding the communicative medium, writing, I have preferred to use this letter because of the ability to reflect and reread that allows us both. In this way, we can present our organized ideas and retrieve the exact information if necessary. I don’t know if a phone conversation or a face-to-face talk would have a greater short-term impact, but the memory is very fallible. This is not, for me, a transient problem that can be solved with a coffee. It is settling into my soul, in my being, and in my mind, unconsciously and insidiously. As far as I believe, it is necessary to root it out and make future revisions so that it doesn’t regrow. Still, at Christmas (Winter Festivities) we’ll have a coffee and catch up.
I will use the metaphor to help express some ideas, as my intellectual resources are markedly limited. But I would appreciate advice without and abusive use of these, unlike I have done. I would like to see clearly the way to be followed and avoid possible misinterpretations. Still, the beauty of your words and the power of the images that your metaphors can create will be a great encouragement in my disoriented search for a right path.
Probably the source of the problem, or a factor in its appearance, was the reading of a book: Pulp by Charles Bukowski (Bukowski 2002). I did it as a language exercise, to improve my English, but its message has insidiously meddled in my thinking. Unconsciously. That hatred, that banality, the death, the impoverished quality of the human being of the twenty-first century, the disorder, the violence…
For similar reasons, I’m no longer sure I want to read poetry anymore. The idea of death appears repeatedly and spontaneously in my thinking. It’s uncontrollable. Sometimes it blocks me, sometimes it depresses me, others it causes me frustration or anger. The multiplicity of interpretations offered by poetry, and my pre-activated mind, make me detect constant references to death. I can’t get rid of the idea. It’s lodged in my brain.
This thought has led me to value the importance of life, a human being. But wrongly, I hope, I only see banality, hopelessness, a continuous wandering towards an inevitable destiny. The nonsense. This society is sinking at an alarming rate. It’s sinking unstoppably. We’re heading for the abyss. I’m losing my faith, my hope.
Besides, something dangerous and new is appearing in my way of being. I’m starting to feel hate. A deep hatred towards mediocrity. A hatred that begins to take root in my heart, in my soul. It catches me in social situations, at mass parties filled with crowds without any purpose. Anesthetizing our senses, with noise, low light, and alcohol. I admit my enormous guilt since many of these thoughts of hatred manifest more intensely if I find myself under the influence of drinks. My instincts are revealed, my hate manifests.
I have not had any physical fights, for I have never been a violent person, by ability and principles. I consider that intellectual capacity and aggression are inversely related. The higher the amount of one, the less of the other. Only when we feel frustrated and scarce in resources does aggression and physical violence appears.
Despite the absence of physical violence, my words and thoughts portray powerful hate. My intentions are, indeed, aggressive and violent. I’m not able to start a conversation that doesn’t lead me to an argument or a clash. I start incriminating the other person, judging her, considering her mediocre, inferior, banal. Things get worse if I try to advise her, showing my point of view, instruct her, even. Who am I to teach? In those moments the confrontation is absolute. The other person gets tired and usually runs away. I still have hate-laden bullets ready to fire.
F. doesn’t help. I think part of this mentality is generated by him, by his verses, his ideas. It’s made me hopeless. Another friend of mine, C., who has a very powerful mind, has also led me down a path that I am not sure is the right one.
Both C. and F. have opened my eyes, they have taught me the world of shadows in which I was living. They have pushed me out of the cave. I’ll always be indebted to them. But, I think, they’re not treating people who continue inside the cavern properly, living out of shadows. They despise them, they abandon them, they repudiate them.
I want to believe that the people left in the cavern, in the banality, should be helped. But the empire of Facebook, Twitter, the Internet, and WhatsApp controls them. Their ideas are now within me. I’m afraid of this situation, it makes me nauseous and disgusted. I’m repulsed by people who are imbued, drowned. They prefer to continue to be fed with vacuous dreams, for not facing the truth.
In the direction towards the exit, I hope, halfway through, I start to see the light. It blinds me. It aches and hurts me. Sometimes I wonder if it’s better to keep living in the cave, comfortably, quietly and futilely. Outside the wind intensifies, and I’m not used either to nudity, transparency, nor loneliness.
I know I’m doing something wrong. I’m misinterpreting something. Or it may be that I am fighting for a lost cause, and I should embark alone on my personal goals, (An ethical force blocks me). Or should I go back to the cavern, to the blindness and join them? (Having approached the light, the truth hampers this option). I feel weak. I need support all the time. I have powerful social needs that drag me to stay inside, living out of shadows. I’m pretty lost and disoriented. I’m thinking of going to therapy to be fooled into fooling myself. Getting into the warmth of the cavern, to see the shadows and wait for death seems to me as the most feasible option.
I’m loosing out on the experience of studying in a foreign country with all these thoughts in my head bombarding and bothering me. I can’t taste the beauty of diversity. I forget the power of human warmth. A few weeks ago, we had a class on the relationship between happiness and social relationships; the sadness of loneliness, of the lonely one. My nature is social, very social. I used to be happy. I’m aimless now.
I think only a person as exceptional as you can give me some guidance and good advice. I need some information to make the lest incorrect decision as possible. To get out of the trap I’ve built myself.
You can’t imagine, T., how lucky I am to have met you. How much I admire you and the tremendous respect I have for you. Death assaults me, all the time. Thinking about your age overwhelms me with anguish. My eyes are flooding. Thinking about the unconditionality and premeditation of our destiny is very hard. I don’t know what I’m going to do when what’s going to happen actually happens, with each and every one of us.
I am surprised that the oldest of my friends retains hope and believes in the future, on the change. However, youth is devastated, hopeless and lost, like me (21 years), C. (24) or F. (33).
Could we pick up the towel we threw away?
I’m sorry again for reading my problems and stealing your time.
Eternally grateful.
Carlos
References
Bukowski, Charles. 2002. Pulp. Txalaparta.