<repo_name>dragosboca/dotmanager<file_sep>/lib/dotmanager/repo.rb
require 'dotmanager/config'
require 'dotmanager/util'
require 'git'

module Dotmanager
   class Repo
      extend self

      def add_file(filename)
         config = Config.instance
         repobase = config[:base_repo]
         g = Git.open repobase, :log => (Logger.new STDOUT)
         g.add filename # add the file
         config.files.each do |dbfile|
            g.add dbfile
         end # also add the database in the same commit
         g.commit "add #{filename} to the database"
      end
      
      def remove_file(filename)
         config = Config.instance
         repobase = config[:base_repo]
         g = Git.open repobase, :log => (Logger.new STDOUT)
         g.remove filename # add the file
         config.files.each do |dbfile|
            g.add dbfile
         end # also add the database in the same commit
         g.commit "remove #{filename} from the database"
      end
      
   end
end
<file_sep>/lib/dotmanager.rb
require 'dotmanager/version'
require 'dotmanager/commands'
require 'dotmanager/config'
require 'dotmanager/repo'
require 'dotmanager/util'

module Dotmanager
  # Your code goes here...
end
<file_sep>/lib/dotmanager/util.rb
require 'fileutils'

module Dotmanager
  module Util
    extend self

    def abspath(source)
      File.expand_path source
    end
  end
end
<file_sep>/Gemfile
source 'https://rubygems.org'

# Specify your gem's dependencies in dotmanager.gemspec
gemspec
<file_sep>/lib/dotmanager/commands.rb
require 'git'
require 'fileutils' # mv, ln, cp,...
require 'dotmanager/config'
require 'dotmanager/util'
require 'dotmanager/repo'

module Dotmanager
   class Action
      # create a new symlink, add the symlink to the database and add the new file to git repo
      def create_symlink(source, destination, **options)
         config = Config.instance
         fail "Unexistent source file: #{source}" unless (FileUtils.stat source).file?
         fail "File: #{source} or #{destination} already in database" if Config.source_defined?(source) ||
             Config.destination_defined?(destination)
         begin
            FileUtils.ln_s Util.abspath(source), Util.abspath(destination)
         rescue Errno::EEXIST
            raise 'Destination file already in the repo. Maybe you should use install or force'
         end
         # now if the symlink suceeded
         config.add_symlink source, destination, options[:description] rescue 'No description'
         Repo.add_file destination
         config.save_database
      end

      def delete_existing_symlink(source, **options)
         config = Config.instance
         fail "The file: #{source} is not in the database" unless Config.source_defined? Util.abspath(source)
         fail "The file: #{source} is registered in the database but is not a symlink." unless File.stat(source).symlink?
         destination = config.get_destination source
         FileUtils.rm Util.abspath source
         FileUtils.mv Util.abspath(destination) , Util.abspath(source)
         if options[:remove_from_git] # also update the db
            Repo.remove_file Config.get_destination source
            config.remove_source source # we must do
         end
         config.remove_source source if options[:update_db]
      end

      def install_existing_symlink(source, **_options)
         config = Config.instance
         fail "Undefined source: #{source}" unless config.source_defined? source
         destination = Config.get_destination source
         if FileUtils.stat(Util.abspath source).file?
            puts "#{source} already exists. Creating backup" #TODO replace with logger
            backup_file source
         end
         FileUtils.symlink Util.abspath(source), Util.abspath(destination)
      end

      def check_symlink(source)
         config = Dotmanager::Config.instance
         return unless config.source_defined? source
         def_source = Util.abspath config.get_destination source
         def_destination = Util.abspath config.get_destination def_source
         File.readlink def_source == def_destination
      end

      # block will be called for every bad defined link
      def check_all_symlinks
         config = Config.instance
         config[:symlinks].each do |link|
            unless check_symlink link[:src]
               if block_given?
                  yield link
               else
                  fail "Incorrect link #{link[:src]}"
               end
            end
         end
      end

      def install_all_symlinks # This is part of bootstrap process (if ever run)
         check_all_symlinks do |link| # check for the missing symlinks
            install_existing_symlink link[:src] # if there is one than install it
         end
      end

      def clone_repo(repo)
         config = Config.instance
         fail "Undefined repo #{repo}" unless config.repo_defined? repo
         Git.clone(repo[:repo], repo[:name], :path=>repo[:dest], :branch=>(repo[:branch] rescue 'master')) # just clone the repo
      end

      def install_package(_package)
      end

      def uninstall_package(_package)
      end

      def install_all_packages
      end

      def install_fonts
      end

      private

      def download_file(_url, _dest)
      end

      def backup_file(filename)
         FileUtils.mv Util.abspath(filename), Util.abspath("#{filename}.dotbackup")
      end
   end

   class Process
      def bootstrap
      end

      def new_env
      end
   end
