sip 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/Gemfile +2 -0
  2. data/LICENSE +674 -0
  3. data/README.rdoc +32 -0
  4. data/Rakefile +21 -0
  5. data/bin/sip +83 -0
  6. data/bin/transpart +114 -0
  7. data/docs/classes/Sip.html +169 -0
  8. data/docs/classes/Sip/CmdOpts.html +179 -0
  9. data/docs/classes/Sip/Config.html +362 -0
  10. data/docs/classes/Sip/DBBase.html +368 -0
  11. data/docs/classes/Sip/HadoopException.html +111 -0
  12. data/docs/classes/Sip/Hive.html +295 -0
  13. data/docs/classes/Sip/HiveQueryException.html +111 -0
  14. data/docs/classes/Sip/ImportScriptExecutionError.html +111 -0
  15. data/docs/classes/Sip/MySQLSipper.html +273 -0
  16. data/docs/classes/Sip/NoSuchColumn.html +111 -0
  17. data/docs/classes/Sip/NoSuchTable.html +111 -0
  18. data/docs/classes/Sip/PastFailureException.html +111 -0
  19. data/docs/classes/Sip/Sipper.html +454 -0
  20. data/docs/classes/Sip/UnsupportedDatabaseType.html +111 -0
  21. data/docs/classes/Sip/Utils.html +269 -0
  22. data/docs/classes/Struct.html +146 -0
  23. data/docs/created.rid +1 -0
  24. data/docs/files/README_rdoc.html +174 -0
  25. data/docs/files/lib/sip/cmdopts_rb.html +101 -0
  26. data/docs/files/lib/sip/config_rb.html +108 -0
  27. data/docs/files/lib/sip/databases/dbbase_rb.html +108 -0
  28. data/docs/files/lib/sip/databases/mysql_rb.html +108 -0
  29. data/docs/files/lib/sip/exceptions_rb.html +101 -0
  30. data/docs/files/lib/sip/extensions_rb.html +101 -0
  31. data/docs/files/lib/sip/hive_rb.html +101 -0
  32. data/docs/files/lib/sip/sipper_rb.html +101 -0
  33. data/docs/files/lib/sip/utils_rb.html +110 -0
  34. data/docs/files/lib/sip/version_rb.html +101 -0
  35. data/docs/files/lib/sip_rb.html +117 -0
  36. data/docs/fr_class_index.html +42 -0
  37. data/docs/fr_file_index.html +38 -0
  38. data/docs/fr_method_index.html +72 -0
  39. data/docs/index.html +24 -0
  40. data/docs/rdoc-style.css +208 -0
  41. data/lib/sip.rb +10 -0
  42. data/lib/sip/cmdopts.rb +20 -0
  43. data/lib/sip/config.rb +80 -0
  44. data/lib/sip/databases/dbbase.rb +56 -0
  45. data/lib/sip/databases/mysql.rb +52 -0
  46. data/lib/sip/exceptions.rb +9 -0
  47. data/lib/sip/extensions.rb +5 -0
  48. data/lib/sip/hive.rb +62 -0
  49. data/lib/sip/sipper.rb +118 -0
  50. data/lib/sip/templates/export.sh +73 -0
  51. data/lib/sip/utils.rb +58 -0
  52. data/lib/sip/version.rb +3 -0
  53. data/test/database_interaction_test.rb +7 -0
  54. data/test/hive_test.rb +28 -0
  55. data/test/sipper_test.rb +25 -0
  56. metadata +125 -0
