diff --git a/lib/one_gadget.rb b/lib/one_gadget.rb index abb5f728..4b1ebf83 100644 --- a/lib/one_gadget.rb +++ b/lib/one_gadget.rb @@ -67,6 +67,9 @@ def one_gadget(arg = nil, **options) OneGadget.gadgets(**options) end +require 'one_gadget/update' +OneGadget::Update.check! + require 'one_gadget/fetcher' require 'one_gadget/helper' require 'one_gadget/logger' diff --git a/lib/one_gadget/logger.rb b/lib/one_gadget/logger.rb index d6964c19..2b3abb6d 100644 --- a/lib/one_gadget/logger.rb +++ b/lib/one_gadget/logger.rb @@ -10,7 +10,7 @@ module Logger prep = ' ' * 12 message = msg.lines.map.with_index do |str, i| next str if i.zero? - prep + str + str.strip.empty? ? str : prep + str end color = case severity when 'WARN' then :warn diff --git a/lib/one_gadget/update.rb b/lib/one_gadget/update.rb new file mode 100644 index 00000000..adfa649b --- /dev/null +++ b/lib/one_gadget/update.rb @@ -0,0 +1,59 @@ +require 'fileutils' + +require 'one_gadget/helper' +require 'one_gadget/logger' + +module OneGadget + # For automatically check update. + module Update + # At least 7 days between check for new version. + FREQUENCY = 7 * 24 * 60 * 60 + # Path to cache file. + CACHE_FILE = File.join(ENV['HOME'], '.cache', 'one_gadget', 'update').freeze + + class << self + # Check if new releases have been drafted. + # + # @return [void] + def check! + return unless need_check? + OneGadget::Logger.info("Checking for new versions of OneGadget\n" \ + "To disable this functionality, do\n$ echo never > #{CACHE_FILE}\n\n") + latest = Helper.latest_tag[1..-1] # remove 'v' + if Gem::Version.new(latest) <= Gem::Version.new(OneGadget::VERSION) + return OneGadget::Logger.info("You have the latest version of OneGadget (#{latest})!\n\n") + end + + # show update message + msg = format("A newer version of OneGadget is available (%s --> %s).\n", OneGadget::VERSION, latest) + msg << "Update with: $ gem update one_gadget\n\n" + OneGadget::Logger.info(msg) + end + + private + + # check ~/.cache/one_gadget/update + def need_check? + cache = cache_file + return false if cache.nil? # cache file fails, no update check. + return false if IO.binread(cache).strip == 'never' + Time.now >= last_check + FREQUENCY + end + + def last_check + cache = cache_file + return Time.now if cache.nil? + File.open(cache, &:mtime) + end + + def cache_file + dir = File.dirname(CACHE_FILE) + FileUtils.mkdir_p(dir) unless File.directory?(dir) + IO.binwrite(CACHE_FILE, '') unless File.exist?(CACHE_FILE) + CACHE_FILE + rescue # prevent dir is not writable + return nil + end + end + end +end diff --git a/spec/update_spec.rb b/spec/update_spec.rb new file mode 100644 index 00000000..d46658a9 --- /dev/null +++ b/spec/update_spec.rb @@ -0,0 +1,70 @@ +require 'logger' + +require 'one_gadget/update' +require 'one_gadget/version' + +describe OneGadget::Update do + before(:all) do + # precent fail on CI + @hook_cache_file = lambda do |&block| + tmp = Dir::Tmpname.make_tmpname('/tmp/one_gadget/update', nil) + stub_const('OneGadget::Update::CACHE_FILE', tmp) + block.call(tmp) + end + + @hook_logger = lambda do |&block| + # no method 'reopen' before ruby 2.3 + org_logger = OneGadget::Logger.instance_variable_get(:@logger) + new_logger = ::Logger.new($stdout) + new_logger.formatter = org_logger.formatter + OneGadget::Logger.instance_variable_set(:@logger, new_logger) + block.call + OneGadget::Logger.instance_variable_set(:@logger, org_logger) + end + end + + after(:all) do + FileUtils.rm_r('/tmp/one_gadget') + end + + it 'cache_file' do + skip 'Windows so hard' unless RUBY_PLATFORM =~ /linux/ + @hook_cache_file.call do |path| + expect(described_class.send(:cache_file)).to eq path + File.chmod(0o000, File.dirname(path)) + expect(described_class.send(:cache_file)).to be nil + File.chmod(0o700, File.dirname(path)) + end + end + + it 'need_check?' do + @hook_cache_file.call do |path| + expect(described_class.send(:need_check?)).to be false + now = Time.now + allow(Time).to receive(:now).and_return(now + 7 * 24 * 3600) + expect(described_class.send(:need_check?)).to be true + IO.binwrite(path, 'never') + expect(described_class.send(:need_check?)).to be false + end + end + + it 'check!' do + OneGadget::Helper.color_off! + @hook_cache_file.call do |path| + allow(described_class).to receive(:need_check?).and_return(true) + expect { @hook_logger.call { described_class.check! } }.to output(<<-EOS).to_stdout +[OneGadget] Checking for new versions of OneGadget + To disable this functionality, do + $ echo never > #{path} + +[OneGadget] You have the latest version of OneGadget (#{OneGadget::VERSION})! + + EOS + stub_const('OneGadget::VERSION', '0.0.0') + expect { @hook_logger.call { described_class.check! } }.to output(include(<<-EOS)).to_stdout +$ gem update one_gadget + EOS + end + OneGadget::Helper.color_on! + end +end