end
<file_sep>/lib/dotmanager/config.rb
require 'singleton'
require 'yaml'
require 'dotmanager/util'

# from https://gist.github.com/morhekil/998709/aafd65c5780a2ce3cda3ef020b6e5dead7cef1d0
# Symbolizes all of hash's keys and subkeys.
# Also allows for custom pre-processing of keys (e.g. downcasing, etc)
# if the block is given:
#
# somehash.deep_symbolize { |key| key.downcase }
#
# Usage: either include it into global Hash class to make it available to
#        to all hashes, or extend only your own hash objects with this
#        module.
#        E.g.:
#        1) class Hash; include DeepSymbolizable; end
#        2) myhash.extend DeepSymbolizable

module DeepSymbolizable
   def deep_symbolize(&block)
      method = self.class.to_s.downcase.to_sym
      syms = DeepSymbolizable::Symbolizers
      syms.respond_to?(method) ? syms.send(method, self, &block) : self
   end

   module Symbolizers
      extend self

      # the primary method - symbolizes keys of the given hash,
      # preprocessing them with a block if one was given, and recursively
      # going into all nested enumerables
      def hash(hash, &block)
         hash.inject({}) do |result, (key, value)|
            # Recursively deep-symbolize subhashes
            value = _recurse_(value, &block)

            # Pre-process the key with a block if it was given
            key = yield key if block_given?
            # Symbolize the key string if it responds to to_sym
            sym_key = key.to_sym rescue key
            # write it back into the result and return the updated hash
            result[sym_key] = value
            result
         end
      end

      # walking over arrays and symbolizing all nested elements
      def array(ary, &block)
         ary.map { |v| _recurse_(v, &block) }
      end

      # handling recursion - any Enumerable elements (except String)
      # is being extended with the module, and then symbolized
      def _recurse_(value, &block)
         if value.is_a?(Enumerable) && !value.is_a?(String)
            # support for a use case without extended core Hash
            value.extend DeepSymbolizable unless value.class.include?(DeepSymbolizable)
            value = value.deep_symbolize(&block)
         end
         value
      end
   end
end

module Dotmanager
   class Config
      attr_reader :files
      include Singleton
      
      def initialize
         @files  = []
         @config = {}
         @config.extend DeepSymbolizable
      end

      # noinspection RubyScope
      def load_file(filename)
         file = Util.abspath(filename)
         unless @files.include? file
            @files << file
            data   = YAML.load_file(file) # transform the keys in symbols
            merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
            @config.merge! data, &merger
            @config = @config.deep_symbolize
         end if File.exist?(file)
         @files.include? file
      end

      def [](key)
         @config[key]
      end

      def source_defined?(source)
         !@config[:symlinks].find { |s| s[:source] == source }.nil?
      end

      def destination_defined?(destination)
         !@config[:symlinks].find { |s| s[:destination] == destination }.nil?
      end

      def get_destination(source)
         fail "Undefined source: #{source}" unless source_defined? source
         (@config[:symlinks].find { |s| s[:source] == source })[:destination].strip
      end

      def get_source(destination)
         fail "Undefined destination: #{destination}" unless destination_defined? destination
         (@config[:symlinks].find { |s| s[:destination] == destination })[:source].strip
      end

      def add_symlink(source, destination, description)
         @config[:symlinks] << { description: description, source: source, destination: destination }
      end

      def delete_source_symlink(source)
         fail "Undefined source: #{source}" unless source_defined? source
         @config[:symlinks].reject! { |s| s[:source] == source }
      end

      def delete_destination_symlink(destination)
         fail "Undefined destination: #{destination}" unless destination_defined? destination
         @config[:symlinks].reject! { |s| s[:destination] == destination }
      end

      def repo_defined?(repo)
         !config[:git_repos].find { |r| r[:repo] == repo }.nil?
      end

      def get_repo_dest(repo)
         fail "Undefined repo #{repo}" unless repo_defined? repo
         (@config[:git_repos].find {|r| r[:repo] == repo})[:dest]
      end

      def save_database

      end
   end
end
