hulaki 0.1 → 1.0.2

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.
@@ -1,22 +1,18 @@
1
1
  class Hulaki::ContactParser
2
-
3
- DefaultFilePath = '~/hulaki/contact.csv'
2
+ DEFAULT = File.expand_path('~/hulaki/contact.csv')
3
+ @@default_file_path = DEFAULT
4
4
 
5
5
  def perform
6
- parse
7
- end
8
-
9
- private
10
- def parse
11
6
  options = {:strings_as_keys => true, :downcase_header => true}
12
- SmarterCSV.process(contact_file, options)
7
+ SmarterCSV.process(@@default_file_path, options)
13
8
  rescue Errno::ENOENT
14
- puts 'Contact file not found. Make sure there is file'
15
- puts @file_path
16
- nil
9
+ raise Hulaki::InvalidFilePath
17
10
  end
18
11
 
19
- def contact_file
20
- @file_path = File.expand_path(DefaultFilePath)
12
+ private
13
+ class << self
14
+ def default_file_path=(path)
15
+ @@default_file_path = File.expand_path(path)
16
+ end
21
17
  end
22
18
  end
@@ -0,0 +1,90 @@
1
+ require 'clipboard'
2
+
3
+ class Hulaki::Core
4
+ def initialize(config)
5
+ @config = config
6
+ end
7
+
8
+ def perform
9
+ if @config.search_keyword
10
+ handle_search_and_clipboard
11
+ else
12
+ handle_messaging
13
+ end
14
+ rescue CSV::MalformedCSVError
15
+ puts 'Your contact.csv file is invalid or has invalid/malformed UTF characters.'.red
16
+ puts 'Please use a valid CSV file.'.red
17
+ end
18
+
19
+ private
20
+ def handle_messaging
21
+ puts '~' * 100
22
+ puts 'Welcome to Hulaki : Your best companion! to make your day great.'
23
+ puts '~' * 100
24
+
25
+ # It supports multiple recipients like two phonenumbers separated by commas `,`
26
+ @config.to.each do |recipient|
27
+ if is_phone?(recipient)
28
+ handle_sms(recipient)
29
+ elsif is_email?(recipient)
30
+ handle_email(recipient)
31
+ end
32
+ end
33
+
34
+ puts '~' * 100
35
+ puts @config.message
36
+ puts '~' * 100
37
+ end
38
+
39
+ def is_phone?(number)
40
+ Hulaki::SmsValidator.is_phone_number?(number)
41
+ end
42
+
43
+ def is_email?(email)
44
+ Hulaki::EmailValidator.is_email?(email)
45
+ end
46
+
47
+ def handle_email(recipient)
48
+ puts "Sending email to #{recipient}"
49
+ email_handler = Hulaki::Mailer.new(
50
+ {
51
+ :to => recipient,
52
+ :message => @config.message,
53
+ :subject => @config.subject,
54
+ :from => @config.from
55
+ }).deliver
56
+ puts "Email sent to `#{recipient}`. from `#{get_sender}`"
57
+ end
58
+
59
+ def handle_sms(recipient)
60
+ puts "Sending SMS to #{recipient}"
61
+ sms_handler = Hulaki::SmsHandler.new(
62
+ {
63
+ :to => recipient,
64
+ :message => @config.message,
65
+ :gateway => @config.gateway,
66
+ :from => @config.from
67
+ })
68
+ if sms_handler.send
69
+ puts "SMS sent successfully to #{recipient} using `#{sms_handler.gateway.class}` gateway"
70
+ end
71
+ end
72
+
73
+ def get_sender
74
+ @config[:from] || Hulaki::Config['email']['from']
75
+ end
76
+
77
+ def handle_search_and_clipboard
78
+ puts
79
+ response = Hulaki::SearchEngine.new.perform(@config.search_keyword)
80
+ Hulaki::Presenter.new(response).display if response
81
+
82
+ if @config.copy_phone_number
83
+ number = response[0][0]['phone_1___value'].gsub(' ', '')
84
+ Clipboard.copy number
85
+ puts "Number '#{number.underline}' is copied to your clipboard"
86
+ puts
87
+ end
88
+ end
89
+
90
+ end
@@ -2,10 +2,10 @@ class Hulaki::EmailValidator
2
2
  EmailRegex = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
3
3
 
4
4
  def initialize(options = {})
5
- @sender_email = options[:from]
5
+ @sender_email = options[:from]
6
6
  @reciepient_email = options[:to]
7
- @message = options[:message]
8
- @errors = {}
7
+ @message = options[:message]
8
+ @errors = {}
9
9
  end
10
10
 
11
11
  def validates_presence