@@ -0,0 +1,32 @@
1
+ = SIP: SQL to Hive Importer
2
+ SIP is a ETL[http://en.wikipedia.org/wiki/Extract,_transform,_load] tool for extracting SQL databases and importing them into Hive[http://hive.apache.org]. It was created because the ability to transform columns and partition data was an absolute requirement, and no other tool provided that functionality.
3
+
4
+ Unique features include:
5
+ * The ability to transform columns (using Ruby code)
6
+ * A single, simple, human readable configuration file
7
+ * The ability to parition tables in Hive based on the value of any (possibly transformed) columns
8
+
9
+ Bug reports and pull requests welcome on github[https://github.com/livingsocial/sip].
10
+
11
+ == Requirements
12
+ * A working Hadoop installation
13
+ * Hive on the namenode
14
+ * Ruby >= 1.8.6 on all datanodes
15
+ * SQL Ruby libs
16
+
17
+ == Installation, Configuration, and Use
18
+ gem install sip
19
+
20
+ note: if no primary key (default: id), must set incremental_index to blank
21
+
22
+
23
+ == How it Works
24
+ Per table to be imported, SIP determines the queries necessary to perform an export and the creates scripts (one per datanode) that are then run individually in parallel. Each script:
25
+ 1. Copies a transformation / partition (transpart) script to the datanode
26
+ 1. Performs the SQL extraction, piping output through the transpart script
27
+ 1. Uploads all partitions to HDFS
28
+
29
+ Then, all of the partitions are imported from HDFS into Hive. Easy squeezy.
30
+
31
+
32
+
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ desc "Run all unit tests"
9
+ Rake::TestTask.new("test") { |t|
10
+ t.libs << "lib"
11
+ t.test_files = FileList['test/*_test.rb']
12
+ t.verbose = true
13
+ }
14
+
15
+ desc "Create documentation"
16
+ Rake::RDocTask.new("docs") { |rdoc|
17
+ rdoc.title = "Sip - SQL to Hive importer"
18
+ rdoc.rdoc_dir = 'docs'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ }
data/bin/sip ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'rubygems'
4
+ require 'sip'
5
+
6
+ starttime = Time.now
7
+
8
+ options = { :db => nil, :table => nil, :config => 'sip.yml', :debug => false, :tfile => nil, :overwrite => false }
9
+ parser = OptionParser.new { |opts|
10
+ opts.banner = "Usage: sip [options]"
11
+
12
+ opts.on('-h', '--help', 'displays usage information') {
13
+ puts opts
14
+ exit
15
+ }
16
+
17
+ opts.on('-o', '--overwrite', 'force full overwrite') {
18
+ options[:overwrite] = true
19
+ }
20
+
21
+ opts.on('-d', '--debug', 'Print debugging information') { |d|
22
+ options[:debug] = true
23
+ }
24
+
25
+ opts.on('-D <database>', '--database <database>', 'only import from given database') { |db|
26
+ options[:db] = db
27
+ }
28
+
29
+ opts.on('-c <config file>', '--config <config file>', 'use specified config') { |c|
30
+ options[:config] = c
31
+ }
32
+
33
+ opts.on('-t <table>', '--table <table>', 'only import from given table (must specify -D as well)') { |t|
34
+ options[:table] = t
35
+ }
36
+
37
+ opts.on('-f <file>', '--transpart-file <file>', 'translation/partition file location (default: looks for file in path)') { |t|
38
+ options[:tfile] = t
39
+ }
40
+ }.parse!
41
+
42
+
43
+ if ENV['HADOOP_HOME'].nil? or ENV['HIVE_HOME'].nil?
44
+ puts "You must first set both the HADOOP_HOME and HIVE_HOME environment variables."
45
+ exit 1
46
+ end
47
+
48
+ debug = Proc.new { |t| puts "#{Sip::Utils::hostname} #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}: #{t}" if options[:debug] }
49
+
50
+ debug.call "Using config file #{options[:config]}"
51
+ config = Sip::Config.load_file options[:config]
52
+ config.set_temp options, [:debug, :overwrite]
53
+
54
+ config['tfile'] = Sip::Utils::tfile_path(options[:tfile] || config['tfile'])
55
+ config['hdfs_tmpdir'] ||= "/tmp/sip"
56
+
57
+ if config['tfile'].nil?
58
+ puts "Could not find transforation / partition (transpart) script. Please use -f option to specify a location."
59
+ exit 1
60
+ end
61
+ debug.call "Using transformation / partitioning script '#{config['tfile']}'"
62
+
63
+ sipper = Sip::Sipper.new config
64
+ config['databases'].each { |dbname, dbconfig|
65
+ next if not options[:db].nil? and options[:db] != dbname
66
+ dbconfig['tables'].each { |tablename, tableconfig|
67
+ next if not options[:db].nil? and not options[:table].nil? and options[:table] != tablename
68
+ debug.call "creating script for table #{tablename} in database #{dbname}"
69
+ script_count = sipper.create_scripts dbconfig, tableconfig
70
+ if script_count > 0
71
+ debug.call "Executing #{script_count} scripts..."
72
+ sipper.run_scripts
73
+ debug.call "Running hive import..."
74
+ sipper.run_hive_import tableconfig
75
+ end
76
+ }
77
+ }
78
+
79
+ # Save new version of config
80
+ debug.call "Writing new version of config file to #{options[:config]}"
81
+ sipper.config.save_file options[:config]
82
+
83
+ debug.call "Finished running import in #{Time.now - starttime} seconds"
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'socket'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+ require 'date'
7
+ require 'socket'
8
+
9
+ class Transformer
10
+ def self.add_one(value)
11
+ value.to_i + 1
12
+ end
13
+
14
+ def self.datetime_to_date(value)
15
+ value.split(' ').first
16
+ end
17
+
18
+ # this takes forever to run
19
+ def self.to_date(value)
20
+ Date.parse(value).to_s
21
+ end
22
+ end
23
+
24
+
25
+ ############################################################################
26
+ # -c one,two,three,created_at -p created_at -t one:transone,two:transformtwo -o /tmp/sip -s 1234 -d
27
+ options = { :cols => [], :partition_by => nil, :trans => {}, :debug => false, :outdir => nil, :onehundreth => nil, :hive_tablename => nil }
28
+ parser = OptionParser.new { |opts|
29
+ opts.banner = "Usage: transpart [options]"
30
+
31
+ opts.on('-c <colum list>', '--columns <column list>', 'list of columns in order') { |c|
32
+ options[:cols] = c.split(",")
33
+ }
34
+
35
+ opts.on('-p <partition column>', '--partition-by <partition column>', 'partition by given column') { |c|
36
+ options[:partition_by] = c
37
+ }
38
+
39
+ opts.on('-t <transformations>', '--transformations <transformations>', 'comma separated col:transmethod list') { |t|
40
+ t.split(',').each { |trans|
41
+ key, value = trans.split(':')
42
+ options[:trans][key] = value
43
+ }
44
+ }
45
+
46
+ opts.on('-H <hive tablename>', '--hive-table <hive tablename>', 'name of the hive table to import into') { |h|
47
+ options[:hive_tablename] = h
48
+ }
49
+
50
+ opts.on('-o <output dir>', '--output-dir <output dir>', 'Directory to output partitions to') { |o|
51
+ options[:outdir] = o
52
+ }
53
+
54
+ opts.on('-s <num rows>', '--size <num rows>', 'Number of rows - used to estimate % done') { |s|
55
+ options[:onehundreth] = s.to_i / 100
56
+ }
57
+
58
+ opts.on('-d', '--debug', 'print debugging info') {
59
+ options[:debug] = true
60
+ }
61
+ }.parse!
62
+
63
+
64
+ if options[:cols].length == 0 and (options[:trans].length > 0 or not options[:partition_by].nil?)
65
+ puts "You must specify column names with the -c option if you also give transformation or parition options"
66
+ exit 1
67
+ elsif not options[:partition_by].nil? and not options[:cols].include? options[:partition_by]
68
+ puts "Can not partition by a non-existant column"
69
+ exit 1
70
+ end
71
+
72
+ partfiles = {}
73
+ outputdir = options[:outdir] || Dir.mktmpdir('sip_')
74
+ hostname = Socket.gethostname
75
+ tmpfname = Proc.new { |partition|
76
+ odir = partition.nil? ? outputdir : File.join(outputdir, partition)
77
+ FileUtils.mkdir_p odir
78
+ File.join(odir, "#{hostname}_#{Time.now.to_f}_#{ (1...10).map { ('a'...'z').to_a.sort_by{rand}.first }.join }.part")
79
+ }
80
+ debug = Proc.new { |t| puts "#{hostname} #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}: #{t}" if options[:debug] }
81
+
82
+
83
+ lineno = 0
84
+ pdone = 0
85
+ $stdin.each_line do |line|
86
+ lineno += 1
87
+ vals = line.chomp("\n").split("\t")
88
+ partval = nil
89
+ transvalues = []
90
+ vals.each_with_index { |value, index|
91
+ if options[:trans].has_key? options[:cols][index]
92
+ value = Transformer.send(options[:trans][options[:cols][index]], value)
93
+ end
94
+ if options[:cols][index] == options[:partition_by]
95
+ partval = value
96
+ else
97
+ transvalues << value
98
+ end
99
+ }
100
+
101
+ if not partfiles.has_key? partval
102
+ partfiles[partval] = File.open(tmpfname.call(partval), 'w')
103
+ end
104
+ partfiles[partval].write(transvalues.join("\001") + "\n")
105
+
106
+ if not options[:onehundreth].nil? and lineno == options[:onehundreth]
107
+ lineno = 0
108
+ pdone += 1
109
+ debug.call "#{pdone}% finished exporting table"
110
+ end
111
+ end
112
+
113
+ debug.call "finished exporting table"
114
+ partfiles.values.each { |f| f.close }
@@ -0,0 +1,169 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
5
+
6
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
7
+ <head>
8
+ <title>Module: Sip</title>
9
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
10
+ <meta http-equiv="Content-Script-Type" content="text/javascript" />
11
+ <link rel="stylesheet" href=".././rdoc-style.css" type="text/css" media="screen" />
12
+ <script type="text/javascript">
13
+ // <![CDATA[
14
+
15
+ function popupCode( url ) {
16
+ window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
17
+ }
18
+
19
+ function toggleCode( id ) {
20
+ if ( document.getElementById )
21
+ elem = document.getElementById( id );
22
+ else if ( document.all )
23
+ elem = eval( "document.all." + id );
24
+ else
25
+ return false;
26
+
27
+ elemStyle = elem.style;
28
+
29
+ if ( elemStyle.display != "block" ) {
30
+ elemStyle.display = "block"
31
+ } else {
32
+ elemStyle.display = "none"
33
+ }
34
+
35
+ return true;
36
+ }
37
+
38
+ // Make codeblocks hidden by default
39
+ document.writeln( "<style type=\"text/css\">div.method-source-code { display: none }</style>" )
40
+
41
+ // ]]>
42
+ </script>
43
+
44
+ </head>
45
+ <body>
46
+
47
+
48
+
49
+ <div id="classHeader">
50
+ <table class="header-table">
51
+ <tr class="top-aligned-row">
52
+ <td><strong>Module</strong></td>
53
+ <td class="class-name-in-header">Sip</td>
54
+ </tr>
55
+ <tr class="top-aligned-row">
56
+ <td><strong>In:</strong></td>
57
+ <td>
58
+ <a href="../files/lib/sip/cmdopts_rb.html">
59
+ lib/sip/cmdopts.rb
60
+ </a>
61
+ <br />
62
+ <a href="../files/lib/sip/config_rb.html">
63
+ lib/sip/config.rb
64
+ </a>
65
+ <br />
66
+ <a href="../files/lib/sip/databases/dbbase_rb.html">
67
+ lib/sip/databases/dbbase.rb
68
+ </a>
69
+ <br />
70
+ <a href="../files/lib/sip/databases/mysql_rb.html">
71
+ lib/sip/databases/mysql.rb
72
+ </a>
73
+ <br />
74
+ <a href="../files/lib/sip/exceptions_rb.html">
75
+ lib/sip/exceptions.rb
76
+ </a>
77
+ <br />
78
+ <a href="../files/lib/sip/hive_rb.html">
79
+ lib/sip/hive.rb
80
+ </a>
81
+ <br />
82
+ <a href="../files/lib/sip/sipper_rb.html">
83
+ lib/sip/sipper.rb
84
+ </a>
85
+ <br />
86
+ <a href="../files/lib/sip/utils_rb.html">
87
+ lib/sip/utils.rb
88
+ </a>
89
+ <br />
90
+ <a href="../files/lib/sip/version_rb.html">
91
+ lib/sip/version.rb
92
+ </a>
93
+ <br />
94
+ </td>
95
+ </tr>
96
+
97
+ </table>
98
+ </div>
99
+ <!-- banner header -->
100
+
101
+ <div id="bodyContent">
102
+
103
+
104
+
105
+ <div id="contextContent">
106
+
107
+
108
+
109
+ </div>
110
+
111
+
112
+ </div>
113
+
114
+
115
+ <!-- if includes -->
116
+
117
+ <div id="section">
118
+
119
+ <div id="class-list">
120
+ <h3 class="section-bar">Classes and Modules</h3>
121
+
122
+ Module <a href="Sip/Hive.html" class="link">Sip::Hive</a><br />
123
+ Module <a href="Sip/Utils.html" class="link">Sip::Utils</a><br />
124
+ Class <a href="Sip/CmdOpts.html" class="link">Sip::CmdOpts</a><br />
125
+ Class <a href="Sip/Config.html" class="link">Sip::Config</a><br />
126
+ Class <a href="Sip/DBBase.html" class="link">Sip::DBBase</a><br />
127
+ Class <a href="Sip/HadoopException.html" class="link">Sip::HadoopException</a><br />
128
+ Class <a href="Sip/HiveQueryException.html" class="link">Sip::HiveQueryException</a><br />
129
+ Class <a href="Sip/ImportScriptExecutionError.html" class="link">Sip::ImportScriptExecutionError</a><br />
130
+ Class <a href="Sip/MySQLSipper.html" class="link">Sip::MySQLSipper</a><br />
131
+ Class <a href="Sip/NoSuchColumn.html" class="link">Sip::NoSuchColumn</a><br />
132
+ Class <a href="Sip/NoSuchTable.html" class="link">Sip::NoSuchTable</a><br />
133
+ Class <a href="Sip/PastFailureException.html" class="link">Sip::PastFailureException</a><br />
134
+ Class <a href="Sip/Sipper.html" class="link">Sip::Sipper</a><br />
135
+ Class <a href="Sip/UnsupportedDatabaseType.html" class="link">Sip::UnsupportedDatabaseType</a><br />
136
+
137
+ </div>
138
+
139
+ <div id="constants-list">
140
+ <h3 class="section-bar">Constants</h3>
141
+
142
+ <div class="name-list">
143
+ <table summary="Constants">
144
+ <tr class="top-aligned-row context-row">
145
+ <td class="context-item-name">VERSION</td>
146
+ <td>=</td>
147
+ <td class="context-item-value">&quot;0.0.1&quot;</td>
148
+ </tr>
149
+ </table>
150
+ </div>
151
+ </div>
152
+
153
+
154
+
155
+
156
+
157
+
158
+ <!-- if method_list -->
159
+
160
+
161
+ </div>
162
+
163
+
164
+ <div id="validator-badges">
165
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
166
+ </div>
167
+
168
+ </body>
169
+ </html>
@@ -0,0 +1,179 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
5
+
6
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
7
+ <head>
8
+ <title>Class: Sip::CmdOpts</title>
9
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
10
+ <meta http-equiv="Content-Script-Type" content="text/javascript" />
11
+ <link rel="stylesheet" href="../.././rdoc-style.css" type="text/css" media="screen" />
12
+ <script type="text/javascript">
13
+ // <![CDATA[
14
+
15
+ function popupCode( url ) {
16
+ window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
17
+ }
18
+
19
+ function toggleCode( id ) {
20
+ if ( document.getElementById )
21
+ elem = document.getElementById( id );
22
+ else if ( document.all )
23
+ elem = eval( "document.all." + id );
24
+ else
25
+ return false;
26
+
27
+ elemStyle = elem.style;
28
+
29
+ if ( elemStyle.display != "block" ) {
30
+ elemStyle.display = "block"
31
+ } else {
32
+ elemStyle.display = "none"
33
+ }
34
+
35
+ return true;
36
+ }
37
+
38
+ // Make codeblocks hidden by default
39
+ document.writeln( "<style type=\"text/css\">div.method-source-code { display: none }</style>" )
40
+
41
+ // ]]>
42
+ </script>
43
+
44
+ </head>
45
+ <body>
46
+
47
+
48
+
49
+ <div id="classHeader">
50
+ <table class="header-table">
51
+ <tr class="top-aligned-row">
52
+ <td><strong>Class</strong></td>
53
+ <td class="class-name-in-header">Sip::CmdOpts</td>
54
+ </tr>
55
+ <tr class="top-aligned-row">
56
+ <td><strong>In:</strong></td>
57
+ <td>
58
+ <a href="../../files/lib/sip/cmdopts_rb.html">
59
+ lib/sip/cmdopts.rb
60
+ </a>
61
+ <br />
62
+ </td>
63
+ </tr>
64
+
65
+ <tr class="top-aligned-row">
66
+ <td><strong>Parent:</strong></td>
67
+ <td>
68
+ Hash
69
+ </td>
70
+ </tr>
71
+ </table>
72
+ </div>
73
+ <!-- banner header -->
74
+
75
+ <div id="bodyContent">
76
+
77
+
78
+
79
+ <div id="contextContent">
80
+
81
+
82
+
83
+ </div>
84
+
85
+ <div id="method-list">
86
+ <h3 class="section-bar">Methods</h3>
87
+
88
+ <div class="name-list">
89
+ <a href="#M000040">set</a>&nbsp;&nbsp;
90
+ <a href="#M000041">to_s</a>&nbsp;&nbsp;
91
+ </div>
92
+ </div>
93
+
94
+ </div>
95
+
96
+
97
+ <!-- if includes -->
98
+
99
+ <div id="section">
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+ <!-- if method_list -->
109
+ <div id="methods">
110
+ <h3 class="section-bar">Public Instance methods</h3>
111
+
112
+ <div id="method-M000040" class="method-detail">
113
+ <a name="M000040"></a>
114
+
115
+ <div class="method-heading">
116
+ <a href="#M000040" class="method-signature">
117
+ <span class="method-name">set</span><span class="method-args">(*k)</span>
118
+ </a>
119
+ </div>
120
+
121
+ <div class="method-description">
122
+ <p><a class="source-toggle" href="#"
123
+ onclick="toggleCode('M000040-source');return false;">[Source]</a></p>
124
+ <div class="method-source-code" id="M000040-source">
125
+ <pre>
126
+ <span class="ruby-comment cmt"># File lib/sip/cmdopts.rb, line 3</span>
127
+ <span class="ruby-keyword kw">def</span> <span class="ruby-identifier">set</span>(<span class="ruby-operator">*</span><span class="ruby-identifier">k</span>)
128
+ <span class="ruby-identifier">k</span>.<span class="ruby-identifier">each</span> { <span class="ruby-operator">|</span><span class="ruby-identifier">key</span><span class="ruby-operator">|</span>
129
+ <span class="ruby-keyword kw">self</span>[<span class="ruby-identifier">key</span>] = <span class="ruby-keyword kw">nil</span>
130
+ }
131
+ <span class="ruby-keyword kw">end</span>
132
+ </pre>
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <div id="method-M000041" class="method-detail">
138
+ <a name="M000041"></a>
139
+
140
+ <div class="method-heading">
141
+ <a href="#M000041" class="method-signature">
142
+ <span class="method-name">to_s</span><span class="method-args">(prefix=nil, suffix=nil)</span>
143
+ </a>
144
+ </div>
145
+
146
+ <div class="method-description">
147
+ <p><a class="source-toggle" href="#"
148
+ onclick="toggleCode('M000041-source');return false;">[Source]</a></p>
149
+ <div class="method-source-code" id="M000041-source">
150
+ <pre>
151
+ <span class="ruby-comment cmt"># File lib/sip/cmdopts.rb, line 9</span>
152
+ <span class="ruby-keyword kw">def</span> <span class="ruby-identifier">to_s</span>(<span class="ruby-identifier">prefix</span>=<span class="ruby-keyword kw">nil</span>, <span class="ruby-identifier">suffix</span>=<span class="ruby-keyword kw">nil</span>)
153
+ <span class="ruby-identifier">opts</span> = <span class="ruby-identifier">map</span> { <span class="ruby-operator">|</span><span class="ruby-identifier">k</span>,<span class="ruby-identifier">v</span><span class="ruby-operator">|</span>
154
+ <span class="ruby-keyword kw">if</span> <span class="ruby-identifier">v</span>.<span class="ruby-identifier">nil?</span>
155
+ (<span class="ruby-identifier">k</span>.<span class="ruby-identifier">length</span> <span class="ruby-operator">&gt;</span> <span class="ruby-value">1</span>) <span class="ruby-operator">?</span> <span class="ruby-node">&quot;--#{k}&quot;</span> <span class="ruby-operator">:</span> <span class="ruby-node">&quot;-#{k}&quot;</span>
156
+ <span class="ruby-keyword kw">else</span>
157
+ (<span class="ruby-identifier">k</span>.<span class="ruby-identifier">length</span> <span class="ruby-operator">&gt;</span> <span class="ruby-value">1</span>) <span class="ruby-operator">?</span> <span class="ruby-node">&quot;--#{k}=#{v}&quot;</span> <span class="ruby-operator">:</span> <span class="ruby-node">&quot;-#{k} #{v}&quot;</span>
158
+ <span class="ruby-keyword kw">end</span>
159
+ }.<span class="ruby-identifier">join</span>(<span class="ruby-value str">&quot; &quot;</span>)
160
+ [(<span class="ruby-identifier">prefix</span>.<span class="ruby-identifier">nil?</span> <span class="ruby-value">? </span><span class="ruby-value str">&quot;&quot;</span> <span class="ruby-operator">:</span> <span class="ruby-identifier">prefix</span>), <span class="ruby-identifier">opts</span>, (<span class="ruby-identifier">suffix</span>.<span class="ruby-identifier">nil?</span> <span class="ruby-value">? </span><span class="ruby-value str">&quot;&quot;</span> <span class="ruby-operator">:</span> <span class="ruby-identifier">suffix</span>)].<span class="ruby-identifier">select</span> { <span class="ruby-operator">|</span><span class="ruby-identifier">v</span><span class="ruby-operator">|</span> <span class="ruby-identifier">v</span> <span class="ruby-operator">!=</span> <span class="ruby-value str">&quot;&quot;</span> }.<span class="ruby-identifier">join</span>(<span class="ruby-value str">&quot; &quot;</span>)
161
+ <span class="ruby-keyword kw">end</span>
162
+ </pre>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+
168
+ </div>
169
+
170
+
171
+ </div>
172
+
173
+
174
+ <div id="validator-badges">
175
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
176
+ </div>
177
+
178
+ </body>
179
+ </html>