DEV Community


Posted on • Updated on

Create new ruby gem - Moriarty part-3

GitHub logo decentralizuj / moriarty

Moriarty - Tool to check social networks for available username

In past series I created moriarti.rb (responsible for everything). I love to work with ruby scripts, because of ability to write powerful/useful code in short time. But this only works for "small projects". If we want to extend script after some time, It'll become pain in the ass. For this we have ruby-gems, which are easily extendible even for different developer.

  • Performance advice:

Each require make ruby slower. Do not create gem with a few lines of code and many requires. For that case, make a script file and use it until you want to extend it.

Create Ruby Gem

First thing is to create directory where you want your ruby gem. In root directory create bin and lib folders. That's same for every gem.

  # Moriarty directory tree

moriarty |
         | - bin/ 
         | - lib/
Enter fullscreen mode Exit fullscreen mode

Lib folder handle our scripts, and bin (binaries) handle executable files. Now let's go back to the root of our gem. We need few more files to add there.

  # Moriarty root files

Rakefile  # important!
Enter fullscreen mode Exit fullscreen mode

Let's talk about README file first. It's important to create and to write few lines about gem. Even if you do not plan to opensource it, or share with others, after some time you'll forget what you did 6 month ago in which file. To avoid time and nerve lose, don't be lazy and create it. Same thing with comments in source code - it's not must to go in details, just something to help you remember those details.

Pro TIP:

Too many comments make source code unreadable. make comments short, write code to be readable. I wrote all this comments for purpose of learning. If you want to add comments in right way, learn RDoc. I will write another article about generating RDoc from gem's comments.

Gemfile handle our dependencies from .gemspec file, so bundler know what to install:

# Moriarty Gemfile

source ''

# this line tell bundler where to look
Enter fullscreen mode Exit fullscreen mode

Gemspec file contain everything about our gem. Dependencies, name, description, version, site... but also all files that our gem use. Specification also have data about executable files, so when we install our gem locally, we can run it moriarty USERNAME instead of ruby bin/moriarty from root directory.

This is a reason why all objects should define path when work with files, because working directory is not always script directory.

# Gemspec file (moriarty.gemspec)

# frozen_string_literal: true do |s|        = 'name'
  s.version     = '0.1.0'
  s.summary     = 'short description'
  s.description = <<~DESC
    # Write long description here.
  s.authors  = ['...']
  s.homepage =  '...'
  s.license  =  '...'

  s.metadata['homepage_uri']    = '...'
  s.metadata['source_code_uri'] = '...'
  s.metadata['bug_tracker_uri'] = '...'

  # here you add all files from your gem
  s.files = ['', 'LICENSE']

  # directory with executable
  s.bindir = 'bin'

  # one or more executable files
  s.executables   = ['...']

  # directory with our files
  s.require_paths = ['lib']

  # define gems needed to work
  s.add_runtime_dependency 'colorize', '~> 0.8.1'
  s.add_runtime_dependency 'rest-client', '~> 2.1.0'
  s.add_runtime_dependency 'nokogiri', '~> 1.11.2'

  # define gems needed for development
  s.add_development_dependency 'bundler', '~> 2.2.9'
  s.add_development_dependency 'rake', '~> 13.0.3'
Enter fullscreen mode Exit fullscreen mode

With all of this, we're ready to create files that will handle our code. In lib/ we will create file moriarty.rb, and that file will require all other files. This way our executable (or other gem) need to require just one file for everything to work.

# lib/moriarty.rb

#!/usr/bin/env ruby
require 'rest-client'
require 'colorize'
require 'nokogiri'

require_relative 'moriarty/cli'
Enter fullscreen mode Exit fullscreen mode

Here I required dependencies from gemspec. I explained in first article why I use rest-client. I also required relative file moriarti/cli, and we need to create it. Don't think about it too much for now.

From our script file I will copy almost all methods. I will not copy #find! and code we used to run script. Only code to construct username, url, send request and receive response.