@@ -0,0 +1,73 @@
1
+ require 'mail'
2
+
3
+ class Hulaki::Mailer
4
+ attr_reader :params, :config
5
+
6
+ TEMPLATE_PATH = ENV['template_path'] || '~/hulaki/template.html.erb'
7
+
8
+ def initialize(params={})
9
+ @params = params
10
+ @config = Hulaki::Config["email"]
11
+ end
12
+
13
+ def deliver
14
+ # Fixme: validation has no purpose for the time being
15
+ validate(@reciever, config["from"], params[:message])
16
+ configure_defaults
17
+ prepare_email
18
+ @mail.deliver
19
+ "sent"
20
+ end
21
+
22
+ private
23
+ def prepare_email
24
+ email_body = params[:message]
25
+
26
+ # sender set from command-line will take precedence over that in `config.yml`
27
+ from = params[:from] || config["from"]
28
+ to = params[:to]
29
+ subject = params[:subject]
30
+
31
+ if config['use_template'] == true
32
+ content = ERB.new(File.read(File.expand_path(TEMPLATE_PATH))).result(binding)
33
+ else
34
+ content = email_body
35
+ end
36
+
37
+ @mail = Mail.new do
38
+ to to
39
+ from from
40
+ subject subject
41
+
42
+ html_part do
43
+ content_type 'text/html; charset=UTF-8'
44
+ body content
45
+ end
46
+ end
47
+ end
48
+
49
+ def configure_defaults
50
+ # instance variables are not available inside the block below; so
51
+ # localizing the variable
52
+ env = @config
53
+ delivery_mode = ENV['mode'] == 'test' ? 'test' : :smtp
54
+ Mail.defaults do
55
+ delivery_method delivery_mode,
56
+ {
57
+ :address => env["address"],
58
+ :port => env["port"],
59
+ :domain => env["domain"],
60
+ :user_name => env["user_name"],
61
+ :password => env["password"],
62
+ :authentication => env["authentication"],
63
+ :enable_starttls_auto => true
64
+ }
65
+ end
66
+ end
67
+
68
+ # returns [Hash] Error
69
+ def validate(reciever, sender, message)
70
+ validator = Hulaki::EmailValidator.new(from: reciever, to: sender, message: message)
71
+ validator.validates_format && validator.validates_presence
72
+ end
73
+ end
@@ -1,16 +1,18 @@
1
1
  require_relative 'recursive_ostruct'
2
2
  require 'fileutils'
3
3
  require 'optparse'
4
+ # require File.expand_path('../string_modifier', __FILE__)
4
5
  require_relative 'presenter'
5
6
  class Hulaki::OptionParser
6
7
  def initialize
7
8
  @config = RecursiveOstruct.ostruct(
8
9
  {
9
10
  to: [],
10
- from: [],
11
+ from: nil,
11
12
  subject: 'Mic testing',
12
13
  message: 'sample message',
13
- command: 'help'
14
+ command: 'help',
15
+ gateway: nil
14
16
  })
15
17
  end
16
18
 
@@ -21,96 +23,104 @@ class Hulaki::OptionParser
21
23
 
22
24
  def options
23
25
  OptionParser.new do |opts|
