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 +7 -0
- data/README.md +179 -0
- data/lib/sapis/bash_helper.rb +80 -0
- data/lib/sapis/computations_helper.rb +95 -0
- data/lib/sapis/concurrency_helper.rb +104 -0
- data/lib/sapis/configuration_helper.rb +128 -0
- data/lib/sapis/desktop_helper.rb +50 -0
- data/lib/sapis/generic_helper.rb +241 -0
- data/lib/sapis/gnome_helper.rb +36 -0
- data/lib/sapis/graphing_helper.rb +202 -0
- data/lib/sapis/interactions_helper.rb +148 -0
- data/lib/sapis/multimedia_helper.rb +281 -0
- data/lib/sapis/sqlite_layer.rb +166 -0
- data/lib/sapis/system_helper.rb +141 -0
- data/lib/sapis/version.rb +3 -0
- data/lib/sapis.rb +33 -0
- metadata +141 -0
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
|
+
|