# Main class to get data and execute request
# Methods: [ #new, #go, #success?, #make_url, #url=, #user= ]
# Attributes:
#  :user     = :moriarty     => 'moriarty'
#  :url      = '' => ''
#  :response => [.code, .headers, .body]  -> restclient#get
#  :html     => scrapped HTML if success? -> nokogiri#html

class Moriarty

  attr_reader :url, :user, :response, :html

  # Set username and site for search request
  # exclude 'https', #make_url will add it for you
  # To use different protocol, set it as third parameter
  # @jim = 'moriarty', '', :http )
  #  => @jim.user == 'moriarty'
  #  => @jim.url  == ''

  def initialize( name = '', site = '', type = :https )
    @user = name.to_s
    @url  = make_url site, type

  # execute request (args are optional)
  # @jim.go site: '', user: 'mynickname'
  #  => true/false
  #  -> @jim.response (.code, .headers, .body)
  #  -> @jim.html (page HTML if request success)

  def go( opt = {} )
    opt[:user] ||= @user
    url = opt[:site].nil? ? @url : make_url(opt[:site])
    uri = url + opt[:user]
    @response = RestClient.get uri
    @html     = Nokogiri::HTML @response
    return @success = true
    return @success = false

  alias search go

  # create URL from site name, add 'https' if needed
  # @jim.make_url ''
  #  => ''

  def make_url( link, prot = :https )
    prot = nil if link.to_s.start_with? prot.to_s
    url  = prot.nil? ? link.to_s : prot.to_s + '://' + link.to_s
    url += '/' unless url.end_with?('/')
    return url

  # Set URL from site name and protocol(optional)
  # @jim.url = ''
  #  => @jim.url == ''

  def url=( link, start = :https )
    @url = make_url link, start

  # Set username from string or :symbol
  # @jim.user = :moriarty
  #  => @jim.user == 'moriarty'

  def user=( name )
    @user = name.to_s

  # Check does request succeeded or not
  # @jim.success?
  #  => true/false

  def success?
    @success == true
Enter fullscreen mode Exit fullscreen mode

Now it's time to create lib/moriarty directory, and cli.rb inside. That file I will use for methods we need in CLI (in future versions, that will be removed).

class Moriarty

 class << self

  # Moriarty.find! 'smartuser'      
  #  => [FOUND!] if username 'smartuser' is free on github
  # Moriarty.find! :stupiduser, '', :hunt
  #  => [FREE!] if user 'stupiduser' is registered on facebook

  def find!( username, web = '', type = :search )

    @jim = username.to_s, web.to_s

    name = print_name(username).to_s
    site = print_url(web).to_s

    when type.to_sym == :hunt && @jim.success?
      p2(" #{name}", :cyan, :bold)
      p2(" found on >> ") 
      puts p2(site, :cyan, :bold)
    when type.to_sym == :hunt && !@jim.success?
      p1('-', :red, :red)
      p2(" #{name} fail on ", :red)
      puts p2(site, :red)
   when @jim.success?
      p1('-', :red, :red)
      p2(" #{name} is taken on ", :red)
      puts p2(site, :red)
      p2(" #{name}", :cyan, :bold)
      p2(" is free on >> ")
      puts p2(site, :cyan, :bold)

  # #hunt! is alias for #find! with :hunt option
  # Check is user 'stupiduser' registered on instagram
  #  -> Moriarty.hunt! 'stupiduser', ''

  def hunt!( name, site = '', type = :hunt)
    find! name, site, type

  # Remove extensions from domain name
  # Moriarty.print_url('')
  #  => 'github'

  def print_url( site )
    site, name = site.to_s, ''
    if site.start_with?('http')
      site.gsub!("https://", '') or site.gsub!("http://", '')
    site.gsub!("www.", '') if site.start_with?('www.')
    ext = site.to_s.split('.').last
    name = site.gsub(".#{ext}", '')
    name = name.split('/').first if ext.size < 5
    return name.capitalize

  # Remove extensions from username
  # Moriarty.print_name('@moriarty')
  #  => 'moriarty'

  def print_name( name )
    name.gsub!('@','') if name.to_s.start_with?('@')
    name.gsub!('#','') if name.to_s.start_with?('#')
    name.gsub!('/u/','') if name.to_s.start_with?('/u/')
    return name

  def p1( title, color1 = :'light_green', color2 = :cyan, type = :bold )
    str = ' ['.colorize(color2) + title.to_s.colorize(color1) + ']'.colorize(color2)
    str = str.bold if type == :bold
    print str

  def p2( title, color = :cyan, type = :regular)
    str = title.colorize(color)
    str = str.bold if type == :bold
    print str
