tag:blogger.com,1999:blog-115374042024-03-12T21:34:08.069-04:00Ponderings Of An Itinerate ProgrammerMike Lambert's musings on programming, computer graphics, software reviews, writing and life.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.comBlogger156125tag:blogger.com,1999:blog-11537404.post-58526193607155019962015-04-14T15:13:00.002-04:002015-04-14T15:13:15.526-04:00DSLs With Ruby TreetopI have been playing with Ruby quite a bit lately. One of the things I have been working on is a simple data query language to interface with our back-end databases. At my job this must work on an old UniData database (running on a HP-UX 10.2 box) and SQL Server. I wanted a language that looks much like the following:<br />
<br />
<b><span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data Query Language Syntax Help</span></b><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><i>General Commands</i>:</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Comamand History: list [NumberOfCommandsToReturn] commands</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Exit Program: exit</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Reparse The Grammar: reparse grammar</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<i><span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Macro Commands:</span></i><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">DQL Macros Are A Series Of Commands That You Want Executed</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Begin Macro: begin macro</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Clear All Macros: clear all macros</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Delete All Macros: delete all macros</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Delete Macro: delete macro ["MacroName"] </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Edit Macro Save File: edit macros [from Filename] in Notepad</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">End Macro: end macro [into "MacroName"]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">List Current Macros: list macros</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">List Macro Commands: list macro "MacroName" commands</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">List Macro Files: list macro files</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Load Macro File: load macros [from FileName] </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Run The Macro: run macro ["MacroName"]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Save Macros To File: save macros [into FileName]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<i><span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Database Object Queries:</span></i><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">List Table Names: list [avante| dw | datawarehouse] tables [into ResultSetName]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">List Table Columns: list [avante| dw | datawarehouse] table "ITMMST" columns [into ResultSetName]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<i><span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Database Queries:</span></i><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Run Query: run [avante| dw | datawarehouse] query "LIST ITMMST PART.NBR DESCRIPTION" [INTO ResultSetName]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<i><span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Result Set Commands:</span></i><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Copy Results: copy result set SourceResultSetName to DestinationResultSetName</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Delete Results: delete ResultSetName</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Filter Results: filter ResultSetName by "FilterString1" [and "FilterString2" and ...]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">List Result Sets: list result sets</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Match Results: match ResultSetName on "MatchString1" [and "MatchString2" and ...]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Output To Excel: output ResultSetName to excel file "ExcelFileName"</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Revert Results: revert ResultSetName</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Show Results: show [NumberOfLinesToReturn] [ResultSetName]</span><br />
<br />
I wanted the ability to run queries on both of our main enterprise databases and do simple filtering/matching on the results (called Result Sets in this system). I also wanted the ability to create a series of commands and save them in a "macro" that I could run later. I wanted to be able to save these macros to a file to be run later as well. In this way I could build up libraries of commonly used commands. Finally, I wanted to be able to call up lists of database objects I am always searching for (table names and columns).<br />
<br />
I knew that the "easiest" way to achieve this would be to create a DSL that encapsulated all these commands. You could do it by manually parsing regexes but that would be no fun. Luckily Ruby has many tools to do DSLs. I chose <a href="https://github.com/nathansobo/treetop" target="_blank">Treetop</a> to create my new language. Treetop is a great tool. The only real negative to using it is the complete lack of good documentation. Hopefully this post may help someone avoid bashing their head into a wall like I did.<br />
<br />
In Treetop you build you language using strings and regular expressions. Let's take an example from the language we created above:<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;">List Table Names: list [avante| dw | datawarehouse] tables [into ResultSetName]</span><br />
<br />
I represented this in a rule that looks like this:<br />
<br />
<span style="font-size: x-small;">rule db_command_list_tables</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>'list' space database_type space database_object space? optional_resultset:into_resultset?</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>def evaluation_code</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>eval_code = ""</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>my_result_set_name = 'default'</span><br />
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-size: x-small;"> </span></span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span># Write Code To Get The Table Names</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>case database_type.text_value</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>when 'avante'</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if database_object.text_value == 'tables'</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>eval_code = "result_set = avante_db.get_table_names"</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>end </span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>when 'dw','datawarehouse'</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if database_object.text_value == 'tables'</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>eval_code = "result_set = datawarehouse_db.get_table_names"</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>end <span class="Apple-tab-span" style="white-space: pre;"> </span></span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>end</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span># Write Code To Handle Optional Result Set</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if defined?(optional_resultset.result_set_name)</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>my_result_set_name = optional_resultset.result_set_name.text_value</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>end </span><br />
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-size: x-small;"> </span></span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span># Add ResultSet To Result Set Dictionary</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>eval_code = eval_code + "; result_set_dict.add_existing_result_set(result_set,'#{my_result_set_name}')"</span><br />
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-size: x-small;"> </span></span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span># Probably Want To List Result Set By Default</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if my_result_set_name == 'default'</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>eval_code = eval_code + "; puts result_set.value; puts \"\\n\" + Rainbow(\"Results Stored In Result Set: default\").green"</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>end </span><br />
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-size: x-small;"> </span></span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>return eval_code</span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>end </span><br />
<span style="font-size: x-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span><br />
<span style="font-size: x-small;">end </span><br />
<span style="font-size: x-small;"><br /></span>
<span style="font-size: x-small;">rule database_type</span><br />
<span style="font-size: x-small;"> 'avante' / 'dw' / 'datawarehouse'</span><br />
<span style="font-size: x-small;"></span><br />
<span style="font-size: x-small;">end</span><br />
<span style="font-size: x-small;"><br /></span>
<span style="font-size: x-small;">rule database_object</span><br />
<span style="font-size: x-small;"> 'table' [s]* / 'query' / 'data' / 'info'</span><br />
<span style="font-size: x-small;"></span><br />
<span style="font-size: x-small;">end</span><br />
<span style="font-size: x-small;"><br /></span>
<span style="font-size: x-small;">rule into_resultset</span><br />
<span style="font-size: x-small;"> 'into' space result_set_name</span><br />
<span style="font-size: x-small;"></span><br />
<span style="font-size: x-small;">end</span><br />
<span style="font-size: x-small;"><br /></span>
<span style="font-size: x-small;">rule space</span><br />
<span style="font-size: x-small;"> [\s]+</span><br />
<span style="font-size: x-small;"></span><br />
<span style="font-size: x-small;">end </span><br />
<br />
The db_command_list_tables rule builds on the database_type, database_object, into_resultset and space rules to create a complete command. One important thing to note is that when you make something optional (by appending a question mark to the end of it) it no longer is able to be referenced directly. An example of this is the into_resultset? part of the db_command_list_tables rule. We added a question mark to the end of it. Notice that I prepended "optional_resultset:" to into_resultset? This enables me to later use this code to look up its value:<br />
<br />
<span style="font-size: x-small;"># Write Code To Handle Optional Result Set</span><br />
<span style="font-size: x-small;">if defined?(optional_resultset.result_set_name)</span><br />
<span style="font-size: x-small;"> my_result_set_name = optional_resultset.result_set_name.text_value</span><br />
<span style="font-size: x-small;">end </span><br />
<br />
It would make sense if your were able to use a construct like into_resultset.result_set_name.text_value to reference the result set name. This will fail. Instead you must prepend optional_resultset: to into_resultset to look into the into_resultset rule. Why? I have no idea and it took me forever to figure this out. I re-iterate ... it is completely necessary to prepend an identifier to any referenced rule you make optional (with a question mark). This is nowhere (that I could find) in the "official" Treetop documentation and it will trip you up.<br />
<br />
The final item of interest is the:<br />
<br />
<span style="font-size: x-small;">{</span><br />
<span style="font-size: x-small;"> def evaluation_code</span><br />
<div>
....</div>
<div>
....</div>
<div>
}</div>
<br />
construct inside the rule. This enables you to use Ruby code to process the text data from rules that match your input. How meta ;-). What this enables you to do is create code that would be evaluated when a rule matches certain input. To use these rules you have to compile the Treetop rules into a file and load them into a parser. I used a parser class to accomplish this:<br />
<br />
<span style="font-size: x-small;">class DQL_Parser</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def initialize(grammar_path)</span><br />
<span style="font-size: x-small;"> @path_to_grammar = grammar_path</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> Treetop.load(grammar_path)</span><br />
<span style="font-size: x-small;"> @parser = DQLParser.new </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def reload_grammar()</span><br />
<span style="font-size: x-small;"> puts Rainbow("Reparsing Grammar").green</span><br />
<span style="font-size: x-small;"> Treetop.load(@path_to_grammar)</span><br />
<span style="font-size: x-small;"> @parser = DQLParser.new</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def parse(command)</span><br />
<span style="font-size: x-small;"> tree = nil</span><br />
<span style="font-size: x-small;"> if command !~/^\s*$/</span><br />
<span style="font-size: x-small;"> # Pass the data over to the parser instance</span><br />
<span style="font-size: x-small;"> tree = @parser.parse(command)</span><br />
<span style="font-size: x-small;"> # If the AST is nil then there was an error during parsing</span><br />
<span style="font-size: x-small;"> # we need to report a simple error message to help the user</span><br />
<span style="font-size: x-small;"> if(tree.nil?)</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Cannot parse \"#{command}\". Error is at offset: #{@parser.index}").red</span><br />
<span style="font-size: x-small;"> puts Rainbow((' ' * (@parser.index + 21)) + '^').red</span><br />
<span style="font-size: x-small;"> puts Rainbow((' ' * (@parser.index + 21)) + '|').red</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> return tree</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"></span><br />
<span style="font-size: x-small;">end</span><br />
<span style="font-size: x-small;"><br /></span>
<span style="font-size: x-small;">$parser = DQL_Parser.new($base_path + '\Data_Query_Language.treetop')</span><br />
<span style="font-size: x-small;">.... some code to set up a REPL</span><br />
<span style="font-size: x-small;">parse_tree = $parser.parse(command)</span><br />
<span style="font-size: x-small;">if !parse_tree.nil?</span><br />
<span style="font-size: x-small;"> if parse_tree.evaluation_code !~ /^ERROR\:/i</span><br />
<span style="font-size: x-small;"> if parse_tree.evaluation_code != ''</span><br />
<span style="font-size: x-small;"> eval (parse_tree.evaluation_code)</span><br />
<span style="font-size: x-small;"> ....</span><br />
<br />
These commands create a parser using all of the commands contained in the Data_Query_Langague.treetop file. Then I parse the commands a user enters with $parser.parse. If the parser tree is not null and if it returns something from the evaluation_code ruby code then we have a winner. We now take the evaluation code returned from the parser and evaluate it with Ruby's eval method. If we put this code in a loop then we have a REPL that we can use to run a series of DQL (Data Query Language) commands.<br />
<br />
The key thing to remember is that the evaluation code is run in the context of the Ruby program that is creating the parser instance. By using the Ruby readline and rainbow gems you can create such convenience features as auto command completion and colored error/information output.<br />
<br />
The only things left for me to do were to create the classes that would enable me to interface with my databases and easily manipulate result sets that they returned. This was accomplished using the DBI, net/telnet, net/ftp and good old fashioned Ruby classes.<br />
<br />
My database classes had the following structure:<br />
<br />
<span style="font-size: x-small;">class DBInfo</span><br />
<span style="font-size: x-small;"> def initialize</span><br />
<span style="font-size: x-small;"><br /></span>
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def log_in</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def run_query</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def get_table_names</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def get_table_cols</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def close</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;">end</span><br />
<br />
My SQL Server and UniData classes inherit from DBInfo. Your classes would depend on the type of databases you were interfacing with. <br />
<br />
The more interesting classes are the result set classes (excuse the mess, I haven't had time to properly re-factor these):<br />
<br />
<span style="font-size: x-small;">class ResultSet</span><br />
<span style="font-size: x-small;"> attr_reader :query, :query_type, :column_chars, :name</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def initialize(orig_result_temp_file, query, query_type, column_chars = '|', name = 'default')</span><br />
<span style="font-size: x-small;"> @orig_result_file = orig_result_temp_file</span><br />
<span style="font-size: x-small;"> @curr_result_file = @orig_result_file </span><br />
<span style="font-size: x-small;"> @query = query</span><br />
<span style="font-size: x-small;"> @query_type = query_type</span><br />
<span style="font-size: x-small;"> @column_chars = column_chars</span><br />
<span style="font-size: x-small;"> @name = name</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;">def value</span><br />
<span style="font-size: x-small;"> # Read Results From (Possibly Filtered) Temporary Result Files</span><br />
<span style="font-size: x-small;"> if File.exist?(@curr_result_file.path)</span><br />
<span style="font-size: x-small;"> return File.read(@curr_result_file.path)</span><br />
<span style="font-size: x-small;"> else</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result File \"#{@curr_result_file.path}\"Does Not Exist").red</span><br />
<span style="font-size: x-small;"> return "" </span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def original_value</span><br />
<span style="font-size: x-small;"> # Return Original Results (Before Filters And Matches)</span><br />
<span style="font-size: x-small;"> if File.exist?(@orig_result_file.path)</span><br />
<span style="font-size: x-small;"> return File.read(@orig_result_file.path)</span><br />
<span style="font-size: x-small;"> else</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result File \"#{@orig_result_file.path}\"Does Not Exist").red</span><br />
<span style="font-size: x-small;"> return "" </span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def print_line_in_columns(lines_to_display = 0)</span><br />
<span style="font-size: x-small;"> line_counter = 1</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> if File.exist?(@curr_result_file.path)</span><br />
<span style="font-size: x-small;"> # Even At One Byte Per Line It Will Never Have More Than This Many Lines</span><br />
<span style="font-size: x-small;"> if lines_to_display <= 0 then lines_to_display = File.size(@curr_result_file.path) end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> File.readlines(@curr_result_file.path).each do |line|</span><br />
<span style="font-size: x-small;"> break if line_counter > lines_to_display</span><br />
<span style="font-size: x-small;"> puts line.gsub(/#{@column_chars}/n,"\t")</span><br />
<span style="font-size: x-small;"> line_counter = line_counter + 1</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> else</span><br />
<span style="font-size: x-small;"> puts Rainbow("Results File \"#{@curr_result_file.path}\" Does Not Exist").red</span><br />
<span style="font-size: x-small;"> exit</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def write_to_excel(filename)</span><br />
<span style="font-size: x-small;"> # Write This Result Set To Excel (xlsx)</span><br />
<span style="font-size: x-small;"> excel_filename = "#{$base_path}/DQL/Excel/#{filename}.xlsx"</span><br />
<span style="font-size: x-small;"> p = Axlsx::Package.new</span><br />
<span style="font-size: x-small;"> p.use_autowidth = true</span><br />
<span style="font-size: x-small;"> wb = p.workbook</span><br />
<span style="font-size: x-small;"> wb.add_worksheet(:name => "Results") do |sheet|</span><br />
<span style="font-size: x-small;"> File.open(@curr_result_file.path,"r").each_line do |line|</span><br />
<span style="font-size: x-small;"> sheet.add_row(line.split(/#{@column_chars}/n))</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> p.serialize(excel_filename)</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def revert_to_original_value</span><br />
<span style="font-size: x-small;"> # Revert Result Set To Original Value (Before Filters And Matches)</span><br />
<span style="font-size: x-small;"> if !@orig_result_file == @curr_result_file</span><br />
<span style="font-size: x-small;"> @curr_result_file.close</span><br />
<span style="font-size: x-small;"> @curr_result_file.unlink</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> @curr_result_file = @orig_result_file</span><br />
<span style="font-size: x-small;"> puts Rainbow("Result Set \"" + name + "\" Reverted To Original Data").green</span><br />
<span style="font-size: x-small;"> self</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def filter_out(filter_query = "")</span><br />
<span style="font-size: x-small;"> # Filter Things Out Of The Result Set</span><br />
<span style="font-size: x-small;"> if @orig_result_file == @curr_result_file</span><br />
<span style="font-size: x-small;"> if not(File.exist?(@orig_result_file.path))</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result File \"#{@orig_result_file.path}\"Does Not Exist").red</span><br />
<span style="font-size: x-small;"> exit </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> source = @orig_result_file</span><br />
<span style="font-size: x-small;"> destination = Tempfile.new('OpenODBCECL_Dest')</span><br />
<span style="font-size: x-small;"> else</span><br />
<span style="font-size: x-small;"> if not(File.exist?(@curr_result_file.path))</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result File \"#{@curr_result_file.path}\"Does Not Exist").red</span><br />
<span style="font-size: x-small;"> exit </span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> source = @curr_result_file</span><br />
<span style="font-size: x-small;"> destination = Tempfile.new('OpenODBCECL_Dest')</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> # Create New Result File Base On The Matching Query</span><br />
<span style="font-size: x-small;"> output = File.open( destination.path,"a" ) </span><br />
<span style="font-size: x-small;"> File.readlines(source.path).each do |line|</span><br />
<span style="font-size: x-small;"> if line !~/#{filter_query}/i</span><br />
<span style="font-size: x-small;"> output << line</span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> output.close</span><br />
<span style="font-size: x-small;"> if @orig_result_file != @curr_result_file</span><br />
<span style="font-size: x-small;"> # Remove Intermediate Result File If It Is Not Equal To Original Results</span><br />
<span style="font-size: x-small;"> @curr_result_file.close</span><br />
<span style="font-size: x-small;"> @curr_result_file.unlink</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> # Set Current Result File To New Destination</span><br />
<span style="font-size: x-small;"> @curr_result_file = destination</span><br />
<span style="font-size: x-small;"> # Give Status</span><br />
<span style="font-size: x-small;"> puts Rainbow("Filter \"#{filter_query}\" Applied To Result Set \"" + name + "\"").green</span><br />
<span style="font-size: x-small;"> # Return self So You Can Chain Filters</span><br />
<span style="font-size: x-small;"> self </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def matching(matching_query = "")</span><br />
<span style="font-size: x-small;"> # Return Items Matching matching_query</span><br />
<span style="font-size: x-small;"> if @orig_result_file == @curr_result_file</span><br />
<span style="font-size: x-small;"> if not(File.exist?(@orig_result_file.path))</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result File \"#{@orig_result_file.path}\"Does Not Exist").red</span><br />
<span style="font-size: x-small;"> exit </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> source = @orig_result_file</span><br />
<span style="font-size: x-small;"> destination = Tempfile.new('OpenODBCECL_Dest')</span><br />
<span style="font-size: x-small;"> else</span><br />
<span style="font-size: x-small;"> if not(File.exist?(@curr_result_file.path))</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result File \"#{@curr_result_file.path}\"Does Not Exist").red</span><br />
<span style="font-size: x-small;"> exit </span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> source = @curr_result_file</span><br />
<span style="font-size: x-small;"> destination = Tempfile.new('OpenODBCECL_Dest')</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> # Create New Result File Base On The Matching Query</span><br />
<span style="font-size: x-small;"> output = File.open( destination.path,"a" ) </span><br />
<span style="font-size: x-small;"> File.readlines(source.path).each do |line|</span><br />
<span style="font-size: x-small;"> if line =~/#{matching_query}/i</span><br />
<span style="font-size: x-small;"> output << line</span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> output.close</span><br />
<span style="font-size: x-small;"> if @orig_result_file != @curr_result_file</span><br />
<span style="font-size: x-small;"> # Remove Intermediate Result File If It Is Not Equal To Original Results</span><br />
<span style="font-size: x-small;"> @curr_result_file.close</span><br />
<span style="font-size: x-small;"> @curr_result_file.unlink</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> # Set Current Result File To New Destination</span><br />
<span style="font-size: x-small;"> @curr_result_file = destination</span><br />
<span style="font-size: x-small;"> # Give Status</span><br />
<span style="font-size: x-small;"> puts Rainbow("Match Query \"#{matching_query}\" Applied To Result Set \"" + name + "\"").green</span><br />
<span style="font-size: x-small;"> # Return self So You Can Chain Filters</span><br />
<span style="font-size: x-small;"> self</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def clean_up_result_files</span><br />
<span style="font-size: x-small;"> # Clean Up Result Files</span><br />
<span style="font-size: x-small;"> if !@orig_result_file.nil?</span><br />
<span style="font-size: x-small;"> if File.exist?(@orig_result_file.path)</span><br />
<span style="font-size: x-small;"> @orig_result_file.close</span><br />
<span style="font-size: x-small;"> @orig_result_file.unlink</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> # @curr_result_file.path.nil? Necessary If @orig_result_file = @curr_result_file</span><br />
<span style="font-size: x-small;"> if !@curr_result_file.nil? and !@curr_result_file.path.nil?</span><br />
<span style="font-size: x-small;"> if File.exist?(@curr_result_file.path)</span><br />
<span style="font-size: x-small;"> @curr_result_file.close</span><br />
<span style="font-size: x-small;"> @curr_result_file.unlink</span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def close</span><br />
<span style="font-size: x-small;"> clean_up_result_files</span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;">end</span><br />
<span style="font-size: x-small;"><br /></span>
<span style="font-size: x-small;">class ResultSetDictionary</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def initialize</span><br />
<span style="font-size: x-small;"> @result_set_hash = Hash.new</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def result_set_exists?(key)</span><br />
<span style="font-size: x-small;"> if @result_set_hash[key].nil?</span><br />
<span style="font-size: x-small;"> return false</span><br />
<span style="font-size: x-small;"> else</span><br />
<span style="font-size: x-small;"> return true </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def list_result_sets()</span><br />
<span style="font-size: x-small;"> puts "Existing Result Sets:"</span><br />
<span style="font-size: x-small;"> @result_set_hash.keys.each {|key| puts " #{key}"}</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def get_result_set(result_set_name)</span><br />
<span style="font-size: x-small;"> #puts @result_set_hash[result_set_name].value</span><br />
<span style="font-size: x-small;"> if result_set_exists?(result_set_name)</span><br />
<span style="font-size: x-small;"> return @result_set_hash[result_set_name]</span><br />
<span style="font-size: x-small;"> else</span><br />
<span style="font-size: x-small;"> puts "Result Set \"#{result_set_name}\" Does Not Exist"</span><br />
<span style="font-size: x-small;"> end </span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def delete_result_set(result_set_name)</span><br />
<span style="font-size: x-small;"> if result_set_exists?(result_set_name)</span><br />
<span style="font-size: x-small;"> get_result_set(result_set_name).close</span><br />
<span style="font-size: x-small;"> @result_set_hash.delete(result_set_name)</span><br />
<span style="font-size: x-small;"> puts Rainbow("Result Set \"#{result_set_name}\" Is Deleted.").green</span><br />
<span style="font-size: x-small;"> else</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result Set \"#{result_set_name}\" Does Not Exist.").red</span><br />
<span style="font-size: x-small;"> return</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def add_existing_result_set(existing_result_set, result_set_name = 'default')</span><br />
<span style="font-size: x-small;"> # Given A ResultSet Add It To My Dictionary</span><br />
<span style="font-size: x-small;"> if result_set_exists?(result_set_name)</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result Set #{result_set_name} For Database Type #{existing_result_set.query_type} All Ready Exists. Deleting Existing Key").red</span><br />
<span style="font-size: x-small;"> # return nil</span><br />
<span style="font-size: x-small;"> delete_result_set(result_set_name)</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> @result_set_hash.store(result_set_name,existing_result_set)</span><br />
<span style="font-size: x-small;"> return @result_set_hash[result_set_name]</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def add_new_result_set(orig_result_file, result_set_name = 'default', database_type = '', column_chars = '|')</span><br />
<span style="font-size: x-small;"> # Given Proper Items Add A New Result Set To The Result Set Dictionary</span><br />
<span style="font-size: x-small;"> if result_set_exists?(result_set_name)</span><br />
<span style="font-size: x-small;"> puts Rainbow("ERROR: Result Set #{result_set_name} For Database Type #{database_type} All Ready Exists. Deleting Existing Key").color(255,102,0)</span><br />
<span style="font-size: x-small;"> #return nil</span><br />
<span style="font-size: x-small;"> delete_result_set(result_set_name)</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> @result_set_hash.store(result_set_name,ResultSet.new(orig_result_file, query, database_type, result_set_name))</span><br />
<span style="font-size: x-small;"> return @result_set_hash[result_set_name]</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;"> </span><br />
<span style="font-size: x-small;"> def cleanup</span><br />
<span style="font-size: x-small;"> puts "Cleaning Up Result Sets"</span><br />
<span style="font-size: x-small;"> # Close All Result Sets</span><br />
<span style="font-size: x-small;"> @result_set_hash.keys.each {|key| puts " Result Set \"#{key}\" Cleaned"; @result_set_hash[key].close}</span><br />
<span style="font-size: x-small;"> puts "Result Sets Cleaned"</span><br />
<span style="font-size: x-small;"> end</span><br />
<span style="font-size: x-small;">end</span><br />
<br />
Basically these two classes let me reference result sets by name in my evaluation_code Treetop definitions. Result sets are basically just temporary text files with my results in them (one record per line). Filtering and matching are done by reading these files on line at a time and dumping appropriate lines into new temporary result set files. These temporary files are cleaned up on program exit or when they are deleted.<br />
<br />
This is the process I used to create my DQL meta programming language. I was able to create the whole language using 634 lines of Treetop definitions and a 734 line program that contains all the database, result set and REPL logic.<br />
<br />
Overall I am impressed with Treetop's capabilities. More documentation would be nice but answers can be found on the Internet (with a lot of effort). I'm hoping this will become another resource for people curious about this outstanding library.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-34259807525809848342015-03-06T15:54:00.002-05:002015-03-07T08:42:53.264-05:00I Always Think Of The Best Things To Say After I LeaveLast Friday I met with the folks from Pomiet Software here in Miamisburg, OH. I was not looking to interview. This opportunity was dropped in my Gmail account from a recruiter I had never talked with before. The Pomiet group is a great group of people and are doing wonderful things it would have been fun to be a part of.<br />
<br />
Part of the interview process was a coding challenge a few days before the interview. Here is the challenge:<br />
<br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;">Hi and welcome to the StoreFront. As you know, we are a small store with a prime location in a prominent city ran by a friendly store manager named Sarah. We also buy and sell only the finest elements. Unfortunately, our items are constantly losing shelf value as they approach their sell by date. We have a system in place that updates our inventory for us. It was developed by a no-nonsense guy named Larry, who has moved on to new adventures. Your task is to add the new feature to our system so that we can begin selling a new category of items. First an introduction to our system:</span><br />
<span style="color: #cccccc;"><br />
<span style="font-family: Courier New, Courier, monospace;"> - All items have a ShelfLife value which denotes the number of days we have to sell the item</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> - All items have a Worth value which denotes how valuable the item is</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> - At the end of each day our system lowers both values for every item</span><br />
<span style="color: #cccccc;"><br />
<span style="font-family: Courier New, Courier, monospace;">Pretty simple, right? Well this is where it gets interesting:</span></span><br />
<span style="color: #cccccc;"><br />
<span style="font-family: Courier New, Courier, monospace;"> - Once the shelf life date has passed, Worth degrades twice as fast</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> - The Worth of an item is never negative</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> - "Gold" actually increases in Worth the older it gets</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> - The Worth of an item is never more than 50</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> - "Cadmium" is rare, has a worth of 80, and will never decrease in Worth</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> - "Helium", like gold, increases in Worth as it's ShelfLife value changes; Worth increases by 2 when there are 10 days or less and by 3 when there are 5 days or less but Worth drops to 0 once the ShelfLife is passed.</span><br />
<span style="color: #cccccc;"><br />
<span style="font-family: Courier New, Courier, monospace;">We have recently signed an alchemist to create "Alchemy" items. This requires an update to our system:</span></span><br />
<span style="color: #cccccc;"><br />
<span style="font-family: Courier New, Courier, monospace;"> - "Alchemy" items degrade in Worth twice as fast as normal items</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> - "Alchemy" items have a maximum worth of 100</span><br />
<span style="color: #cccccc;"><br />
<span style="font-family: Courier New, Courier, monospace;">Feel free to make any changes to the UpdateWorth method and add any new code as long as everything still works correctly. However, do not alter the Item class or Items property as those belong to another team that doesn’t believe in shared code ownership (you can make the UpdateWorth method and Items property static if you like, we'll cover for you). If you happen to find any conflicts with the above requirements, we would appreciate if you fixed them. </span></span><br />
<span style="color: #cccccc;"><br />
<span style="font-family: Courier New, Courier, monospace;">Just for clarification, an item can never have its Worth increase above 50, however "Cadmium" is a rare item and as such its Worth is 80 and it never alters.</span></span><br />
<span style="color: #cccccc;"><br />
<span style="font-family: Courier New, Courier, monospace;">PLEASE RETURN ALL OF THE FILES THAT YOU CREATE. FEEL FREE TO ZIP THE ENTIRE SOLUTION AND RETURN IT TO US.</span></span><br />
<span style="color: #cccccc;"><br />
<br />
<span style="font-family: Courier New, Courier, monospace;"> private void UpdateWorth()</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> for (var i = 0; i < Items.Count; i++)</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Name != "Gold" && Items[i].Name != "Helium")</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Worth > 0)</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Name != "Cadmium")</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> Items[i].Worth = Items[i].Worth - 1;</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> else</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Worth < 50)</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> Items[i].Worth = Items[i].Worth + 1;</span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"> if (Items[i].Name == "Helium")</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].ShelfLife < 11)</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Worth < 50)</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> Items[i].Worth = Items[i].Worth + 1;</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"> if (Items[i].ShelfLife < 6)</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Worth < 50)</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> Items[i].Worth = Items[i].Worth + 1;</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"> if (Items[i].Name != "Cadmium")</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> Items[i].ShelfLife = Items[i].ShelfLife - 1;</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"> if (Items[i].ShelfLife < 0)</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Name != "Gold")</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Name != "Helium")</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Worth > 0)</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Name != "Cadmium")</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> Items[i].Worth = Items[i].Worth - 1;</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> else</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> Items[i].Worth = Items[i].Worth - Items[i].Worth;</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> else</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> if (Items[i].Worth < 50)</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> Items[i].Worth = Items[i].Worth + 1;</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> }</span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"> private IList<item> Items = new List<item></item></item></span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> {</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> new Item {Name = "Aluminum Shackles", ShelfLife = 10, Worth = 20},</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> new Item {Name = "Gold", ShelfLife = 2, Worth = 50},</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> new Item {Name = "Plutonium Pinball Parts", ShelfLife = 5, Worth = 7},</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> new Item {Name = "Cadmium", ShelfLife = 0, Worth = 80},</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> new Item {Name = "Helium", ShelfLife = 15, Worth = 38},</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> new Item {Name = "Alchemy Iron", ShelfLife = 3, Worth = 75}</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> };</span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">class Item</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;">{</span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;"> public string Name { get; set; }</span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"> public int ShelfLife { get; set; }</span></span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;"> public int Worth { get; set; }</span></span><br />
<span style="color: #cccccc; font-family: Courier New, Courier, monospace;">}</span><br />
<span style="color: #cccccc;"><span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">Spend as much time as you feel comfortable spending. Typically you can expect to commit about 1 hour towards your solution. I look forward to seeing the completed project.</span></span><br />
<div>
<br /></div>
It was in C# which I had not coded in for a few years. That is no excuse for how badly I botched this job. It had been a little while since I had delved into Design Patterns and object-oriented programming so I went with a naive procedural approach. It was much better than the original (that wasn't hard) but it did not use object-oriented design, design patterns or anything approaching my best work.<br />
<br />
Even worse when they asked me how I could expand my code to handle hundreds of items I choked in a big way (easy to do after a 2.5 hour interview I suppose). I left the interview knowing that I had failed to get the job. Much worse that this was the feeling that I did not really show them what I could do.<br />
<br />
Sure enough, the recruiter dropped me an email on Sunday that said that they thought I was a fit culturally but I did not have the technical skills they were looking for in a team lead. I was kind of devastated but that's life. I knew I had not shown them how quickly I could learn the things they were looking for. I had done a B- job at best in that interview.<br />
<br />
I took away a few things from this process:<br />
<br />
<ul>
<li>Refresh my object-oriented design knowledge</li>
<li>Learn more about agile software design</li>
<li>Learn Ruby - I have looked at it a few times in the past but one of the guys in the interview seemed really sold on it</li>
</ul>
<div>
Looking at that list I decided to learn Ruby and use it to refresh by object-oriented design skills. Monday, I started looking at it and I am really amazed by how well Ruby gets out of your way. It is really great.</div>
<div>
<br /></div>
<div>
Anyway, here is a non-instrumented solution to the above coding challenge in Ruby. I did not put in the "business rules" for all of the items in the challenge. I just put in enough to prove to myself that this would have been an acceptable solution:</div>
<div>
<br /></div>
<br />
<div>
<span style="color: #999999;">class Inventory</span><br />
<span style="color: #999999;"> def initialize(*items)</span><br />
<span style="color: #999999;"> @inventory = []</span><br />
<span style="color: #999999;"> add_inventory_items(*items)</span><br />
<span style="color: #999999;"> end </span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def add_inventory_items(*items)</span><br />
<span style="color: #999999;"> items.each do |item|</span><br />
<span style="color: #999999;"> puts "Adding #{item.name} Inventory"</span><br />
<span style="color: #999999;"> @inventory.push(item)</span><br />
<span style="color: #999999;"> end </span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def remove_inventory_items(*items)</span><br />
<span style="color: #999999;"> items.each do |item|</span><br />
<span style="color: #999999;"> puts "Removing #{item.name} Inventory"</span><br />
<span style="color: #999999;"> @inventory.remove(item)</span><br />
<span style="color: #999999;"> end </span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def return_inventory_items</span><br />
<span style="color: #999999;"> @inventory</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def to_s</span><br />
<span style="color: #999999;"> @inventory.each {|item| puts item}</span><br />
<span style="color: #999999;"> # Return empty string so that you do not get Inventory object printed at end of input</span><br />
<span style="color: #999999;"> '' </span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;">end</span><br />
<span style="color: #999999;"><br />
class Item</span><br />
<span style="color: #999999;"> attr_reader :name</span><br />
<span style="color: #999999;"> attr_accessor :shelflife, :worth</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def initialize(name, shelflife, worth)</span><br />
<span style="color: #999999;"> @name = name</span><br />
<span style="color: #999999;"> @shelflife = shelflife</span><br />
<span style="color: #999999;"> @worth = worth</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def to_s</span><br />
<span style="color: #999999;"> "#{name} => Value Of #{worth} With Shelf Life Of #{shelflife}"</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;">end</span><br />
<span style="color: #999999;"><br />
class BusinessRule</span><br />
<span style="color: #999999;"> # &block is a block that takes at least one argument (the object variable)</span><br />
<span style="color: #999999;"> def initialize(block)</span><br />
<span style="color: #999999;"> @br_block = block</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def apply_rule(object,*other_args)</span><br />
<span style="color: #999999;"> @br_block.call(object)</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;">end</span><br />
<span style="color: #999999;"><br />
class BusinessRules</span><br />
<span style="color: #999999;"> def initialize</span><br />
<span style="color: #999999;"> # Can be multiple Rule Types Per Object</span><br />
<span style="color: #999999;"> @rules = Hash.new{|hash, key| hash[key] = Array.new}</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def add_rule(object,block)</span><br />
<span style="color: #999999;"> @rules[object.name].push(BusinessRule.new(block))</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def apply_all_matching_rules(object,*other_args)</span><br />
<span style="color: #999999;"> @rules[object.name].each do |br|</span><br />
<span style="color: #999999;"> br.apply_rule(object,*other_args)</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> </span><br />
<span style="color: #999999;"> def has_business_rules_for?(object)</span><br />
<span style="color: #999999;"> @rules.key?(object.name)</span><br />
<span style="color: #999999;"> end </span><br />
<span style="color: #999999;">end</span><br />
<span style="color: #999999;"><br />
def subtract_one_day_from_shelflife(item)</span><br />
<span style="color: #999999;"> item.shelflife -= 1 </span><br />
<span style="color: #999999;"> if item.shelflife < 0 then</span><br />
<span style="color: #999999;"> item.shelflife = 0</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;">end</span><br />
<span style="color: #999999;"><br />
puts "Addng Initial Inventory Values"</span><br />
<span style="color: #999999;">gold = Item.new("Gold",15,50)</span><br />
<span style="color: #999999;">adamantium = Item.new("Adamantium",100,1000)</span><br />
<span style="color: #999999;">alchemy_sulfer = Item.new("Alchemy Sulfur",30,40)</span><br />
<span style="color: #999999;">store_inventory = Inventory.new(gold,adamantium,alchemy_sulfer)</span><br />
<span style="color: #999999;">puts "\nInventory Values Before Applying Business Rules:"</span><br />
<span style="color: #999999;">puts store_inventory</span><br />
<span style="color: #999999;"><br />
puts "Creating Business Rules For Gold"</span><br />
<span style="color: #999999;">daily_process_rules = BusinessRules.new()</span><br />
<span style="color: #999999;">daily_process_rules.add_rule(gold, lambda {|item| subtract_one_day_from_shelflife(item)})</span><br />
<span style="color: #999999;">daily_process_rules.add_rule(gold, lambda {|item| item.worth += 1})</span><br />
<span style="color: #999999;">daily_process_rules.add_rule(gold, lambda {|item| if item.worth < 50 then item.worth = 50 end})</span><br />
<span style="color: #999999;"><br />
puts "Adding Specialized Business Rules For Items With No Pre-Defined Rules"</span><br />
<span style="color: #999999;"># Add Generic Business Rules To Items That Have None </span><br />
<span style="color: #999999;">store_inventory.return_inventory_items.each do |item|</span><br />
<span style="color: #999999;"> if item.name.downcase.include?("alchemy ") then</span><br />
<span style="color: #999999;"> puts " Creating Business Rules For Alchemy Object \"#{item.name}\""</span><br />
<span style="color: #999999;"> daily_process_rules.add_rule(item, lambda {|item| subtract_one_day_from_shelflife(item)})</span><br />
<span style="color: #999999;"> daily_process_rules.add_rule(item, lambda {|item| if item.shelflife == 0 then item.worth -= 2 else item.worth -= 1 end})</span><br />
<span style="color: #999999;"> daily_process_rules.add_rule(item, lambda {|item| if item.worth < 0 then item.worth = 0 end}) </span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;"> # Add Generic Business Rules To Item Because None Have Been Given</span><br />
<span style="color: #999999;"> if !(daily_process_rules.has_business_rules_for?(item)) then</span><br />
<span style="color: #999999;"> puts " Adding Generic Business Rules to Item #{item.name}"</span><br />
<span style="color: #999999;"> daily_process_rules.add_rule(item, lambda {|item| subtract_one_day_from_shelflife(item)})</span><br />
<span style="color: #999999;"> daily_process_rules.add_rule(item, lambda {|item| item.worth -= 1})</span><br />
<span style="color: #999999;"> daily_process_rules.add_rule(item, lambda {|item| if item.worth < 0 then item.worth = 0 end})</span><br />
<span style="color: #999999;"> end</span><br />
<span style="color: #999999;">end</span><br />
<span style="color: #999999;"><br />
puts "\nApplying Daily Process Business Rules For All Inventory Items"</span><br />
<span style="color: #999999;">store_inventory.return_inventory_items.each do |item|</span><br />
<span style="color: #999999;"> puts " Applying Daily Process Business Rules For #{item.name}"</span><br />
<span style="color: #999999;"> daily_process_rules.apply_all_matching_rules(item)</span><br />
<span style="color: #999999;">end</span><br />
<span style="color: #999999;"><br />
puts "\nInventory Values After Applying Business Rules:"</span><br />
<span style="color: #999999;">puts store_inventory</span><br />
<span style="color: #999999;"><br />
puts "Strings Could Be Placed In DataBase And Loaded As Business Rules Using Eval And Lambda"</span><br />
<span style="color: #999999;">it_works = eval "lambda {puts \"IT WORKS!!!\"}"</span><br />
<span style="color: #999999;">it_works.call</span><br />
<br />
I think if I could have presented a design like this I probably would have gotten the job. I am fairly happy at the job I have so I guess this is not the end of the world ;-).</div>
Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-631455297366732902008-10-01T08:46:00.001-04:002008-10-01T08:48:43.072-04:00Cloud Computing Is A TrapRichard Stallman <a href="http://www.guardian.co.uk/technology/2008/sep/29/cloud.computing.richard.stallman">says</a> that cloud computing is a trap and a hyped fad. It is only there to make you pay more over time and trap you in a proprietary format. In other news, the Internet is a fad that should be fading sometime soon.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-26334087888355863502008-09-19T14:35:00.002-04:002008-09-19T14:42:24.570-04:00Still Out Of PowerI've been out of power since Sunday at 4:30PM. I thought I would be going crazy by now but strangely I am enjoying being disconnected from all the technology I have in my life. Don't get me wrong, I will be happy to have television again. It just just isn't as critical as I thought it would be. Instead of zoning in front of the TV, I have begun reading in earnest again. I've read 3 books this week:<br /><br />The Man With The Golden Torc<br />Street Of Shadows (Star Wars Coruscant Nights Book 2)<br />Against the Tide Of Years<br /><br />It's amazing how much reading you can get done when you don't have the television distracting you.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-65090143192814022482008-07-21T01:08:00.002-04:002008-07-21T01:10:52.917-04:00Getting Back Into "Art" AgainI had some free time this last weekend and whipped this up. Maybe it will go good on a t-shirt ... maybe not ;-).<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMkZ1gJqEFVkK-M9JSLKdkj-dlSqmr9Nz2jAqK7VXolOiFmz2shrhsT5FU8d7y82RoHrbWzu9IqDQcxaZYSIFLINGgmTrWF-4XSZdGOHZnAEWpXa1xjsGQWWKrvjxSgg3iC-dK/s1600-h/Wanted_Poster_Final5.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMkZ1gJqEFVkK-M9JSLKdkj-dlSqmr9Nz2jAqK7VXolOiFmz2shrhsT5FU8d7y82RoHrbWzu9IqDQcxaZYSIFLINGgmTrWF-4XSZdGOHZnAEWpXa1xjsGQWWKrvjxSgg3iC-dK/s400/Wanted_Poster_Final5.png" alt="" id="BLOGGER_PHOTO_ID_5225330368082493426" border="0" /></a>Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-28055072606838982792008-07-09T15:23:00.003-04:002008-07-09T15:39:28.549-04:00Baggage MishandlingHey wait. If the airlines are going to be charging me $15 for each checked bag, shouldn't they be doing a better job. 1/138 of all bags get lost forever. This statistic does not include luggage that gets to you late. If the airlines want to charge you money for something they used to give you for free, shouldn't they at least have to provide value? Just a thought.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-82593204653289459672008-07-09T11:25:00.003-04:002008-07-09T13:20:40.952-04:00Real Grief In A Virtual WorldI just found out a long time SecondLife friend (Vx Shaw) passed away last month. I had not been in SL since last year. I had gotten really burnt out after 2.5 years of playing and needed a break. Weeks turned into months. I kept meaning to go back and check in but my home computer died and I just got it fixed. Now I'll never have a chance to talk to Vx (aka Bazoo Benton) again.<br /><br />The things I remember best about V is her upbeat attitude and the endless support that she provided her friends. She let me camp my store out on Abydos for a year for free. She also always supported my many and sometimes wacky endeavors in-game. She will truly be missed.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-83904678618598097622008-07-08T12:23:00.002-04:002008-07-08T12:28:56.704-04:00Telecom Companies Need To Buy A ClueI was just reading on <a href="http://www.law.com/jsp/legaltechnology/pubArticleLT.jsp?id=1202422769174&rss=ltn">law.com</a> that telecoms are suing municipalities for rolling out free wifi to their constituents. These same telecoms often cannot be bothered to roll out any internet whatsoever to these municipalities. This is what happens when you base your business plans on artificial scarcity. You end up having to sue your own customers (i.e. the music and movie industries). Once your services are ubiquitous commodities you have to either find another business or employ economies of scale to make more money. Suing your customers is not a viable alternative ... ever. If you do so, do not be surprised when your customers walk all over you on their way towards a better alternative.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-41453675653018900582008-07-07T13:02:00.001-04:002008-07-07T13:04:02.316-04:00SXE Phil Is AwesomeCan I just say the <a href="http://www.youtube.com/user/sxephil">SXE Phil</a> is one of the smartest and funniest guys I've ever had the pleasure to see on YouTube? Well, if you won't allow it I'm going to say it anyway ;-)Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-26983459649875833732008-06-25T15:34:00.001-04:002015-03-10T10:55:20.584-04:00Some People Have Too Much Time On Their HandsI just got told by our service manager that someone had connected this blog with some software I wrote for my company. They told him I did not represent my company well on this blog and I had an awfully high opinion of myself. Maybe there is a reason for not representing my company well ... hmmmm. Oh yeah, this is not my company's blog. It is my personal blog. I have removed references to the software I created for my company. Hopefully, that will prevent this from happening in the future.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-22678888158189918532008-06-20T09:27:00.003-04:002008-06-20T09:36:30.665-04:00Senator Christopher Dodd's Housing Bill And YouWell, the federal government is at it again. Slipped into a new housing bill is a data collection policy requiring payment systems to track and report information on nearly every electronic transaction to the government. Yes, this is the same government that managed to lose 800,000 social security numbers by leaving a backup tape in an employees car. It's the same government that lost millions of veteran's social security numbers when a Veteran Affairs employee took the data home without authorization. Do we really trust them with our credit card and payment information? Why is this slipped innocuously into a Housing Bill? Are they ashamed to foist this turd in its own bill?Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-67949678551578599792008-06-18T08:14:00.003-04:002008-06-18T08:30:40.051-04:00Where's My Fiber?In a time when our Internet Service Providers are planning on foisting "pay as you go" plans upon the American consumer, it might be useful to look back 12 years to 1996. In 1996, America's Congress was ready to pass the <span style="font-family:Arial;">Telecommunications Reform Act. The telephone companies "promised" to run fiber to 86 million American homes by 2008. They also promised that this fiber would enable us to cruise the Internet at 45 Mbps. In exchange for this promised, the telcos received $200 billion in tax breaks and beneficial legislation. That is about $2000 per American home in benefits we never received.<br />If you or I had scammed every household in the United States to the tune of $2000 we would be sitting in prison. Of course, you and I don't have millions of dollars to pay the American government hush money.<br />Telcos ... I'm still waiting for my fiber. Next week would be fine with me.<br /></span>Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-42382286386409992762008-06-17T15:29:00.002-04:002008-06-17T15:36:36.492-04:00Chosing Between Crap And PooAhhhh, politics is in the air again campers. Aren't we all just so excited? I don't know about you but I am sick of all the political parties and candidates. It just seems like every elections we get the choice between crap and poo. They both stink and taste bad going down. Just once, I would love to have a candidate that was not:<br /><ol><li>In bed with special interests</li><li>Lying to me</li><li>Pre-compromised</li><li>Looking for as much power as they can grab</li></ol>Oh well, I guess I won't hold my breath. I don't want to turn blue and pass out.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-69804042681465078752008-06-13T08:07:00.002-04:002008-06-13T08:12:55.983-04:00RIAA Bullies At It AgainJust <a href="http://arstechnica.com/news.ars/post/20080611-riaa-doubles-settlement-cost-for-students-fighting-subpoenas.html">read</a> about the RIAA doubling settling costs for people that try to quash their automated "piracy" pre-litigation letters. You know, the ones that try to link IP addresses to people (sometimes with horrifyingly unreliable <a href="http://www.afterdawn.com/news/archive/6038.cfm">results</a>).<br />In other news, school yard bullies are doubling the amount of lunch money they take from their victims if they try to fight back.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-10786414907191294082008-06-12T15:53:00.006-04:002008-06-13T07:58:11.954-04:00Bruce Lee's Top 7 Fundamentals For Getting Your Life In ShapeJust read some <a href="http://www.positivityblog.com/index.php/2008/03/07/bruce-lees-top-7-fundamentals-for-getting-your-life-in-shape/">crap</a> at a site called the positivity blog. It is the authors re-interpretation of 7 rules Bruce Lee had to make his life better. They are:<br /><ol><li>What are you really thinking about today?</li><li>Simplify.</li><li>Learn about yourself in interactions.</li><li>Do not divide.</li><li>Avoid a dependency on validation from others.</li><li>Be proactive.</li><li>Be you.</li></ol>Not bad rules necessarily (if a little general). The only problem is that you have to wade through so many pages of flipping Google ads to get to the rules. I did not even realize the site had completely loaded because no content besides ads was coming up. My only other gripe is that the author forgot to include Bruce Lee's 8th rule:<br /><br /><strong><span class="body"><span style="color: rgb(53, 53, 53);" lang="EN-GB"> </span></span></strong>Don't do too much cocaine. It's a real killer.<br /><br />I know. I'm utterly tasteless.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-64315627198464052142008-06-11T14:31:00.002-04:002008-06-11T14:33:01.144-04:00Twitter Is Like A Big Whale ... Suspended By Birds<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIKjzOP_prAswvFeGO7FOU17I9cPqM16COBZFGWWDIErfywYNjxtx2cq48QBbXEYnR5d7iPE-WgMLZcXH0iFXQ-2-1B-Ap5tfIcEligH0BQU0CviCjjUKSNXXAkiFOs3wjAKFG/s1600-h/Twitter_Whale.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIKjzOP_prAswvFeGO7FOU17I9cPqM16COBZFGWWDIErfywYNjxtx2cq48QBbXEYnR5d7iPE-WgMLZcXH0iFXQ-2-1B-Ap5tfIcEligH0BQU0CviCjjUKSNXXAkiFOs3wjAKFG/s400/Twitter_Whale.png" alt="" id="BLOGGER_PHOTO_ID_5210693437326940658" border="0" /></a><br />This just seems Sooooooo appropriate. Look everyone. It is the Twitter Whale suspended in the air by some really small birds. Maybe that's why Twitter is so unreliable?Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-23232587373004860692007-08-25T01:54:00.001-04:002007-08-25T22:55:47.298-04:00A Little Something New<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2e8jWbpnkGsGntsenDW9CsU6BeWz53obT7WbCGjeWgIyNBo5tEgYHtWKbCX1X6zcRQiLfBsxoKJA96bj3-14_FQpJYLg0Tp8wBL0awf1BKQuq9WJmRz5F5CjGqHZRyr1ECMqG/s1600-h/Shhhhhhhhhh_4480x2540_5Color.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2e8jWbpnkGsGntsenDW9CsU6BeWz53obT7WbCGjeWgIyNBo5tEgYHtWKbCX1X6zcRQiLfBsxoKJA96bj3-14_FQpJYLg0Tp8wBL0awf1BKQuq9WJmRz5F5CjGqHZRyr1ECMqG/s400/Shhhhhhhhhh_4480x2540_5Color.gif" alt="" id="BLOGGER_PHOTO_ID_5102836074432186082" border="0" /></a><br />Just a little something I've been working on. This is a larger version (than the preview) of a t-shirt I created for <a href="http://www.threadless.com">threadless.com</a>. I hope you enjoy.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com1tag:blogger.com,1999:blog-11537404.post-75058010986927118242007-08-23T10:06:00.001-04:002007-08-23T10:25:30.860-04:00I Have Problems. I Can't Tell You The Details But Could You Fix Them?We have a sales director where I work that insists on sending an email to the company president about how much GroupWise (an email system) sucks. He usually waits until something goes wrong (yesterday I didn't get 20 messages), polls his people for their problems (without any detail) and then sends our president an email. He makes no effort to tell the IT department any details about the problems. His emails contain such helpful statements as (not exact quotes ... just examples):<br /><br />"This thing simply isn't working."<br />"GroupWise sucks."<br />"Why can't you get this thing working."<br />"I didn't get 20 messages that I was supposed to get yesterday."<br />"If we were running Microsoft Exchange Server it would work."<br />"It's working great now but yesterday it wasn't working right."<br /><br />An average user might find the above statements helpful. Let me tell you why they aren't:<br /><br /><ol><li>There are no details. If you can take the time to copy the company president you think you could say what messages you didn't get giving details such as email address, time it should have been received.</li><li>Saying something sucks is not constructive. There is usually a reason why a business has chosen a particular product to use (cost, ease of use, fit with business objectives). Until you know the reason why a product has been chosen just saying it sucks is not what I would call "constructive criticism".</li><li>The comments lack evidence. Simply saying that another product would work better without evidence is like saying a certain make of car is better without looking at crash, safety and maintenance statistics.</li><li>You went above my head without giving me a chance to fix your problem. This sets the tone of the conversation. I'm already on the defensive and I probably am not feeling like having a constructive dialog with you.</li><li>Saying things like, "Why can't you get something working?" immediately puts someone on the defensive. They are no longer in the mood to talk with you ... much less solve your problems.</li><li>If you tell me that it's working great today but you didn't contact me when there were problems will NOT help me solve your problems. When you have issues ... COMMUNICATE. I probably could help you at that point. Now it is too late.</li></ol>The upshot of this is there are two types of conversations we can have ... constructive and nonconstructive. By communicating with your IT professional in the manner you are setting yourself up to not be helped. There are limits to what someone will do for an individual that treats them in this manner. It may not be right but let's face it, you will go the extra mile for a person that treats you with respect and professional courtesy. For everyone else ... you're just going to do the bare minimum required to keep your job. Having any other kind of expectation is as silly as expecting someone to solve your problems without any supportive details.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com1tag:blogger.com,1999:blog-11537404.post-53191303312728893632007-08-15T00:44:00.000-04:002007-08-15T00:46:00.426-04:00Every Dog Gets It's Day<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2MFI6hPvKPydYlJM-bdlwdhGKCTlXde8gEmEIAJ_eIx2iMMpTJtbr9vqpz2nOGPxa1BZARqquMi_YcWlkU1GjCwowb1Go353_HouDoNloh8o5VGVcSXi2ED_-5oud9H3ghyphenhyphenkZ/s1600-h/EveryDog_Tshirt_Submission_640x480.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2MFI6hPvKPydYlJM-bdlwdhGKCTlXde8gEmEIAJ_eIx2iMMpTJtbr9vqpz2nOGPxa1BZARqquMi_YcWlkU1GjCwowb1Go353_HouDoNloh8o5VGVcSXi2ED_-5oud9H3ghyphenhyphenkZ/s400/EveryDog_Tshirt_Submission_640x480.gif" alt="" id="BLOGGER_PHOTO_ID_5098783961436770354" border="0" /></a><br />Another new shirt design. This one has a happy ending ... for everyone but the dog.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-48310635847475602942007-08-10T01:07:00.000-04:002007-08-10T01:09:27.355-04:00A Little Lighter<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyMkGZzMYRFJYJGYGg0v5nJXzdtUXRS5hYUPMnxZlBtISGlH71keCILWfqpltxkIp0ag3hHma41uqeOaB3kxNa47lZC7uL0eDgYEXQ0MLnBwkeWkRly0wk09-4zTer1p0GC61B/s1600-h/Finished_Tee_LonelyAtTheTop_640x480.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyMkGZzMYRFJYJGYGg0v5nJXzdtUXRS5hYUPMnxZlBtISGlH71keCILWfqpltxkIp0ag3hHma41uqeOaB3kxNa47lZC7uL0eDgYEXQ0MLnBwkeWkRly0wk09-4zTer1p0GC61B/s400/Finished_Tee_LonelyAtTheTop_640x480.gif" alt="" id="BLOGGER_PHOTO_ID_5096934515811101490" border="0" /></a><br />People at Threadless say that my t-shirt might be a little too dark. I have created a lighter version just for them. I hope they like it.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-30147680040574977402007-08-07T09:39:00.000-04:002007-08-07T09:49:09.474-04:00Smart People Don't Follow AdviceI was talking to my president today and something unremarkable happened. He once again did not follow my advice.<br /><br />Just to preface, my president is an incredibly smart and accomplished man. I have nothing but respect for him and like him in general. He does however get trapped by the Labrea Tar Pit I see smart people fall into all the time. He doesn't follow advice by a topical expert and yet still expects good results to follow.<br /><br />Every 3 months he comes to me and asks why his mail is running so slow. I go up to his desk and run his email. I see anywhere between 6000 - 10000 messages in his inbox. I always tell him that he needs to move all but 500 - 1000 of these messages into folders. I further tell him that having that vast quantity of messages is slowing down everything he is trying to do with email. His mail client takes forever to come up. Why? The thousands of messages in his inbox.<br /><br />His reply is always the same, "It didn't used to do that." He doesn't bother trying to take my advice. He just falls back upon his intelligence which tells him that computers are automata and should always work the same way. Never mind that he's installed 10 programs this month and that we've upgraded his computer 5 times since the moment when, "It didn't used to do that."<br /><br />Personally, if I hired someone for their expertise I would hope that I would at least try to follow their advice. I hope I would but I probably wouldn't.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-38476819078851176252007-08-05T16:03:00.000-04:002007-08-05T16:06:00.797-04:00New Threadless TShirt<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhExl5atKHzhKC-IATHavrWucldfH1lLJ2YxyPpqVORag26mIs3CZieu7Bh5S8II8YIWVTRj_Pw-OfYdC79iOoKLbMPGjWjnd-3naePtQICO3qUfaquw09n5p0F2Xfh4JvI7TFq/s1600-h/BattleForTheDesktop_Final_Large.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhExl5atKHzhKC-IATHavrWucldfH1lLJ2YxyPpqVORag26mIs3CZieu7Bh5S8II8YIWVTRj_Pw-OfYdC79iOoKLbMPGjWjnd-3naePtQICO3qUfaquw09n5p0F2Xfh4JvI7TFq/s400/BattleForTheDesktop_Final_Large.gif" alt="" id="BLOGGER_PHOTO_ID_5095310154884786978" border="0" /></a><br />Well, I'm at it again. I'm trying to get a design accepted for <a href="http://www.threadless.com/">Threadless T-Shirts</a>. I hope that the design is original enough to get passed through. I guess time will tell.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-63702910749798682952007-07-19T23:36:00.001-04:002007-07-19T23:38:12.220-04:00A Little Something I've Been Working On<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiblCM8T2G0BplVhr52bAilKMSQeQl1FErvKNej2vY5SfpIkcwFg81LO-g32RvuzRVffR3QBlC91J9o42aT-JSmB2PahO6TzioxL_eFYaH2wvGxFZt-o5ikI-jIU_sPgjJm5dd9/s1600-h/RedMatteButterflies.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiblCM8T2G0BplVhr52bAilKMSQeQl1FErvKNej2vY5SfpIkcwFg81LO-g32RvuzRVffR3QBlC91J9o42aT-JSmB2PahO6TzioxL_eFYaH2wvGxFZt-o5ikI-jIU_sPgjJm5dd9/s400/RedMatteButterflies.png" alt="" id="BLOGGER_PHOTO_ID_5089118253505799170" border="0" /></a><br />Well, I've been at it again and I think I might be getting a bit better. Hopefully, I can turn this hobby into some real money at istockphoto. If not, there is always the enjoyment of creation.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-54885303368130127122007-07-09T14:55:00.000-04:002007-07-09T15:48:48.828-04:00Net NeutralityImagine this...<br />You are driving down the road and come to a toll plaza. It's the only way to get to your destination so you pay the $1.50 and move down the road. Then you get to the end of the toll road and pay another $1.50. Leaving the toll road you reach your destination, a grocery store, you proceed to buy food. You get up to the cash register and pay your bill. When you get your receipt you see a extra charge for $1.50 on it. You go to the service desk and ask why the extra charge was tacked onto your bill and they say it is to pay for you coming to their store on the toll road. They nicely explain that they are just passing on to you the charge the toll company passes on to them. You exclaim that you already paid those tolls and they say that's just the way it is.<br />That in a nutshell is what giving up net neutrality is going to do to this country. By enabling the toll road companies (i.e. Verizon, AT&T, etc.) the ability to charge companies for their traffic across their small section of the Internet you are going to hobble everyone. The U.S. (you know, the inventor of the Internet) is not the number one country when it comes to average connection speeds and is the most expensive one in which to have a high speed Internet connection. Do we really want to hobble this countries Internet connectivity anymore by giving in to these highway robbers? I think not.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0tag:blogger.com,1999:blog-11537404.post-53267004948013447182007-07-07T19:51:00.000-04:002007-07-07T20:05:56.964-04:00The Garden<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3X5Ft1kqTLbUcet8fUHogNuv6Ko7IOO3Vuu52qz6qPn_esnMJyYszOCONWu29P9cU1JzGm3KF3Z9CzPx1DagQBtWmIEyZhbywGeLPZvHYiXFUj7W9A9mh_9D_KhaN69lnxrWt/s1600-h/TreeOfKnowledge_Istock.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3X5Ft1kqTLbUcet8fUHogNuv6Ko7IOO3Vuu52qz6qPn_esnMJyYszOCONWu29P9cU1JzGm3KF3Z9CzPx1DagQBtWmIEyZhbywGeLPZvHYiXFUj7W9A9mh_9D_KhaN69lnxrWt/s400/TreeOfKnowledge_Istock.png" alt="" id="BLOGGER_PHOTO_ID_5084609597777360802" border="0" /></a><br />Just a little Inkscape vector drawing I worked on this weekend. After I get better, I plan on perhaps submitting some of these to istockphoto. We'll see.Michael Lamberthttp://www.blogger.com/profile/14487982558495601343noreply@blogger.com0