#!/usr/bin/env ruby # Copyright 2013 Dafydd Crosby (dafydd@dafyddcrosby.com) # All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'optparse' require 'ostruct' require 'rubygems' require 'active_record' require 'yaml' require 'pathname' module Booklist options = OpenStruct.new STATES = { "read" => "Mark as read", "reading" => "Mark as currently reading", "toread" => "Mark as to-read" } options.act_num = 0 usage = nil OptionParser.new do |opts| opts.banner = "Usage: booklist [options]" opts.separator "Actions:" opts.on("-a", "--add", "Add a book record", "(--title is required)") do options.act_num += 1 options.action = :add end opts.on("-e", "--edit [ID]", Integer, "Edit a book record") do |id| options.act_num += 1 options.id = id if id options.action = :edit end opts.on("-d", "--delete [ID]", Integer, "Delete a book record") do |id| options.act_num += 1 options.id = id if id options.action = :delete end opts.on("-s", "--search", "Search for a book") do options.act_num += 1 options.action = :search end opts.on("-r", "--read [ID]", Integer, "Show a book record's details") do |id| options.act_num += 1 options.id = id if id options.action = :read end opts.on("--list", "List all books in the database") do options.act_num += 1 options.action = :list end opts.separator "" opts.separator "Book details:" # Book details opts.on("--title TITLE", "Book title") do |title| options.title = title end opts.on("--state STATE", "What state the book is in (read, to-read, reading, abandoned)") do |state| options.state = state end opts.on("--author AUTHOR", "Book author") do |author| options.author = author end opts.on("--addn_authors ADDN_AUTHORS", "Additional authors") do |aa| options.addn_authors = aa end opts.on("--tags X,Y,Z", Array, "List of tags") do |tags| options.tags = tags end opts.on("--id ID", Integer, "Book ID number") do |id| options.id = id end opts.separator "" opts.separator "Common options:" # TODO - configurable booklist path #opts.on("-c", "--config FILE", "Use configuration file") do |file| # options.config_file = file #end opts.on("--debug", "Debug node") do options.debug = true end opts.on("--updatedb", "Update the book database schema") do options.update_schema = true end opts.on_tail("--version", "Get the booklist version") do puts Booklist::version exit end opts.on_tail("-h", "--help", "Show this message") do puts opts exit end usage = opts end.parse! if options.act_num > 1 puts "Please only use one action." puts usage exit end # TODO Create configuration directory if it doesn't exist # mkdir ~/.booklist # TODO configurable booklist path booklist_db_path = File.join(Dir.home, '.booklist', 'booklist.db') ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => booklist_db_path.to_s) ActiveRecord::Base.logger = Logger.new(STDERR) if options.debug if options.update_schema || !File.exist?(booklist_db_path) ActiveRecord::Migrator.migrate('db/migrate') end class Book < ActiveRecord::Base has_many :taggings has_many :tags, through: :taggings #, source: "book_id" validates :title, presence: true def cli_display puts "ID: #{id}\n" puts "Title: #{title}\n" puts "Author: #{author}\n" if author puts "Additional authors: #{addn_authors}" if addn_authors puts "State: #{state}" if state puts "Tags: #{tag_list}\n" if tags.count > 0 puts "\n" end def tag_list #put tags tags.map(&:name).join(", ") end def tag_list=(names) self.tags = names.map do |n| Tag.where(name: n.strip).first_or_create! end end end class Tag < ActiveRecord::Base has_many :taggings has_many :books, through: :taggings #, source: "tag_id" end class Tagging < ActiveRecord::Base belongs_to :tag belongs_to :book end if options.act_num = 1 if options.action == :add if !options.title || options.title == "" puts "Please add a --title with the book name" puts usage exit end book = Book.new do |b| b.title = options.title b.author = options.author if options.author b.addn_authors if options.addn_authors b.state = options.state if options.state b.tag_list = options.tags if options.tags end book.save book.cli_display elsif options.action == :edit book = Book.find_by(id: options.id) if book book.title = options.title if options.title book.author = options.author if options.author book.addn_authors = options.addn_authors if options.addn_authors book.state = options.state if options.state book.tag_list = options.tags if options.tags book.save book.cli_display else puts "No book with ID of #{options.id} was found" end elsif options.action == :delete book = Book.find_by(id: options.id) book.destroy elsif options.action == :read book = Book.find_by(id: options.id) if book book.cli_display else puts "No book with ID of #{options.id} was found" end elsif options.action == :search books = Book.where(["title LIKE ?", "%#{options.title}%"]) if options.title # TODO - books << Book.where(["author LIKE ? or addn_authors LIKE ?", "%#{options.author}%", "%#{options.author}%"]) if options.author # TODO show unique records books.each { |b| b.cli_display } elsif options.action == :list books = Book.all books.each{ |b| b.cli_display } end elsif options.act_num > 1 puts "Please only use one action." puts usage exit end end