├── lib ├── notehub │ ├── version.rb │ └── notehub.rb └── notehub.rb ├── Gemfile ├── README.rdoc ├── .gitignore ├── notehub.rdoc ├── features ├── step_definitions │ └── notehub_steps.rb ├── notehub.feature └── support │ └── env.rb ├── test ├── test_helper.rb ├── default_test.rb └── testnote.md ├── Gemfile.lock ├── notehub.gemspec ├── Rakefile ├── README.md └── bin └── notehub /lib/notehub/version.rb: -------------------------------------------------------------------------------- 1 | module Notehub 2 | VERSION = '0.0.4' 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'highline', '~> 1.6.21' 3 | gemspec 4 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = notehub 2 | 3 | A CLI for notehub.org. 4 | 5 | :include:notehub.rdoc 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | notehub.sublime-project 2 | notehub.sublime-workspace 3 | notehub.taskpaper 4 | pkg 5 | -------------------------------------------------------------------------------- /notehub.rdoc: -------------------------------------------------------------------------------- 1 | = notehub 2 | 3 | Generate this with 4 | notehub rdoc 5 | After you have described your command line interface -------------------------------------------------------------------------------- /features/step_definitions/notehub_steps.rb: -------------------------------------------------------------------------------- 1 | When /^I get help for "([^"]*)"$/ do |app_name| 2 | @app_name = app_name 3 | step %(I run `#{app_name} help`) 4 | end 5 | 6 | # Add more step definitions here 7 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | # Add test libraries you want to use here, e.g. mocha 4 | 5 | class Test::Unit::TestCase 6 | 7 | # Add global extensions to the test case class here 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/default_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DefaultTest < Test::Unit::TestCase 4 | 5 | def setup 6 | end 7 | 8 | def teardown 9 | end 10 | 11 | def test_the_truth 12 | assert true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /features/notehub.feature: -------------------------------------------------------------------------------- 1 | Feature: My bootstrapped app kinda works 2 | In order to get going on coding my awesome app 3 | I want to have aruba and cucumber setup 4 | So I don't have to do it myself 5 | 6 | Scenario: App just runs 7 | When I get help for "notehub" 8 | Then the exit status should be 0 9 | -------------------------------------------------------------------------------- /lib/notehub.rb: -------------------------------------------------------------------------------- 1 | require 'notehub/version.rb' 2 | require 'notehub/notehub.rb' 3 | require 'json' 4 | require 'yaml' 5 | require 'fileutils' 6 | require 'net/http' 7 | require 'digest/md5' 8 | require 'cgi' 9 | require 'highline/import' 10 | require 'shellwords' 11 | 12 | # Add requires for other files you add to your project here, so 13 | # you just need to require this one file in your bin file 14 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'aruba/cucumber' 2 | 3 | ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}" 4 | LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib') 5 | 6 | Before do 7 | # Using "announce" causes massive warnings on 1.9.2 8 | @puts = true 9 | @original_rubylib = ENV['RUBYLIB'] 10 | ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s 11 | end 12 | 13 | After do 14 | ENV['RUBYLIB'] = @original_rubylib 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | notehub (0.0.4) 5 | gli (= 2.7.0) 6 | highline (= 1.6.21) 7 | json (= 1.8.1) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | aruba (0.5.4) 13 | childprocess (>= 0.3.6) 14 | cucumber (>= 1.1.1) 15 | rspec-expectations (>= 2.7.0) 16 | builder (3.2.2) 17 | childprocess (0.5.1) 18 | ffi (~> 1.0, >= 1.0.11) 19 | cucumber (1.3.11) 20 | builder (>= 2.1.2) 21 | diff-lcs (>= 1.1.3) 22 | gherkin (~> 2.12) 23 | multi_json (>= 1.7.5, < 2.0) 24 | multi_test (>= 0.0.2) 25 | diff-lcs (1.2.5) 26 | ffi (1.9.24) 27 | gherkin (2.12.2) 28 | multi_json (~> 1.3) 29 | gli (2.7.0) 30 | highline (1.6.21) 31 | json (1.8.1) 32 | multi_json (1.9.0) 33 | multi_test (0.0.3) 34 | rake (10.1.1) 35 | rdoc (6.1.2.1) 36 | rspec-expectations (2.14.5) 37 | diff-lcs (>= 1.1.3, < 2.0) 38 | 39 | PLATFORMS 40 | ruby 41 | 42 | DEPENDENCIES 43 | aruba 44 | highline (~> 1.6.21) 45 | notehub! 46 | rake 47 | rdoc 48 | -------------------------------------------------------------------------------- /notehub.gemspec: -------------------------------------------------------------------------------- 1 | # Ensure we require the local version and not one we might have installed already 2 | require File.join([File.dirname(__FILE__),'lib','notehub','version.rb']) 3 | spec = Gem::Specification.new do |s| 4 | s.name = 'notehub' 5 | s.version = Notehub::VERSION 6 | s.author = 'Your Name Here' 7 | s.email = 'your@email.address.com' 8 | s.homepage = 'http://your.website.com' 9 | s.platform = Gem::Platform::RUBY 10 | s.summary = 'A description of your project' 11 | # Add your other files here if you make them 12 | s.files = %w( 13 | bin/notehub 14 | lib/notehub/version.rb 15 | lib/notehub/notehub.rb 16 | lib/notehub.rb 17 | ) 18 | s.require_paths << 'lib' 19 | s.has_rdoc = true 20 | s.extra_rdoc_files = ['README.rdoc','notehub.rdoc'] 21 | s.rdoc_options << '--title' << 'notehub' << '--main' << 'README.rdoc' << '-ri' 22 | s.bindir = 'bin' 23 | s.executables << 'notehub' 24 | s.add_development_dependency('rake') 25 | s.add_development_dependency('rdoc') 26 | s.add_development_dependency('aruba') 27 | s.add_runtime_dependency('gli','2.7.0') 28 | s.add_runtime_dependency('json','1.8.1') 29 | s.add_runtime_dependency('highline','1.6.21') 30 | end 31 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | require 'rubygems' 3 | require 'rubygems/package_task' 4 | require 'rdoc/task' 5 | require 'cucumber' 6 | require 'cucumber/rake/task' 7 | Rake::RDocTask.new do |rd| 8 | rd.main = "README.rdoc" 9 | rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*") 10 | rd.title = 'Your application title' 11 | end 12 | 13 | spec = eval(File.read('notehub.gemspec')) 14 | 15 | Gem::PackageTask.new(spec) do |pkg| 16 | end 17 | CUKE_RESULTS = 'results.html' 18 | CLEAN << CUKE_RESULTS 19 | desc 'Run features' 20 | Cucumber::Rake::Task.new(:features) do |t| 21 | opts = "features --format html -o #{CUKE_RESULTS} --format progress -x" 22 | opts += " --tags #{ENV['TAGS']}" if ENV['TAGS'] 23 | t.cucumber_opts = opts 24 | t.fork = false 25 | end 26 | 27 | desc 'Run features tagged as work-in-progress (@wip)' 28 | Cucumber::Rake::Task.new('features:wip') do |t| 29 | tag_opts = ' --tags ~@pending' 30 | tag_opts = ' --tags @wip' 31 | t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x -s#{tag_opts}" 32 | t.fork = false 33 | end 34 | 35 | task :cucumber => :features 36 | task 'cucumber:wip' => 'features:wip' 37 | task :wip => 'features:wip' 38 | require 'rake/testtask' 39 | Rake::TestTask.new do |t| 40 | t.libs << "test" 41 | t.test_files = FileList['test/*_test.rb'] 42 | end 43 | 44 | task :default => [:test,:features] 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # notehub 2 | 3 | A CLI for working with [notehub.org](http://notehub.org) from the command line. Very much a work in progress. 4 | 5 | [sudo] gem install notehub 6 | 7 | You'll need a publisher key to use the notehub CLI. Keys can be requested [here](http://www.notehub.org/api#registration). When you run `notehub`, it will create a config file in `~/.notehub/config.yml`. Edit this and provide your publisher_id and secret_key. 8 | 9 | $ notehub help 10 | NAME 11 | notehub - A command line interface for Notehub 12 | 13 | SYNOPSIS 14 | notehub [global options] command [command options] [arguments...] 15 | 16 | VERSION 17 | 0.0.1 18 | 19 | GLOBAL OPTIONS 20 | --help - Show this message 21 | --version - Display the program version 22 | 23 | COMMANDS 24 | create - Create a new note 25 | help - Shows a list of commands or help for one command 26 | info - Retrieve info for a selected note 27 | update - Update a note 28 | view - Open the selected note in the default browser 29 | 30 | $ notehub help create 31 | NAME 32 | create - Create a new note 33 | 34 | SYNOPSIS 35 | notehub [global options] create [command options] [text for new note] 36 | 37 | COMMAND OPTIONS 38 | -P, --paste - Create note from pasteboard (OS X only) 39 | -c, --copy - Copy resulting url to clipboard 40 | -f, --file=arg - Read input from file (default: none) 41 | --font=arg - Alternate font to use for note (Google Web Fonts) (default: none) 42 | --header=arg - Alternate font to use for headers (Google Web Fonts) (default: none) 43 | -o, --open - Open created note in browser 44 | -p, --password=arg - Password for future edits (default: rattf1nk) 45 | -s, --short - Shorten URL 46 | --theme=arg - Alternate theme to use for note (dark, solarized-light, solarized-dark) (default: none) 47 | 48 | See `notehub help [command]` for additional info. 49 | -------------------------------------------------------------------------------- /test/testnote.md: -------------------------------------------------------------------------------- 1 | # Alice is mad at me all over again 2 | 3 | # Which way was 4 | 5 | However jury-men would take a morsel of room to open any direction like telescopes this minute to twenty at present at last in as sure she swam nearer Alice noticed had at everything seemed quite enough for protection. All on slates and most interesting story for **having** seen she dreamed of herself as you got used to them fast asleep I *or* heard before they can't remember about among them a Mock Turtle with curiosity and fetch things I Oh you can have [everybody else. Nothing](http://dummy.com) said do once while and even room when they began thinking it pointed to tremble. I've heard a holiday. 6 | 7 | Back to dull reality the banquet What fun now dears. IT TO LEAVE THE LITTLE larger than Alice took her hair. *Stolen.* Collar that into Alice's shoulder with **draggled** feathers the rosetree for serpents night and he's [perfectly sure. ](http://dummy.com) 8 | 9 | ## Edwin and fanned herself in trying to 10 | 11 | Dinah'll be herself not that did Alice ventured to on to draw water had put **on** What's your eye fell upon pegs. Stop this [*pool.* ](http://dummy.com)[^fn1] 12 | 13 | [^fn1]: HEARTHRUG NEAR THE VOICE OF HEARTS. 14 | 15 | * ashamed 16 | * MILE 17 | * BUSY 18 | * you're 19 | * wings 20 | * dull 21 | * shock 22 | 23 | 24 | Only mustard isn't said I am to remain where HAVE their slates SHE said do once and finding morals in all played at one about children. THAT you said It quite strange [Adventures of mushroom growing. Once](http://dummy.com) more to stoop. Don't you learn it up eagerly There was standing before as it's very truthful child. You might venture to cats eat a RED rose-tree and behind them something out her chin in among the Dormouse crossed the loveliest garden *among* those twelve and waited a proper way back in head to yesterday things had entirely disappeared so VERY ill. Why not swim in time while till now in here till I'm getting home thought to end then I'm glad I've forgotten **that** it very humbly I GAVE HER ONE. muttered to try if I'd rather anxiously about wasting our house I find a frightened by seeing the cattle in chorus Yes it fitted. 25 | 26 | ![dummy][img1] 27 | 28 | [img1]: http://placekitten.com/400/300 29 | 30 | ### Herald read as ferrets. 31 | 32 | |is|uglify|to|took|And| 33 | |:-----:|:-----:|:-----:|:-----:|:-----:| 34 | seem|doesn't|it|towards|up| 35 | down|got|shoulders|my|in| 36 | hedge.|the|let|Don't|| 37 | clock.|the|All||| 38 | said|she|happen|might|I| 39 | are|You|began|puppy|enormous| 40 | it's|Sure|as|wait|well| 41 | for|opening|encouraging|an|as| 42 | indeed|so|vanishing|and|us| 43 | throw|you|hear|hardly|I| 44 | 45 | 46 | HEARTHRUG NEAR THE SLUGGARD said his mind what it here any more. Dinah'll be at a duck with MINE. catch hold it could **do** *you* could keep them said aloud addressing nobody attends to keep appearing and their curls got their fur and saying to remark seemed too bad cold if you'd only answered very nearly carried the earth takes some [other curious child away comfortably enough. Advice](http://dummy.com) from him you liked with cupboards and day of gloves. 47 | 48 | > Call the way never go among those of sticks and saying and hot tureen. 49 | > for. 50 | 51 | 52 | 1. ventured 53 | 1. tea 54 | 1. fishes 55 | 1. May 56 | 1. tougher 57 | 58 | 59 | Pig and away without a languid sleepy voice the squeaking of great thistle to *kill* it all said And yesterday things being ordered about children she found all else but at it altogether for fish came skimming out laughing and the treacle said So Alice called softly after **hunting** all ready to say things in rather late it's called lessons and eels of making quite like ears for poor speaker said do that [all crowded together she made](http://dummy.com) some while Alice all think for making personal remarks and though. Digging for. She'll get to Alice not a Dodo said severely Who ARE you ever see some severity it's no toys to fall was dreadfully ugly and some were really you mean purpose.[^fn2] 60 | 61 | [^fn2]: Seven flung down among those roses growing too stiff. 62 | 63 | 64 | --- 65 | 66 | Hand it quite away went timidly as you're mad people. 67 | Mind that in chains with oh dear I find out You'd better not quite pale 68 | Nearly two which it puffed away the rattling teacups as I won't walk a 69 | I'll come up any minute. 70 | Alas. 71 | 72 | 73 | You've no room when his shining tail when I heard of brokenStop this fit An 74 | : Back to without speaking but very hard as usual. 75 | 76 | My dear little room when Alice 77 | : was soon began nursing a sleepy voice but some curiosity. 78 | 79 | By-the bye what Latitude or judge 80 | : Whoever lives. 81 | 82 | -------------------------------------------------------------------------------- /lib/notehub/notehub.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | class Hash 4 | def to_query 5 | prefix = "?" 6 | query_string = "" 7 | self.each {|p, v| 8 | query_string += "#{prefix}#{p}=#{CGI.escape(v)}" 9 | prefix = "&" 10 | } 11 | query_string 12 | end 13 | end 14 | 15 | class String 16 | def hr(length=0) 17 | length = `tput cols`.strip.to_i if length == 0 18 | out = "" 19 | length.times do 20 | out += self 21 | end 22 | out 23 | end 24 | end 25 | 26 | 27 | class NotehubAPI 28 | API_VERSION = "1.4" 29 | attr_reader :notes, :default_password, :default_theme, :default_font, :default_header_font 30 | 31 | def initialize(opts={}) 32 | 33 | opts['config_location'] ||= "#{ENV['HOME']}/.notehub" 34 | opts['config_file'] ||= "#{opts['config_location']}/config.yml" 35 | opts['notes_db'] ||= "#{opts['config_location']}/notes.db" 36 | 37 | config_file = opts['config_file'] 38 | @notes_db = opts['notes_db'] 39 | 40 | # Set up config 41 | FileUtils.mkdir_p(opts['config_location'],:mode => 0755) unless File.directory? opts['config_location'] 42 | unless File.exists?(config_file) 43 | new_config = { 44 | 'publisher_id' => "your_publisher_id", 45 | 'secret_key' => "your_secret_key", 46 | 'default_password' => "default password for editing notes", 47 | 'default_theme' => "light", 48 | 'default_font' => 'Georgia' 49 | } 50 | 51 | File.open(config_file, 'w') { |yf| YAML::dump(new_config, yf) } 52 | end 53 | 54 | config = YAML.load_file(config_file) 55 | @pid = config['publisher_id'] 56 | @psk = config['secret_key'] 57 | if config['default_password'] && config['default_password'].length > 0 58 | @default_password = config['default_password'] 59 | else 60 | @default_password = false 61 | end 62 | @default_theme = config['default_theme'] || 'light' 63 | @default_font = config['default_font'] || 'Georgia' 64 | @default_header_font = config['default_header_font'] || 'Georgia' 65 | 66 | # verify config 67 | if @pid == "your_publisher_id" || @psk == "your_secret_key" 68 | puts "Please edit #{config_file} and run again" 69 | Process.exit 1 70 | end 71 | 72 | # set up notes database 73 | unless File.exists?(@notes_db) 74 | new_db = {'notes' => {}} 75 | File.open(@notes_db, 'w') { |yf| YAML::dump(new_db, yf) } 76 | end 77 | 78 | # load existing notes 79 | @notes = YAML.load_file(@notes_db) 80 | end 81 | 82 | def store_note(note) 83 | @notes['notes'][note['id']] = note 84 | File.open(@notes_db, 'w') { |yf| YAML::dump(@notes, yf) } 85 | end 86 | 87 | def post_api(params, action="post") 88 | uri = URI("http://www.notehub.org/api/note") 89 | 90 | if action == "put" 91 | req = Net::HTTP::Put.new(uri) 92 | else 93 | req = Net::HTTP::Post.new(uri) 94 | end 95 | req.set_form_data(params) 96 | 97 | res = Net::HTTP.start(uri.hostname, uri.port) do |http| 98 | http.request(req) 99 | end 100 | 101 | case res 102 | when Net::HTTPSuccess, Net::HTTPRedirection 103 | json = JSON.parse(res.body) 104 | if json['status']['success'] 105 | return json 106 | else 107 | raise "POST request returned error: #{json['status']['message']}" 108 | end 109 | else 110 | # res.value 111 | p res.body if res 112 | raise "Error retrieving POST request to API" 113 | end 114 | # res = Net::HTTP.post_form(uri, params) 115 | end 116 | 117 | def get_api(params) 118 | params['version'] = API_VERSION 119 | 120 | uri = URI("http://www.notehub.org/api/note#{params.to_query}") 121 | 122 | Net::HTTP.start(uri.host, uri.port) do |http| 123 | req = Net::HTTP::Get.new uri 124 | res = http.request req 125 | if res && res.code == "200" 126 | json = JSON.parse(res.body) 127 | if json['status']['success'] 128 | return json 129 | else 130 | raise "GET request returned error: #{json['status']['message']}" 131 | end 132 | else 133 | p res.body if res 134 | raise "Error retrieving GET request to API" 135 | end 136 | end 137 | end 138 | 139 | def new_note(text, pass=false, options={}) 140 | options[:theme] ||= nil 141 | options[:font] ||= nil 142 | 143 | params = {} 144 | params['note'] = text.strip 145 | params['pid'] = @pid 146 | params['signature'] = Digest::MD5.hexdigest(@pid + @psk + text.strip) 147 | params['password'] = Digest::MD5.hexdigest(pass) if pass 148 | params['version'] = API_VERSION 149 | 150 | params['theme'] = options[:theme] unless options[:theme].nil? 151 | params['text-font'] = options[:font] unless options[:font].nil? 152 | params['header-font'] = options[:header_font] unless options[:header_font].nil? 153 | 154 | res = post_api(params) 155 | 156 | if res && res['status']['success'] 157 | note_data = read_note(res['noteID']) 158 | note = { 159 | 'title' => note_data['title'][0..80], 160 | 'id' => res['noteID'], 161 | 'url' => res['longURL'], 162 | 'short' => res['shortURL'], 163 | 'stats' => note_data['statistics'], 164 | 'pass' => pass ? pass : "", 165 | 'file' => options[:file] ? options[:file] : "" 166 | } 167 | store_note(note) 168 | return note 169 | else 170 | if res 171 | raise "Failed: #{res['status']['comment']} " 172 | else 173 | raise "Failed to create note" 174 | end 175 | end 176 | end 177 | 178 | def update_note(id, text, pass=false) 179 | note = @notes['notes'][id] 180 | if note.has_key?('file') 181 | if text.nil? || text == "use_previous" 182 | file = File.expand_path(note['file']) 183 | if File.exists?(file) 184 | unless text == "use_previous" 185 | puts "File: #{file}" 186 | res = ask("Read from file? (Y/n) ", String) 187 | if res.strip =~ /^y?$/i 188 | text = IO.read(file) 189 | end 190 | else 191 | text = IO.read(file) 192 | end 193 | end 194 | end 195 | end 196 | 197 | return false unless text 198 | 199 | params = {} 200 | pass ||= @default_password 201 | # raise "Password required for update" unless pass 202 | 203 | md5_pass = Digest::MD5.hexdigest(pass) 204 | 205 | params['password'] = md5_pass 206 | params['noteID'] = id 207 | params['note'] = text.strip 208 | params['pid'] = @pid 209 | sig = @pid + @psk + id + text.strip + md5_pass 210 | params['signature'] = Digest::MD5.hexdigest(sig) 211 | params['version'] = API_VERSION 212 | res = post_api(params,"put") 213 | 214 | if res && res['status']['success'] 215 | note_data = read_note(id) 216 | note = { 217 | 'title' => note_data['title'][0..80].strip, 218 | 'id' => id, 219 | 'url' => res['longURL'], 220 | 'short' => res['shortURL'], 221 | 'stats' => note_data['statistics'], 222 | 'pass' => pass || "" 223 | } 224 | store_note(note) 225 | return note 226 | else 227 | if res 228 | raise "Failed: #{res['status']['comment']} " 229 | else 230 | raise "Failed to update note" 231 | end 232 | end 233 | end 234 | 235 | def read_note(id) 236 | params = {'noteID' => id} 237 | get_api(params) 238 | end 239 | 240 | def list_notes(term=".*") 241 | notes = find_notes(term) 242 | notes.each {|note| 243 | puts "> #{note['title']} [ #{note['id']} ]" 244 | } 245 | end 246 | 247 | def find_notes(term=".*") 248 | term.gsub!(/\s+/,".*?") 249 | found_notes = [] 250 | @notes['notes'].each {|k, v| 251 | v['id'] = k 252 | found_notes.push(v) if v['title'] =~ /#{term}/i 253 | } 254 | found_notes 255 | end 256 | 257 | def choose_note(term=".*") 258 | 259 | puts "Choose a note:" 260 | 261 | list = find_notes(term) 262 | list.each_with_index { |note, i| puts "% 3d: %s" % [i+1, note['title']] } 263 | # list.each_with_index { |f,i| puts "% 3d: %s" % [i+1, f] } 264 | num = ask("Which note? ", Integer) { |q| q.in = 1..list.length } 265 | 266 | return false if num =~ /^[a-z ]*$/i 267 | 268 | list[num.to_i - 1] 269 | end 270 | 271 | def tag_file(id,short) 272 | if @notes['notes'].has_key?([id]) 273 | note = @notes['notes'][id] 274 | file = note.has_key?('file') ? note['file'] : false 275 | return false unless file 276 | content = IO.read(File.expand_path(file)) 277 | output = "\n\n\n" 278 | File.open(File.expand_path(file),'w+') do |f| 279 | f.puts content 280 | f.puts output 281 | end 282 | end 283 | end 284 | end 285 | 286 | # nh = NotehubAPI.new 287 | # nh.chooose_note 288 | -------------------------------------------------------------------------------- /bin/notehub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'gli' 3 | require 'notehub' 4 | 5 | nh = NotehubAPI.new 6 | 7 | if RUBY_VERSION.to_f > 1.9 8 | Encoding.default_external = Encoding::UTF_8 9 | Encoding.default_internal = Encoding::UTF_8 10 | end 11 | 12 | include GLI::App 13 | 14 | program_desc 'A command line interface for Notehub ' 15 | 16 | version Notehub::VERSION 17 | 18 | desc 'Create a new note' 19 | arg_name '[text for new note]' 20 | command :create do |c| 21 | c.desc 'Alternate theme to use for note (dark, solarized-light, solarized-dark)' 22 | c.flag [:theme] 23 | 24 | c.desc 'Alternate font to use for note (Google Web Fonts)' 25 | c.flag [:font] 26 | 27 | c.desc 'Alternate font to use for headers (Google Web Fonts)' 28 | c.flag [:header] 29 | 30 | c.desc 'Copy resulting url to clipboard' 31 | c.switch [:c,:copy], :negatable => false 32 | 33 | c.desc 'Open created note in browser' 34 | c.switch [:o,:open], :negatable => false 35 | 36 | c.desc 'Shorten URL' 37 | c.switch [:s,:short], :negatable => false 38 | 39 | c.desc 'Password for future edits' 40 | c.default_value nh.default_password 41 | c.flag [:p,:password] 42 | 43 | c.desc 'Read input from file' 44 | default_value false 45 | c.flag [:f,:file] 46 | 47 | c.desc 'Create note from pasteboard (OS X only)' 48 | c.switch [:P,:paste], :negatable => false 49 | 50 | c.action do |global_options,options,args| 51 | if options[:f] 52 | if File.exists?(File.expand_path(options[:f])) 53 | input = IO.read(File.expand_path(options[:f])) 54 | else 55 | raise "File not found: #{options[:f]}" 56 | end 57 | elsif options[:P] 58 | input = %x{pbpaste} 59 | elsif args.length > 0 && args[0] != "-" 60 | input = args.join(" ") 61 | # elsif STDIN.stat.size > 0 62 | else 63 | # puts "Input note text, ^d to submit" 64 | if RUBY_VERSION.to_f > 1.9 65 | input = STDIN.read.force_encoding('utf-8') 66 | else 67 | input = STDIN.read 68 | end 69 | # else 70 | # raise "No input or text specified for note" 71 | end 72 | 73 | additional_options = {} 74 | additional_options[:theme] = options[:theme] ? options[:theme] : nh.default_theme 75 | additional_options[:font] = options[:font] ? options[:font] : nh.default_font 76 | additional_options[:header_font] = options[:header] ? options[:header] : nh.default_header_font 77 | additional_options[:file] = File.expand_path(options[:f]) if options[:f] 78 | 79 | res = nh.new_note(input, options[:p], additional_options) 80 | 81 | raise "Error creating note" unless res 82 | note_url = options[:s] ? res['short'] : res['url'] 83 | puts "Note created: #{note_url}" 84 | 85 | %x{echo "#{note_url}"|pbcopy} if options[:c] 86 | 87 | %x{open "#{res['url']}"} if options[:o] 88 | end 89 | end 90 | 91 | # desc '' 92 | 93 | # desc 'List stored note ids' 94 | # arg_name 'search_term' 95 | # command :list do |c| 96 | # c.action do |global_options,options,args| 97 | # nh.list_notes(args.join(" ")) 98 | # end 99 | # end 100 | 101 | desc 'Create or update from a file' 102 | arg_name 'file_name' 103 | command :file do |c| 104 | c.action do |global_options,options,args| 105 | id = nil 106 | input = nil 107 | pass = nil 108 | short = nil 109 | notes = nh.notes['notes'] 110 | notes.each {|k,note| 111 | if note.has_key?('file') 112 | if File.basename(note['file']) == File.basename(args[0]) 113 | note['file'] = File.expand_path(args[0]) 114 | nh.store_note(note) 115 | id = note['id'] 116 | short = note['short'] 117 | pass = note['pass'] 118 | input = "use_previous" 119 | break 120 | end 121 | end 122 | } 123 | if id 124 | res = nh.update_note(id, input, pass) 125 | nh.tag_file(id,short) 126 | if res 127 | puts "Note updated" 128 | 129 | else 130 | puts "Error updating note" 131 | end 132 | else 133 | file = File.expand_path(args[0]) 134 | if File.exists?(file) 135 | additional_options = {} 136 | additional_options[:theme] = nh.default_theme 137 | additional_options[:font] = nh.default_font 138 | additional_options[:header_font] = nh.default_header_font 139 | additional_options[:file] = File.expand_path(args[0]) 140 | input = IO.read(file) 141 | res = nh.new_note(input, nh.default_password, additional_options) 142 | if res 143 | puts "Note created" 144 | nh.tag_file(res['id'],res['short']) 145 | else 146 | puts "Error creating note" 147 | end 148 | else 149 | raise "No such file" 150 | end 151 | end 152 | end 153 | end 154 | 155 | desc 'Update a note' 156 | arg_name 'search_term' 157 | command :update do |c| 158 | c.desc 'ID for note (default: choose from list)' 159 | c.default_value false 160 | c.flag [:id] 161 | 162 | c.desc 'Create note from pasteboard (OS X only)' 163 | c.switch [:P,:paste] 164 | 165 | c.desc 'Password for note' 166 | c.flag [:p,:password] 167 | 168 | c.desc 'Read input from file' 169 | default_value false 170 | c.flag [:f,:file,'file_name'] 171 | 172 | c.desc 'Update from previously used file (re-use)' 173 | c.switch [:r,:reuse] 174 | 175 | c.desc 'Copy resulting url to clipboard' 176 | c.switch [:c,:copy] 177 | 178 | c.desc 'Open created note in browser' 179 | c.switch [:o,:open] 180 | 181 | c.desc 'Shorten URL' 182 | c.switch [:s,:short] 183 | 184 | c.action do |global_options,options,args| 185 | if options[:f] && !options[:r] 186 | if File.exists?(File.expand_path(options[:f])) 187 | input = IO.read(File.expand_path(options[:f])) 188 | else 189 | raise "File not found: #{options[:f]}" 190 | end 191 | elsif options[:P] 192 | input = %x{pbpaste} 193 | elsif options[:r] 194 | input = "use_previous" 195 | else 196 | # puts "Input note text, ^d to submit" unless STDIN.stat.size > 0 197 | if RUBY_VERSION.to_f > 1.9 198 | input = STDIN.read.force_encoding('utf-8') 199 | else 200 | input = STDIN.read 201 | end 202 | # else 203 | # raise "No input or text specified for note" 204 | end 205 | 206 | args = args.join(" ").strip 207 | note = false 208 | notes = nh.notes['notes'] 209 | if args =~ /\S\/\S/ && args =~ /^[a-z0-9\/\-]+$/ 210 | 211 | if notes.has_key? (args) 212 | note = notes[args] 213 | end 214 | end 215 | 216 | unless note 217 | puts "Choose a note:" 218 | note = nh.choose_note(args) 219 | end 220 | 221 | 222 | raise "Error reading selected note" unless note 223 | id = note['id'].strip 224 | 225 | res = nh.update_note(id, input, options[:p]) 226 | raise "Error updating note" unless res 227 | 228 | note_url = options[:s] ? res['shortUrl'] : res['longUrl'] 229 | puts "Note updated: #{note_url}" 230 | 231 | %x{echo "#{note_url}"|pbcopy} if options[:c] 232 | 233 | %x{open #{res['url']}} if options[:o] 234 | end 235 | end 236 | 237 | desc 'Retrieve info for a selected note' 238 | arg_name 'search_term' 239 | command :info do |c| 240 | c.desc 'Specific key to retrieve (id, url, short, etc.)' 241 | c.flag [:k,:key] 242 | 243 | c.desc 'Copy result to clipboard (OS X)' 244 | c.switch [:c,:copy], :negatable => false 245 | 246 | c.action do |global_options, options, args| 247 | args = args.join(" ").strip 248 | note = false 249 | notes = nh.notes['notes'] 250 | if args =~ /\S\/\S/ && args =~ /^[a-z0-9\/\-]+$/ 251 | 252 | if notes.has_key? (args) 253 | note = notes[args] 254 | end 255 | end 256 | 257 | unless note 258 | puts "Choose a note:" 259 | note = nh.choose_note(args) 260 | end 261 | 262 | if note 263 | extra = nh.read_note(note['id']) 264 | note['stats'] = extra['statistics'] 265 | nh.store_note(note) 266 | if options[:k] 267 | if note.has_key?(options[:k]) 268 | out = note[options[:k]].strip 269 | else 270 | raise "Key #{options[:k]} not found" 271 | end 272 | else 273 | out = "\n" 274 | out += note['title'].strip + "\n" 275 | out += "-".hr(note['title'].strip.length) + "\n" 276 | out += " URL: #{note['short']} (#{note['url']})" + "\n" 277 | out += "Created: #{note['stats']['published']}" + "\n" 278 | out += " Edited: #{note['stats']['edited']}" + "\n" if note['stats'].has_key?('edited') 279 | out += " Views: #{note['stats']['views']}" + "\n" 280 | out += " ID: #{note['id']}" + "\n" 281 | out += " File: #{note['file']}" if note.has_key?('file') 282 | end 283 | puts out 284 | 285 | if options[:c] 286 | %x{echo #{Shellwords.escape(out.strip)}|tr -d "\n"|pbcopy} 287 | end 288 | else 289 | raise "Cancelled" 290 | end 291 | end 292 | 293 | end 294 | 295 | desc 'Open the selected note in the default browser' 296 | arg_name 'search_term' 297 | command :view do |c| 298 | c.action do |global_options,options,args| 299 | puts "Choose a note:" 300 | note = nh.choose_note(args.join(" ")) 301 | if note 302 | %x{open "#{note['url']}"} 303 | else 304 | raise "Cancelled" 305 | end 306 | # list = nh.find_notes(args.join(" ")) 307 | # list.each_with_index { |note, i| puts "% 3d: %s" % [i+1, note['title']] } 308 | # print "> " 309 | # num = gets 310 | # unless num =~ /^[a-z ]*$/i 311 | # %x{open #{list[num.to_i - 1]['url']}} 312 | # end 313 | end 314 | end 315 | 316 | pre do |global,command,options,args| 317 | # Pre logic here 318 | # Return true to proceed; false to abort and not call the 319 | # chosen command 320 | # Use skips_pre before a command to skip this block 321 | # on that command only 322 | true 323 | end 324 | 325 | post do |global,command,options,args| 326 | # Post logic here 327 | # Use skips_post before a command to skip this 328 | # block on that command only 329 | end 330 | 331 | on_error do |exception| 332 | # Error logic here 333 | # return false to skip default error handling 334 | true 335 | end 336 | 337 | exit run(ARGV) 338 | --------------------------------------------------------------------------------