24
- opts.banner = [
25
- 'Usage: ',
26
- '------- Search --------',
27
- '$ hulaki -s search-string',
28
- '# Example: Hulaki features fuzzy search',
29
- '# $ hulaki -s smithjohn',
30
- '# $ hulaki -s johsmith',
31
- '# $ hulaki -s smijohnth',
32
- '',
33
- '------- SMS --------',
34
- '$ hulaki -t +977xxxxxxxxxx -m "Message to be sent"',
35
- '------- EMAIL --------',
36
- ].join("\n")
26
+ opts.banner =
27
+ "Usage: \n"\
28
+ "------- Search --------\n"\
29
+ "$ hulaki -s search-string\n"\
30
+ "# Example: Hulaki features fuzzy search\n"\
31
+ "$ hulaki -s smithjohn\n"\
32
+ "$ hulaki -s johsmith\n"\
33
+ "$ hulaki -s smijohnth\n"\
34
+ "\n"\
35
+ "------- Copy PhoneNumber to ClipBoard -----------\n"\
36
+ "$ hulaki -s smithjohn -c \n"\
37
+ "# You will see phone_number of the top result copied to ClipBoard \n"\
38
+ "# Number '+97798xxx66455' is copied to your clipboard\n"\
39
+ "\n"\
40
+ "------- SMS --------\n"\
41
+ "$ hulaki -t +977xxxxxxxxxx -m \"Message to be sent\"\n"\
42
+ "\n"\
43
+ "# You can even select a specific SMS Gateway\n"\
44
+ "$ hulaki -t +977xxxxxxxxxx -m \"Message to be sent\" -g nexmo\n"\
45
+ "$ hulaki -t +61xxxxxxxxxx -m \"Message to be sent\" -g twilio\n"\
46
+ "$ hulaki -t +1xxxxxxxxxx -m \"Message to be sent\" -g sparrow\n"\
47
+ "\n"\
48
+ "# You can even broadcast SMSes selecting a specific SMS Gateway\n"\
49
+ "# However, keep in mind that multiple SMS request will go to the server\n"\
50
+ "$ hulaki -t +977xxxxxxxxxx,+9779832xxxxxx -m \"Message to be sent\" -g nexmo\n"\
51
+ "\n"\
52
+ "# You can also change the name that appears on recipient's Phone using `-f` switch. This only works with Nexmo\n"\
53
+ "$ hulaki -t +977xxxxxxxxxx,+9779832xxxxxx -m \"Message to be sent\" -g nexmo -f \"Hero Dai!\"\n"\
54
+ "\n"\
55
+ "------- EMAIL --------\n"\
56
+ "$ hulaki -t someone@example.com -S \"Subject of the email\" -m \"Message to be sent\"\n"\
57
+ "$ hulaki -t someone@example.com --subject \"Subject of the email\" -m \"Message to be sent\"\n"\
58
+ "\n"\
59
+ "# You can even broadcast emails, i.e. mutiple recipients\n"\
60
+ "# However, keep in mind that multiple SMTP request will go to the server. No `CC`, `BCC` will be used\n"\
61
+ "$ hulaki -t someone@example.com,nextperson@email.com --subject \"Subject of the email\" -m \"Message to be sent\"\n"\
62
+ "\n"\
63
+ "# You can also change your sender id using `-f` switch\n"\
64
+ "$ hulaki -t someone@example.com -S \"Subject of the email\" -m \"Message to be sent\" -f \"My Name<anonymous@example.com>\" \n"\
65
+ "\n"\
66
+ "------- EMAIL TEMPLATES --------\n"\
67
+ "# You are allowed to have an Email template in HTML format at `~/hulaki/template.html.erb` which\n"\
68
+ "# will be copied when you use `-i` switch. If you have `use_template` setting set to `true` then only\n"\
69
+ "# you will be able to use the template\n"\
70
+ "$ hulaki -t someone@example.com -S \"Subject of the email\" -m \"Messagopts.to_se to be sent\"\n"
71
+
72
+
37
73
 
38
74
  opts.separator ''
39
75
  opts.separator 'Specific options:'
40
76
 
41
- opts.on('-t x,y,z', '--to x,y,z', Array, 'list of recipient, can be') do |list|
42
- @config.to = list
77
+ # This can be list of emails or phonenumbers separated by commas `,`
78
+ opts.on('-t x,y,z', '--to x,y,z', Array, 'list of recipient, can be') do |recipient_list|
79
+ @config.to = recipient_list
43
80
  end
44
81
 
45
- opts.on('-m [Message]', '--message [Message]', String, 'Message to be sent to recipient') do |msg|
46
- @config.message = msg
82
+ opts.on('-m [Message]', '--message [Message]', String, 'Message to be sent to recipient') do |message|
83
+ @config.message = message
47
84
  end
85
+ "\n"\
48
86
 
49
- opts.on('-S [Subject]', '--subject [Subject]', String, 'Subject to email') do |sub|
50
- @config.subject = sub
87
+ opts.on('-S [Subject]', '--subject [Subject]', String, 'Subject to email') do |subject|
88
+ @config.subject = subject
51
89
  end
52
90
 
53
- opts.on('-f x,y,z', '--from x,y,z', Array, 'Help / Examples') do |sender_list|
54
- @config.to = sender_list
91
+ opts.on('-c', '--copy', nil, 'Copy phone-number at top to ClipBoard; Linux users need to install `xclip`.') do
92
+ @config.copy_phone_number = true
55
93
  end
56
94
 
57
- opts.on('-s [name/contact]', '--search [name/contact]', String, 'Search keyword') do |word|
58
- response = Hulaki::SearchEngine.new.perform(word)
59
- Hulaki::Presenter.new(response).display if response
60
- exit
95
+ opts.on('-g [Gateway Name]', '--gateway [Gateway Name]', String, 'Name of the gateway, `nexmo`, `twilio`, `sparrow` are currently supported') do |gateway|
96
+ @config.gateway = gateway
97
+ end
98
+
99
+ opts.on('-f [Sender]', '--from [Sender]', String, "name <email> | PhoneNumber; this will take precedence over `from` in `config.yml`.") do |sender|
100
+ @config.from = sender
101
+ end
102
+
103
+ opts.on('-s [name/contact]', '--search [name/contact]', String, 'Search keyword') do |search_keyword|
104
+ @config.search_keyword = search_keyword
61
105
  end
