cheatr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,137 @@
1
+ require "rest_client"
2
+
3
+ module Cheatr::Client
4
+ class Sheet
5
+
6
+ attr_reader :name, :contents
7
+ attr_reader :errors, :uri, :cache_file
8
+
9
+ def self.all(query = nil)
10
+ RestClient.get "http://#{Cheatr::Client.server}/#{query}"
11
+ end
12
+
13
+ def initialize(name, opts = {})
14
+ raise "Sheet name '#{name}' is not valid" unless name =~ Cheatr::SHEET_NAME_REGEXP
15
+ @name = name
16
+ @cache_file = File.join(Cheatr::Client.cache_dir, "#{name}.md")
17
+ @uri = "http://#{Cheatr::Client.server}/#{name}"
18
+ fetch(opts)
19
+ end
20
+
21
+ #
22
+ # Sets new contents to be saved.
23
+ #
24
+ def contents=(new_contents)
25
+ if new_contents != contents
26
+ @old_contents = contents
27
+ @contents = new_contents
28
+ end
29
+ new_contents
30
+ end
31
+
32
+ #
33
+ # Returns true if the contents were last fetched from the remote server,
34
+ # false if fetched from cache.
35
+ #
36
+ def remote?
37
+ @remote == true
38
+ end
39
+
40
+ #
41
+ # Returns true if the contents have been modified and need to be saved,
42
+ # false otherwise.
43
+ #
44
+ def changed?
45
+ @old_contents != @contents
46
+ end
47
+
48
+ #
49
+ # Saves the contents if changed.
50
+ #
51
+ # Cache is updated if saving to remote server was successful.
52
+ #
53
+ # Returns true if successful, false otherwise.
54
+ #
55
+ def save
56
+ return false if contents.nil?
57
+ if changed? && save_remote
58
+ # Re-fetch because the server may modify contents on saving.
59
+ # Cached version will be saved during re-fetching below.
60
+ fetch(ignore_cache: true)
61
+ end
62
+ !changed?
63
+ end
64
+
65
+ #
66
+ # Fetches the contents, either from cache if available, or from the remote
67
+ # server.
68
+ #
69
+ # If ignore_cache is true, the cache is ignored, and contents are updated
70
+ # from the remote server.
71
+ #
72
+ # If contents are fetched from the remote server, the cache is updated.
73
+ # Returns true if successful, false otherwise.
74
+ #
75
+ def fetch(opts = {})
76
+ fetched = opts[:ignore_cache] ? nil : fetch_cache
77
+ fetched ||= fetch_remote
78
+ if fetched
79
+ @contents = fetched
80
+ @old_contents = @contents
81
+ save_cache if remote?
82
+ end
83
+ !!fetched
84
+ end
85
+
86
+ private
87
+
88
+ #
89
+ # Saves contents to the cache file.
90
+ #
91
+ def save_cache
92
+ File.open(cache_file, 'w') { |f| f.write(contents) } unless contents.nil?
93
+ end
94
+
95
+ #
96
+ # Returns the cached contents, or nil.
97
+ #
98
+ def fetch_cache
99
+ @remote = false
100
+ File.read(cache_file) rescue nil
101
+ end
102
+
103
+ #
104
+ # Saves the contents to remote server.
105
+ #
106
+ # Returns true if successful, false otherwise.
107
+ #
108
+ def save_remote
109
+ RestClient.put uri, contents
110
+ @errors = nil
111
+ true
112
+ rescue RestClient::BadRequest => e
113
+ @errors = e.response.body.strip.split("\n")
114
+ false
115
+ rescue Errno::ECONNREFUSED => e
116
+ @errors = ["Could not connect to server (#{e.message})"]
117
+ false
118
+ end
119
+
120
+ #
121
+ # Fetches contents from remote server.
122
+ #
123
+ # Returns the contents, or nil if unsuccessful.
124
+ #
125
+ def fetch_remote
126
+ @remote = true
127
+ RestClient.get uri
128
+ rescue RestClient::ResourceNotFound
129
+ @errors = ["Sheet '#{name}' does not exist."]
130
+ nil
131
+ rescue Errno::ECONNREFUSED => e
132
+ @errors = ["Could not connect to server (#{e.message})"]
133
+ nil
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,7 @@
1
+ module Cheatr
2
+ class Error < StandardError
3
+ def initialize(message)
4
+ super(message)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+
2
+ module Cheatr
3
+ module Server
4
+ autoload :Sheet, "cheatr/server/sheet"
5
+ autoload :App, "cheatr/server/app"
6
+
7
+ def self.run(repository, opts = {})
8
+ @@config = @@config.merge(opts.to_hash)
9
+ Sheet.repository = File.expand_path(repository)
10
+ puts "Serving cheatr repository at #{Sheet.repository}"
11
+ App.run!
12
+ rescue Cheatr::Error => e
13
+ puts "Error: #{e.message}"
14
+ end
15
+
16
+ @@config = {}
17
+
18
+ def self.config
19
+ @@config
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,80 @@
1
+ require 'erb'
2
+ require 'redcarpet'
3
+ require 'sinatra/base'
4
+ require 'cheatr/server/sheet'
5
+ require 'cheatr/server/helpers'
6
+
7
+ module Cheatr::Server
8
+ class App < Sinatra::Base
9
+ include Helpers
10
+
11
+ # Routes
12
+
13
+ get '/' do
14
+ @title = 'Cheat sheets'
15
+ @sheets = Sheet.all
16
+ template :index
17
+ end
18
+
19
+ get '/:name' do |name|
20
+ if query? name
21
+ @sheets = Sheet.all(name)
22
+ @title = "Cheat sheets matching '#{name}'"
23
+ template :index
24
+ else
25
+ @sheet = Sheet.find!(name)
26
+ @title = @sheet.human_name
27
+ template :sheet
28
+ end
29
+ end
30
+
31
+ put '/:name' do |name|
32
+ @sheet = Sheet.new(name)
33
+ @sheet.contents = request.body.read
34
+ if @sheet.save
35
+ text "Sheet #{name} updated successfully"
36
+ else
37
+ text @sheet.errors.full_messages, status: 400
38
+ end
39
+ end
40
+
41
+ # Configuration
42
+
43
+ def self.base_path(append = nil)
44
+ append ? File.join(path, append) : path
45
+ end
46
+
47
+ configure do
48
+ set :app_file, __FILE__
49
+ set :base_path, File.dirname(__FILE__)
50
+ set :views, File.join(settings.base_path, 'views')
51
+ set :public_folder, File.join(settings.base_path, 'public')
52
+ Cheatr::Server.config.each_pair do |option, value|
53
+ set option.to_sym, value
54
+ end
55
+ end
56
+
57
+ configure :production, :development do
58
+ enable :logging
59
+ end
60
+
61
+ configure :development do
62
+ enable :logging, :dump_errors, :raise_errors
63
+ end
64
+
65
+ configure :production do
66
+ set :raise_errors, false
67
+ set :show_exceptions, false
68
+ end
69
+
70
+ error do
71
+ status 500
72
+ template :error
73
+ end
74
+
75
+ not_found do
76
+ send_file File.join(settings.public_folder, '404.html'), status: 404
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,47 @@
1
+ module Cheatr::Server
2
+ module Helpers
3
+ def query?(name)
4
+ name.include? '*'
5
+ end
6
+
7
+ def html?
8
+ request.accept? 'text/html'
9
+ end
10
+
11
+ def default_content_type
12
+ html? ? 'text/html' : 'text/markdown'
13
+ end
14
+
15
+ #
16
+ # Processes the given text as markdown, additionally processing cheatr links as well.
17
+ #
18
+ def md(text)
19
+ markdown text.
20
+ gsub(/{{([a-z]+([\.\-\_][a-z]+)*)}}/, '[\1](/\1)'). # {{name}} -> [name](/name)
21
+ gsub(/{{([^\|}]+)\|([a-z]+([\.\-\_][a-z]+)*)}}/, '[\1](/\2)'). # {{link text|name}} -> [link text](/name)
22
+ to_s
23
+ end
24
+
25
+ def text(output, opts = {})
26
+ content_type 'text/plain'
27
+ status opts[:status] if opts[:status]
28
+ if output.is_a?(Array)
29
+ output = output.map { |s| "#{s}\n" }
30
+ end
31
+ logger.info output
32
+ output
33
+ end
34
+
35
+ def template(name, opts = {})
36
+ status opts[:status] if opts[:status]
37
+ content_type opts[:content_type] || default_content_type
38
+ logger.info "Rendering template #{name}"
39
+ output = erb :"#{name}.md", layout: false
40
+ if html?
41
+ erb md(output), layout: :"layout.html"
42
+ else
43
+ output
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,157 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Page Not Found :( - Cheatr</title>
6
+ <style>
7
+ ::-moz-selection {
8
+ background: #b3d4fc;
9
+ text-shadow: none;
10
+ }
11
+
12
+ ::selection {
13
+ background: #b3d4fc;
14
+ text-shadow: none;
15
+ }
16
+
17
+ html {
18
+ padding: 30px 10px;
19
+ font-size: 20px;
20
+ line-height: 1.4;
21
+ color: #737373;
22
+ background: #f0f0f0;
23
+ -webkit-text-size-adjust: 100%;
24
+ -ms-text-size-adjust: 100%;
25
+ }
26
+
27
+ html,
28
+ input {
29
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
30
+ }
31
+
32
+ body {
33
+ max-width: 500px;
34
+ _width: 500px;
35
+ padding: 30px 20px 50px;
36
+ border: 1px solid #b3b3b3;
37
+ border-radius: 4px;
38
+ margin: 0 auto;
39
+ box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
40
+ background: #fcfcfc;
41
+ }
42
+
43
+ h1 {
44
+ margin: 0 10px;
45
+ font-size: 50px;
46
+ text-align: center;
47
+ }
48
+
49
+ h1 span {
50
+ color: #bbb;
51
+ }
52
+
53
+ h3 {
54
+ margin: 1.5em 0 0.5em;
55
+ }
56
+
57
+ p {
58
+ margin: 1em 0;
59
+ }
60
+
61
+ ul {
62
+ padding: 0 0 0 40px;
63
+ margin: 1em 0;
64
+ }
65
+
66
+ .container {
67
+ max-width: 380px;
68
+ _width: 380px;
69
+ margin: 0 auto;
70
+ }
71
+
72
+ /* google search */
73
+
74
+ #goog-fixurl ul {
75
+ list-style: none;
76
+ padding: 0;
77
+ margin: 0;
78
+ }
79
+
80
+ #goog-fixurl form {
81
+ margin: 0;
82
+ }
83
+
84
+ #goog-wm-qt,
85
+ #goog-wm-sb {
86
+ border: 1px solid #bbb;
87
+ font-size: 16px;
88
+ line-height: normal;
89
+ vertical-align: top;
90
+ color: #444;
91
+ border-radius: 2px;
92
+ }
93
+
94
+ #goog-wm-qt {
95
+ width: 220px;
96
+ height: 20px;
97
+ padding: 5px;
98
+ margin: 5px 10px 0 0;
99
+ box-shadow: inset 0 1px 1px #ccc;
100
+ }
101
+
102
+ #goog-wm-sb {
103
+ display: inline-block;
104
+ height: 32px;
105
+ padding: 0 10px;
106
+ margin: 5px 0 0;
107
+ white-space: nowrap;
108
+ cursor: pointer;
109
+ background-color: #f5f5f5;
110
+ background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
111
+ background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
112
+ background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
113
+ background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
114
+ -webkit-appearance: none;
115
+ -moz-appearance: none;
116
+ appearance: none;
117
+ *overflow: visible;
118
+ *display: inline;
119
+ *zoom: 1;
120
+ }
121
+
122
+ #goog-wm-sb:hover,
123
+ #goog-wm-sb:focus {
124
+ border-color: #aaa;
125
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
126
+ background-color: #f8f8f8;
127
+ }
128
+
129
+ #goog-wm-qt:hover,
130
+ #goog-wm-qt:focus {
131
+ border-color: #105cb6;
132
+ outline: 0;
133
+ color: #222;
134
+ }
135
+
136
+ input::-moz-focus-inner {
137
+ padding: 0;
138
+ border: 0;
139
+ }
140
+ </style>
141
+ </head>
142
+ <body>
143
+ <div class="container">
144
+ <h1>Not found <span>:(</span></h1>
145
+ <p>Sorry, but the page you were trying to view does not exist.</p>
146
+ <p>It looks like this was the result of either:</p>
147
+ <ul>
148
+ <li>a mistyped address</li>
149
+ <li>an out-of-date link</li>
150
+ </ul>
151
+ <script>
152
+ var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
153
+ </script>
154
+ <script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
155
+ </div>
156
+ </body>
157
+ </html>