sapis 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 31d4f98fb8910d18204ff9e5871480ac78185666f865e747ea86805024791530
4
+ data.tar.gz: cc9bc89dbb404f6014f6b0916d16e9c53d76eb8d67d741d31ff9d7bf43729732
5
+ SHA512:
6
+ metadata.gz: 2dcf77bcecdcd1776e908cad92fb248256a0fe7359ffa8390818d456729f319d5ef2462f19ed77920944067440ebe6a19003ddf44aea808c044bf88d15b7292c
7
+ data.tar.gz: 438f3befe6b4fed326a2efc963282bccabc9646f9cc16589fe2da4989a5469a19970e8041f72c73ff75102cdcc0721ff34c82076842eee56b944b41882b73782
data/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # Sapis
2
+
3
+ Sav's APIs - A collection of Ruby utility helpers for various tasks including graphing, configuration management, concurrency, system operations, multimedia handling, and more.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'sapis'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ gem install sapis
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Require the gem in your Ruby code:
28
+
29
+ ```ruby
30
+ require 'sapis'
31
+ ```
32
+
33
+ ### Available Modules
34
+
35
+ #### GraphingHelper
36
+
37
+ Provides functionality for creating and formatting graphs using Gruff.
38
+
39
+ ```ruby
40
+ # Transpose data and create line graphs
41
+ data, days = GraphingHelper.transpose_data_top_headers( source_data )
42
+ GraphingHelper.format_as_line_graph( data, days, out_file: 'graph.png' )
43
+
44
+ # Format data as tables
45
+ table = GraphingHelper.format_as_table( rows, separator: '|', align: { 'Header' => :left } )
46
+ ```
47
+
48
+ #### ConfigurationHelper
49
+
50
+ Load configuration from files with optional encryption support.
51
+
52
+ ```ruby
53
+ config = ConfigurationHelper.load_configuration( group: 'myapp', sym_keys: true )
54
+ value = config[ :some_key ]
55
+ ```
56
+
57
+ #### ConcurrencyHelper
58
+
59
+ Parallel processing with thread pooling.
60
+
61
+ ```ruby
62
+ ConcurrencyHelper.with_parallel_queue( 4 ) do | queue, semaphore |
63
+ queue.push { task_1 }
64
+ queue.push { task_2 }
65
+ end
66
+ ```
67
+
68
+ #### GenericHelper
69
+
70
+ General utility functions including date decoding and retry logic.
71
+
72
+ ```ruby
73
+ date = GenericHelper.decode_date( 'today' )
74
+ GenericHelper.do_retry( max_retries: 3, sleep: 1 ) { risky_operation }
75
+ ```
76
+
77
+ #### BashHelper
78
+
79
+ Safe execution of bash commands.
80
+
81
+ ```ruby
82
+ result = BashHelper.safe_execute( 'ls -la' )
83
+ BashHelper.simple_bash_execute( 'rm', file1, file2, file3 )
84
+ ```
85
+
86
+ #### ComputationsHelper
87
+
88
+ Data manipulation and smoothing functions.
89
+
90
+ ```ruby
91
+ ComputationsHelper.convert_to_incremental_values!( values )
92
+ ComputationsHelper.smooth_line!( values, 5 )
93
+ ```
94
+
95
+ #### DesktopHelper
96
+
97
+ Desktop integration utilities.
98
+
99
+ ```ruby
100
+ DesktopHelper.set_clipboard_text( 'Hello, World!' )
101
+ result = DesktopHelper.display_dialog( 'Are you sure?' )
102
+ DesktopHelper.desktop_notification( 'Task completed' )
103
+ ```
104
+
105
+ #### InteractionsHelper
106
+
107
+ Command-line user interaction helpers.
108
+
109
+ ```ruby
110
+ password = InteractionsHelper.secure_ask( 'Enter password: ' )
111
+ answer = InteractionsHelper.ask_entry( 'Name', 'default_name' )
112
+ choice = InteractionsHelper.ask_entries_with_points( 'Select option', options )
113
+ ```
114
+
115
+ #### MultimediaHelper
116
+
117
+ Audio and multimedia file operations.
118
+
119
+ ```ruby
120
+ MultimediaHelper.play_audio_file( 'song.mp3' )
121
+ MultimediaHelper.normalize_songs( file1, file2 )
122
+ MultimediaHelper.create_m3u_playlist( files, basedir, 'playlist.m3u' )
123
+ ```
124
+
125
+ #### GnomeHelper
126
+
127
+ GNOME desktop environment helpers.
128
+
129
+ ```ruby
130
+ GnomeHelper.set_gnome_background( '/path/to/image.jpg' )
131
+ current = GnomeHelper.get_gnome_background_filename
132
+ ```
133
+
134
+ #### SystemHelper
135
+
136
+ System-level utilities.
137
+
138
+ ```ruby
139
+ cores = SystemHelper.system_cores_number
140
+ files = SystemHelper.find_files( '*.rb', [ '/path' ], file_type: SystemHelper::SEARCH_FILES )
141
+ SystemHelper.open_file( 'document.pdf' )
142
+ ```
143
+
144
+ #### SQLiteLayer
145
+
146
+ Simplified SQLite database operations.
147
+
148
+ ```ruby
149
+ db = SQLiteLayer.new( 'database.db' )
150
+ id = db.insert_values( 'users', { name: 'John', email: 'john@example.com' } )
151
+ results = db.select_all( 'SELECT * FROM users WHERE active = ?', true )
152
+ db.transaction do
153
+ # database operations
154
+ end
155
+ db.close
156
+ ```
157
+
158
+ ## Dependencies
159
+
160
+ - gruff (~> 0.7) - For graphing functionality
161
+ - parseconfig (~> 1.0) - For configuration file parsing
162
+ - highline (~> 2.0) - For secure command-line input
163
+ - sqlite3 (~> 1.4) - For SQLite database operations
164
+
165
+ ## Development
166
+
167
+ After checking out the repo, run `bundle install` to install dependencies.
168
+
169
+ ## License
170
+
171
+ This project is licensed under the GNU General Public License v3.0. See the source files for full license text.
172
+
173
+ ## Author
174
+
175
+ Saverio Miroddi
176
+
177
+ ## Contributing
178
+
179
+ Bug reports and pull requests are welcome on GitHub.
@@ -0,0 +1,80 @@
1
+ =begin
2
+
3
+ <bash_helper.rb> - Part of Sav's APIs.
4
+ Copyright (C) 2011 Saverio Miroddi
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
20
+
21
+ module BashHelper
22
+
23
+ require 'open3'
24
+ require 'shellwords'
25
+
26
+ # REFACTOR: default :out to STDOUT
27
+ #
28
+ def self.safe_execute( cmd, options={} )
29
+ out = options[ :out ]
30
+
31
+ Open3.popen3( cmd ) do | stdin, stdout, stderr, wait_thr |
32
+ exit_status = wait_thr.value.exitstatus
33
+
34
+ output = stdout.read
35
+
36
+ out << output if out
37
+
38
+ if exit_status == 0
39
+ output.chomp unless out
40
+ else
41
+ raise stderr.read.chomp
42
+ end
43
+ end
44
+ end
45
+
46
+ def safe_execute( cmd, options={} )
47
+ BashHelper.safe_execute( cmd, options )
48
+ end
49
+
50
+ # Encode as single-quoted, space-separated series of filenames.
51
+ # Encoding a slash apparently requires four slashes.
52
+ #
53
+
54
+ def simple_bash_execute( command, *files_and_options )
55
+ BashHelper.simple_bash_execute( command, *files_and_options )
56
+ end
57
+
58
+ def BashHelper.simple_bash_execute( command, *files_and_options )
59
+ options = files_and_options.last.is_a?( Hash ) ? files_and_options.pop : {}
60
+ files = files_and_options
61
+
62
+ output = options.has_key?( :out ) ? options[ :out ] : STDOUT
63
+
64
+ files = files.flatten
65
+ command = "#{ command } #{ encode_bash_filenames( *files ) }"
66
+
67
+ safe_execute( command, out: output )
68
+ end
69
+
70
+ def encode_bash_filenames( *files )
71
+ BashHelper.encode_bash_filenames( *files )
72
+ end
73
+
74
+ def BashHelper.encode_bash_filenames( *files )
75
+ quoted_filenames = files.map { | file | file.shellescape }
76
+ quoted_filenames.join( ' ' )
77
+ end
78
+
79
+ end
80
+
@@ -0,0 +1,95 @@
1
+ =begin
2
+
3
+ <computations_helper.rb> - Part of Sav's APIs.
4
+ Copyright (C) 2011 Saverio Miroddi
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
20
+
21
+ module ComputationsHelper
22
+
23
+ # Covnerts to incremental values.
24
+ #
25
+ # Modifies the original data.
26
+ #
27
+ # Example:
28
+ # [ 1, 0, -1, 1 ]
29
+ # becomes:
30
+ # [ 1, 1, 0, 1 ]
31
+ #
32
+ def self.convert_to_incremental_values!( source_values )
33
+ current_sum = 0
34
+
35
+ source_values.map! do | value |
36
+ if value
37
+ current_sum += value
38
+ current_sum
39
+ end
40
+ end
41
+ end
42
+
43
+ SMOOTHING_DATA = {
44
+ 5 => [ 35.0, [ -3, 12, 17, 12, -3 ] ],
45
+ 7 => [ 21.0, [ -2, 3, 6, 7, 6, 3, -2 ] ],
46
+ 9 => [ 231.0, [ -21, 14, 39, 54, 59, 54, 39, 14, -21 ] ],
47
+ }
48
+
49
+ # Reference: http://stackoverflow.com/questions/4388911/how-can-i-draw-smoothed-rounded-curved-line-graphs-c
50
+ #
51
+ # Optimized for readability :-)
52
+ #
53
+ def self.smooth_line!( values, coefficients_number )
54
+ h, coefficients = SMOOTHING_DATA[ coefficients_number ] || raise( 'Wrong number of coefficients' )
55
+
56
+ raise "Smoothing needs at least #{ coefficients.size } values" if values.compact.size < coefficients.size
57
+
58
+ buffer_middle_position = ( coefficients.size + 1 ) / 2 - 1 # 0-based
59
+
60
+ non_empty_positions = []
61
+ original_values = values.clone
62
+
63
+ # The complexity is caused by the presence of nil values.
64
+ # We cycle the array, and fill the buffer with the position of each non-null value encountered.
65
+ # When the buffer is ready, we compute the smoothed value and set it, and remove the first entry
66
+ # from the buffer.
67
+ #
68
+ values.each_with_index do | value, current_position |
69
+ non_empty_positions << current_position if value
70
+
71
+ next if non_empty_positions.size < coefficients.size
72
+
73
+ buffer = non_empty_positions.map { | non_empty_position | original_values[ non_empty_position ] }
74
+ modifying_position = non_empty_positions[ buffer_middle_position ]
75
+
76
+ values[ modifying_position ] = compute_smoothed_point( buffer, coefficients, h )
77
+
78
+ non_empty_positions.shift
79
+ end
80
+
81
+ nil
82
+ end
83
+
84
+ private
85
+
86
+ def self.compute_smoothed_point( buffer, coefficients, h )
87
+ sum = buffer.zip( coefficients ).inject( 0 ) do | current_sum, ( value, coefficient ) |
88
+ current_sum + value * coefficient
89
+ end
90
+
91
+ sum / h
92
+ end
93
+
94
+ end
95
+
@@ -0,0 +1,104 @@
1
+ =begin
2
+
3
+ <concurrency_helper.rb> - Part of Sav's APIs.
4
+ Copyright (C) 2011 Saverio Miroddi
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
20
+
21
+ module ConcurrencyHelper
22
+
23
+ require 'thread'
24
+
25
+ # Parallel working, constrained on the number of threads.
26
+ # Sets Thread.abort_on_exception to true as default.
27
+ #
28
+ # Usage:
29
+ #
30
+ # ConcurrencyHelper.with_parallel_queue( <slots> ) do | queue, semaphore |
31
+ # queue.push { <operation_1> }
32
+ # queue.push { <operation_2> }
33
+ # end
34
+ #
35
+ # or:
36
+ #
37
+ # ConcurrencyHelper.with_parallel_queue( <slots>, :instances => <Enumerable> ) do | instance, semaphore |
38
+ # -> { <operation>( <instance> ) }
39
+ # end
40
+ #
41
+ # or:
42
+ #
43
+ # queue = ParallelWorkersQueue.new( <threads> )
44
+ # queue.push { <operation_1> }
45
+ # queue.push { <operation_2> }
46
+ # queue.join
47
+ #
48
+ # The semaphore is a generic semaphore; it can be used for example to lock when printing information to stdout.
49
+ #
50
+ class ParallelWorkersQueue
51
+
52
+ def initialize( slots, options={} )
53
+ abort_on_exception = ! options.has_key?( :abort_on_exception ) || options[ :abort_on_exception ]
54
+
55
+ @queue = SizedQueue.new( slots )
56
+
57
+ @threads = slots.times.map do
58
+ Thread.new do
59
+ while ( data = @queue.pop ) != :stop
60
+ data[]
61
+ end
62
+ end
63
+ end
64
+
65
+ Thread.abort_on_exception = abort_on_exception
66
+ end
67
+
68
+ def push( &task )
69
+ @queue.push( task )
70
+ end
71
+
72
+ def join
73
+ @threads.each do
74
+ @queue.push( :stop )
75
+ end
76
+
77
+ @threads.each( &:join )
78
+ end
79
+
80
+ end
81
+
82
+ def self.with_parallel_queue( slots, options={} )
83
+ instances = options[ :instances ]
84
+ abort_on_exception = options[ :abort_on_exception ]
85
+
86
+ queue = ParallelWorkersQueue.new( slots, :abort_on_exception => abort_on_exception )
87
+ semaphore = Mutex.new
88
+
89
+ if instances
90
+ instances.each do | instance |
91
+ proc = yield( instance, semaphore )
92
+
93
+ raise "The value returned is not a Proc!" unless proc.is_a?( Proc )
94
+
95
+ queue.push( &proc )
96
+ end
97
+ else
98
+ yield( queue, semaphore )
99
+ end
100
+
101
+ queue.join
102
+ end
103
+
104
+ end
@@ -0,0 +1,128 @@
1
+ =begin
2
+
3
+ <configuration_helper.rb> - Part of Sav's APIs.
4
+ Copyright (C) 2011 Saverio Miroddi
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
20
+
21
+ module ConfigurationHelper
22
+
23
+ require 'rubygems'
24
+ require 'parseconfig'
25
+ require 'openssl'
26
+ require 'base64'
27
+
28
+ CONFIGURATION_FILE = File.expand_path( '.sav_scripts', '~' )
29
+ PASSWORD_KEY_FILE = File.expand_path( '.sav_scripts_key', '~' )
30
+
31
+ # The encryption is intentionally weak.
32
+ #
33
+ PASSWORD_CIPHER = 'des'
34
+ PASSWORD_KEY = IO.read( PASSWORD_KEY_FILE ).chomp
35
+
36
+ # Loads the configuration from a file, for a given group.
37
+ #
38
+ # Returns a Hash with an extra method: :path(key), which interprets the value as an absolute path if it starts with
39
+ # a slash (/), and as a relative to home if it doesn't.
40
+ #
41
+ # options:
42
+ # :group manually specify the group; defaults to the calling script.
43
+ # :sym_keys keys as symbols
44
+ # :file configuration filename
45
+ #
46
+ def self.load_configuration( options={} )
47
+ $stderr.puts ">>> MIGRATE TO SIMPLE_SCRIPTING::CONFIG!"
48
+
49
+ raise "Change argument to :group if needed (when the filename is different from the group)" if options.is_a?( String )
50
+
51
+ group = options[ :group ] || File.basename( $0 ).chomp( '.rb' )
52
+ sym_keys = options[ :sym_keys ]
53
+ configuration_file = options[ :file ] || CONFIGURATION_FILE
54
+
55
+ configuration = ParseConfig.new( configuration_file )[ group ]
56
+
57
+ raise "Group not found in configuration: #{ group }" if configuration.nil?
58
+
59
+ if configuration[ 'password' ]
60
+ configuration[ 'password' ] = decrypt( configuration[ 'password' ], PASSWORD_KEY, PASSWORD_CIPHER, :base_64_decoding => true )
61
+ end
62
+
63
+
64
+ configuration = Hash[ configuration.map{ | key, value | [ key.to_sym, value ] } ] if sym_keys
65
+
66
+ def configuration.path( key )
67
+ raw_value = self[ key ]
68
+ raw_value.start_with?( '/' ) ? raw_value : File.expand_path( raw_value, '~' )
69
+ end
70
+
71
+ configuration
72
+ end
73
+
74
+ # Shortcut for the previous method.
75
+ #
76
+ # Returns a String when querying only one key, otherwise an Array.
77
+ #
78
+ def self.load_configuration_values( *keys )
79
+ configuration = load_configuration
80
+
81
+ values = keys.inject( [] ) do | current_values, key |
82
+ current_values << configuration[ key ]
83
+ end
84
+
85
+ if values.size == 1
86
+ values.first
87
+ else
88
+ values
89
+ end
90
+ end
91
+
92
+ def self.encrypt( plaintext, key, algo, options={} )
93
+ base_64_encoding = !! options[ :base_64_encoding ]
94
+
95
+ cipher = OpenSSL::Cipher::Cipher.new( algo )
96
+ cipher.encrypt
97
+
98
+ iv = cipher.random_iv
99
+
100
+ cipher.key = key
101
+ cipher.iv = iv
102
+
103
+ ciphertext = iv + cipher.update( plaintext ) + cipher.final
104
+
105
+ ciphertext = Base64.encode64( ciphertext ) if base_64_encoding
106
+
107
+ ciphertext
108
+ end
109
+
110
+ def self.decrypt( ciphertext, key, algo, options={} )
111
+ puts ">>> ConfigurationHelper.decrypt should have key and algo as options"
112
+
113
+ base_64_decoding = !! options[ :base_64_decoding ]
114
+
115
+ ciphertext = Base64.decode64( ciphertext ) if base_64_decoding
116
+
117
+ cipher = OpenSSL::Cipher::Cipher.new( algo )
118
+ cipher.decrypt
119
+
120
+ cipher.key = key
121
+
122
+ cipher.iv = ciphertext.slice!( 0, cipher.iv_len )
123
+ plaintext = cipher.update( ciphertext ) + cipher.final
124
+
125
+ plaintext
126
+ end
127
+
128
+ end
@@ -0,0 +1,50 @@
1
+ =begin
2
+
3
+ <desktop_helper.rb> - Part of Sav's APIs.
4
+ Copyright (C) 2011 Saverio Miroddi
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
20
+
21
+ require_relative 'system_helper'
22
+ require 'shellwords'
23
+
24
+ module DesktopHelper
25
+ CLIPBOARD_COMMAND = "xi"
26
+
27
+ def set_clipboard_text( text )
28
+ IO.popen(CLIPBOARD_COMMAND, 'w' ) { | io | io << text }
29
+ end
30
+
31
+ # Displays an OK/Cancel dialog.
32
+ #
33
+ # Returns true for OK, false for Cancel/Esc.
34
+ #
35
+ def display_dialog( text )
36
+ quoted_text = '"' + text.gsub( '"', '\"' ).gsub( "'", "'\\\\''" ) + '"'
37
+
38
+ if SystemHelper.mac?
39
+ `osascript -e 'tell app "System Events" to display dialog #{ quoted_text }'`.strip == 'button returned:OK'
40
+ else
41
+ system( "zenity --question --text=#{ quoted_text }" )
42
+ end
43
+ end
44
+
45
+ def desktop_notification(text)
46
+ `notify-send #{text.shellescape}`
47
+ end
48
+
49
+ end
50
+