Enter fullscreen mode Exit fullscreen mode

In cli.rb we again define class Moriarty, but this time accept only self methods. Note that you can create as many files as you want, just require them in main lib/moriarty.rb, and add in .gemspec.

Now we have our classes, but we need a way to execute it in terminal. For that we will create bin/moriarty:

#!/usr/bin/env ruby

# require 'lib/moriarty', which will require 'moriarti/cli'

require_relative '../lib/moriarty'

  # Simple way to try first version
  # accept '--hunt' as argument

  hunt = ARGV.include?('--hunt') ? :hunt : :search
  message = hunt == :hunt ? "Starting hunt on user -> " : "Starting search for free username -> "

  # If no args, or include '-h' or '--help', print banner
  # If use '--hunt', do not search for '--hunt' username

  if ARGV.empty? or ARGV.include? '-h' or ARGV.include? '--help'

    puts "\nSearch social networks for free username".cyan
    puts " $ ".white + "ruby bin/moriarty sherlock watson".green
    puts "\nRun with --hunt to search for registered users".cyan
    puts " $ ".white + "ruby bin/moriarty sherlock watson --hunt\n".green


    system 'clear' or system 'cls'
    start_time =

    ARGV.each do |user|
      next if user == '--hunt'

      Moriarty.p2(message, :cyan, :bold)
      puts; puts

      Moriarty.find! user,         '',            hunt
      Moriarty.find! user,         '', hunt
      Moriarty.find! "@#{user}",   '',                hunt
      Moriarty.find! "@#{user}",   '',            hunt
      Moriarty.find! "/u/#{user}", '',            hunt
      Moriarty.find! user,         '',           hunt
      Moriarty.find! user,         '',          hunt
      Moriarty.find! user,         '',         hunt
      Moriarty.find! "@#{user}",   '',            hunt

    end_time =
    counter  = end_time - start_time

    sec = ' second'
    sec += 's' unless counter.to_s.end_with?('1')

    Moriarty.p2('Finished in ->', :cyan, :bold)
    Moriarty.p2(sec, :cyan, :bold)
    puts; puts
Enter fullscreen mode Exit fullscreen mode

Don't forget to give this file executable permissions:

sudo chmod +x bin/moriarty
Enter fullscreen mode Exit fullscreen mode

Almost done! We need to add all those files in .gemspec:

  s.files = ['bin/moriarty', 'lib/moriarty.rb', 'lib/moriarty/cli.rb', 'moriarty.gemspec',
             '', 'LICENSE']
  s.bindir = 'bin'
  s.executables = ['moriarty']
Enter fullscreen mode Exit fullscreen mode

I added license file here (MIT in my case), that's all up to you. The easiest way is to create repository on GitHub and choose license.

To be able to use rake tasks, open Rakefile and add:

require 'bundler/gem_tasks'
Enter fullscreen mode Exit fullscreen mode

Now you can use your gem from terminal:

ruby bin/moriarti sherlock --hunt
Enter fullscreen mode Exit fullscreen mode

Or compile into gem:

bundle exec rake build
Enter fullscreen mode Exit fullscreen mode

To push into rubygems:

# register on rubygems

gem push pkg/moriarti-0.1.0.gem
Enter fullscreen mode Exit fullscreen mode

Or upload to github and use from git:

# Gemfile of some app

require 'moriarty', '~> 0.1.0', git: ''
Enter fullscreen mode Exit fullscreen mode

Next article will be about refactoring gem and terminal use (options, colors, output...)

Top comments (0)