62
106
 
63
107
  # ----------------------------------------------------------------------
64
108
  opts.on('-h', '--help', 'Help / Examples') do
65
- Hulaki::Logger.log opts.banner
109
+ puts Utils.present(opts)
66
110
  exit
67
111
  end
68
112
 
69
113
  opts.on('-l', '--list', 'list all the options available') do
70
- puts opts
114
+ puts Utils.present(opts)
71
115
  exit
72
116
  end
73
117
 
74
- opts.on('-i', '--install', 'Creates ~/hulaki/config.yml') do
75
- create_dir
76
- start_copying_file
118
+ opts.on('-i', '--install', 'Creates ~/hulaki/config.yml, `template.html.erb`. Will ask you if have to replace them') do
119
+ Utils.install_dependencies
120
+ Utils.create_dir
121
+ Utils.start_copying_file
77
122
  exit
78
123
  end
79
124
  end
80
125
  end
81
-
82
- def create_dir
83
- FileUtils.mkdir(File.expand_path('~/hulaki'))
84
- rescue Errno::EEXIST
85
- puts 'Directory already exists.'
86
- end
87
-
88
- def start_copying_file
89
- this_file = __FILE__
90
- file_path = File.expand_path('../../lib/hulaki/config/config_sample.yml',
91
- File.dirname(this_file))
92
- desc_file = File.expand_path('~/hulaki/config.yml')
93
- if File.exist?(desc_file)
94
- puts "Looks like the file '#{desc_file}' already exists."
95
- puts 'shall we forcefully override the file?(yes/no)'
96
- handle_conflict(file_path, desc_file)
97
- else
98
- copy_file(file_path, desc_file)
99
- end
100
- end
101
-
102
- def handle_conflict(file_path, desc_file)
103
- input = gets().chomp()
104
- if %w{yes y}.include?(input.downcase)
105
- copy_file(file_path, desc_file)
106
- else
107
- puts 'You choose to leave it as it is.'
108
- end
109
- end
110
-
111
- def copy_file(file_path, desc_file)
112
- puts "Creating file '~/hulaki/config.yml' ..."
113
- FileUtils.cp(file_path, desc_file)
114
- puts "Created file '~/hulaki/config.yml' ..."
115
- end
116
126
  end
@@ -1,17 +1,23 @@
1
+ require 'terminal-table'
1
2
  class Hulaki::Presenter
2
3
  def initialize(data)
3
- @data = [data].flatten
4
+ no_of_results = Hulaki::Config['search'] && Hulaki::Config['search']['no_of_results'] || 10
5
+ @data = data[0..(no_of_results - 1)]
4
6
  end
5
7
 
6
8
  def display
7
- puts '~' * 100
8
- puts 'Search Results'
9
- puts '~' * 100
10
- @data[0..5].each do |row|
11
- email = row.fetch('e_mail_1___value', 'N/A') rescue 'N/A'
12
- phone_number = row.fetch('phone_1___value', 'N/A') rescue 'N/A'
13
- puts "name: #{row['name']}, phone: #{phone_number}, email: #{email}" rescue nil
9
+ data = @data.map(&:first).map.with_index do |row, index|
10
+ [index+1, row['name'].bold, row['phone_1___value'].to_s.green, row['phone_2___value'], row['email']]
14
11
  end
15
- puts '~' * 100
12
+
13
+ table = Terminal::Table.new :title => "Hulaki #{Hulaki::VERSION}",
14
+ :headings => ['S.N', 'Name', 'Phone 1', 'Phone 2', 'Email'],
15
+ :rows => data
16
+
17
+ table.align_column(2, :right)
18
+ table.align_column(3, :right)
19
+ table.align_column(4, :right)
20
+
21
+ puts table
16
22
  end
17
23
  end
@@ -1,4 +1,16 @@
1
1
  require 'ostruct'
2
+ # = RecursiveOstruct, a tool to convert every hash inside a Hash/Array to OpenStruct object
3
+ # One of the benifit is, it will respond to very sort of messages like
4
+ # Example:
5
+ #
6
+ # newHash = {'a' => 12}
7
+ # wrappedHash = RecursiveOstruct.new(newHash)
8
+ # wrappedHash.a # => 12 : a valid call
9
+ # wrappedHash['a'] # => 12 : a valid call
10
+ # wrappedHash[:a] # => 12 : a valid call
11
+ #
12
+ # It is a option for those who do not want to use Rails::ActiveSupport's `HashWithIndifferentAccess`
13
+ #
2
14
  class RecursiveOstruct
3
15
  def self.ostruct(object)
4
16
  if object.is_a?(Hash)