chisel 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/bin/chisel CHANGED
@@ -1,45 +1,120 @@
1
1
  #! /usr/bin/env ruby
2
2
 
3
3
  require 'chisel/wrapper'
4
+ require 'filesystemwatcher/filesystemwatcher'
4
5
  require 'optparse'
6
+ require 'webrick'
5
7
 
6
- module Chisel
7
- if ARGV.count == 0
8
- w = Wrapper.new('.')
9
- w.run
10
- else
8
+ include WEBrick
9
+ include Chisel
10
+
11
+ def start_webrick(config = {})
12
+ server = HTTPServer.new(config)
13
+
14
+ yield server if block_given?
15
+ ['INT', 'TERM'].each do |signal|
16
+ trap(signal) { server.shutdown }
17
+ end
18
+
19
+ puts "\n*** Press Ctrl-C to stop the server ***\n\n"
20
+
21
+ server.start
22
+ end
11
23
 
12
- case ARGV[0].downcase
13
-
14
- when 'new'
15
- # TODO: Add checking for existing site, with option to "force" creation
24
+ # Set up default command-line options
25
+
26
+ options = {}
27
+ options[:verbose] = false
28
+ options[:server] = false
29
+ options[:watch] = false
30
+ options[:port] = 4000
31
+
32
+ # Parse out options
33
+
34
+ OptionParser.new do |opts|
35
+ opts.banner = 'Usage: chisel [options] [new|resource] [command]'
36
+
37
+ opts.on('-v', '--verbose', 'Run verbosely') do |v|
38
+ options[:verbose] = v
39
+ end
40
+
41
+ opts.on('-s', '--server [port]', 'Run local WEBrick server (default port 4000)') do |port|
42
+ options[:server] = true
43
+ options[:port] = port.to_i if port
44
+ end
45
+
46
+ opts.on('-w', '--watch', 'Watch filesystem for changes and update automatically') do |w|
47
+ options[:watch] = w
48
+ end
49
+
50
+ end.parse!
51
+
52
+ # Run the appropriate command
53
+
54
+ if ARGV.count == 0
55
+
56
+ site_dir = SiteDirectory.new('.')
57
+
58
+ w = Wrapper.new(site_dir)
59
+ w.run
60
+
61
+ if options[:watch]
62
+ puts 'File system watcher not yet implemented. Sorry!'
63
+ # TODO: Implement file system watcher
16
64
 
17
- if ARGV.length == 1 then
18
- puts 'Creating new Chisel site in the current directory...'
19
- SiteDirectory.create('.')
20
- puts 'Done. Run \'chisel\' to create your site.'
21
- else
22
- dir = ARGV[1]
23
- puts "Creating new Chisel site in directory '#{dir}'..."
24
- SiteDirectory.create(dir)
25
- puts "Done. Go into \'#{dir}\' and run \'chisel\' to create your site."
26
- end
65
+ # watcher = FileSystemWatcher.new
66
+ # watcher.addDirectory(site_dir.realdirpath.to_s, '*.*')
67
+ #
68
+ # site_dir.entries.each do |entry|
69
+ #
70
+ # end
71
+ #
72
+ # watcher.sleepTime = 1
73
+ # watcher.start do |status, file|
74
+ # w1 = Wrapper.new(site_dir)
75
+ # w1.run
76
+ # end
77
+ # watcher.join
78
+ end
79
+
80
+ if options[:server]
81
+ output_dir = site_dir.output_dir.realdirpath.to_s
82
+ start_webrick(:DocumentRoot => output_dir, :Port => options[:port])
83
+ end
84
+
85
+ else
86
+
87
+ case ARGV[0].downcase
88
+
89
+ when 'new'
90
+ # TODO: Add checking for existing site, with option to "force" creation
91
+
92
+ if ARGV.length == 1 then
93
+ puts 'Creating new Chisel site in the current directory...'
94
+ SiteDirectory.create('.')
95
+ puts 'Done. Run \'chisel\' to create your site.'
96
+ else
97
+ dir = ARGV[1]
98
+ puts "Creating new Chisel site in directory '#{dir}'..."
99
+ SiteDirectory.create(dir)
100
+ puts "Done. Go into \'#{dir}\' and run \'chisel\' to create your site."
101
+ end
102
+
103
+ when 'resource'
27
104
 
28
- when 'resource'
29
-
30
- if ARGV.length == 1 then
31
- # TODO: Something
32
- else
33
- site_dir = SiteDirectory.new('.')
34
- case ARGV[1].downcase
35
- when 'new'
36
- site_dir.create_resource(ARGV[2])
37
- end
105
+ if ARGV.length == 1 then
106
+ # TODO: Something
107
+ else
108
+ site_dir = SiteDirectory.new('.')
109
+ case ARGV[1].downcase
110
+ when 'new'
111
+ # TODO: Check that ARGV[2] exists and show help otherwise
112
+ site_dir.create_resource(ARGV[2])
38
113
  end
39
-
40
- when 'help'
41
-
42
114
  end
43
115
 
116
+ when 'help'
117
+
44
118
  end
119
+
45
120
  end
@@ -1,5 +1,5 @@
1
1
  class Array
2
- def sort_by(method_name, ascending=true)
2
+ def sort_by_method(method_name, ascending=true)
3
3
  method_name = method_name.to_sym
4
4
  self.sort do |left, right|
5
5
  if left.respond_to?(method_name) and right.respond_to?(method_name)
@@ -61,9 +61,9 @@ module Chisel
61
61
  self.output_dir.join(*resource.id, view_with_extension(view))
62
62
  end
63
63
 
64
- def page_output_path(page)
64
+ def page_output_path(page, view_output_dir)
65
65
  page = view_with_extension(page)
66
- self.output_dir.join(page)
66
+ self.site_relative_path(page, view_output_dir)
67
67
  end
68
68
 
69
69
  def layout_view_path(layout_name)
@@ -73,6 +73,14 @@ module Chisel
73
73
  def view_path(view)
74
74
  self.view_dir.join("#{view_with_extension(view)}.erb")
75
75
  end
76
+
77
+ def site_relative_path(relative_path, view_output_dir)
78
+ if relative_path[0] == '/'
79
+ self.output_dir.join(relative_path[1..-1])
80
+ else
81
+ view_output_dir.join(relative_path)
82
+ end
83
+ end
76
84
 
77
85
  def view_with_extension(view)
78
86
  view = "#{view}index" if view[-1] == '/'
data/lib/chisel/view.rb CHANGED
@@ -26,7 +26,7 @@ module Chisel
26
26
 
27
27
  @path = Pathname.new(options[:path]) if options[:path]
28
28
  @site_dir = SiteDirectory.new(options[:site_dir]) if options[:site_dir]
29
-
29
+
30
30
  if options[:resource]
31
31
  @type = :resource
32
32
  @resource = options[:resource]
@@ -96,6 +96,8 @@ module Chisel
96
96
 
97
97
  header_vars = {}
98
98
  if options[:resource]
99
+ resource_key = options[:resource].resource_type.to_sym
100
+ header_vars[resource_key] = options[:resource]
99
101
  header_vars[:resource] = options[:resource]
100
102
  end
101
103
 
@@ -35,14 +35,14 @@ module Chisel
35
35
  end
36
36
 
37
37
  def link_to_page(page, text, options = {})
38
- page_output_path = @site_dir.page_output_path(page)
38
+ page_output_path = @site_dir.page_output_path(page, @output_path.dirname)
39
39
  href = page_output_path.relative_path_from(@output_path.dirname).to_s
40
40
 
41
41
  link_to(href, text, options)
42
42
  end
43
43
 
44
44
  def path_to(site_relative_path)
45
- @site_dir.output_dir.join(site_relative_path).relative_path_from(@output_path.dirname)
45
+ @site_dir.site_relative_path(site_relative_path, @output_path.dirname).relative_path_from(@output_path.dirname)
46
46
  end
47
47
 
48
48
  def link_to(href, text, options = {})
@@ -53,7 +53,7 @@ module Chisel
53
53
  end
54
54
  end
55
55
 
56
- href = href[0..-11] if href.end_with?('index.html')
56
+ href = href[0..-11] if href.end_with?('index.html') and href != 'index.html'
57
57
 
58
58
  "<a href=\"#{href}\"#{attribute_string}>#{text}</a>"
59
59
  end
@@ -0,0 +1,330 @@
1
+ require "digest/md5"
2
+ require "thread"
3
+ require "#{File.dirname(__FILE__)}/servicestate.rb"
4
+
5
+ # This class will watch a directory or a set of directories and alert you of
6
+ # new files, modified files, deleted files. You can optionally only be alerted
7
+ # when a files md5 hash has been changed so you only are alerted to real changes.
8
+ # this of course means slower performance and higher cpu/io usage.
9
+ class FileSystemWatcher
10
+ include ServiceState
11
+
12
+ CREATED = 0
13
+ MODIFIED = 1
14
+ DELETED = 2
15
+
16
+ # the time to wait before checking the directories again
17
+ attr_accessor :sleepTime, :priority, :directories
18
+
19
+ # you can optionally use the file contents md5 to detect if a file has changed
20
+ attr_accessor :useMD5
21
+
22
+ def initialize(dir=nil, expression="**/*")
23
+ @sleepTime = 5
24
+ @useMD5 = false
25
+ @priority = 0
26
+ @stopWhen = nil
27
+
28
+ @directories = Array.new()
29
+ @files = Array.new()
30
+
31
+ @foundFiles = nil
32
+ @firstLoad = true
33
+ @watchThread = nil
34
+
35
+ initializeState()
36
+
37
+ if dir then
38
+ addDirectory(dir, expression)
39
+ end
40
+ end
41
+
42
+ # add a directory to be watched
43
+ # @param dir the directory to watch
44
+ # @param expression the glob pattern to search under the watched directory
45
+ def addDirectory(dir, expression="**/*")
46
+ if FileTest.exists?(dir) && FileTest.readable?(dir) then
47
+ @directories << FSWatcher::Directory.new(dir, expression)
48
+ else
49
+ raise FSWatcher::InvalidDirectoryError, "Dir '#{dir}' either doesnt exist or isnt readable"
50
+ end
51
+ end
52
+
53
+ def removeDirectory(dir)
54
+ @directories.delete(dir)
55
+ end
56
+
57
+ # add a specific file to the watch list
58
+ # @param file the file to watch
59
+ def addFile(file)
60
+ if FileTest.exists?(file) && FileTest.readable?(file) then
61
+ @files << file
62
+ else
63
+ raise FSWatcher::InvalidFileError, "File '#{file}' either doesnt exist or isnt readable"
64
+ end
65
+ end
66
+
67
+ def removeFile(file)
68
+ @files.delete(file)
69
+ end
70
+
71
+ # start watching the specified files/directories
72
+ def start(&block)
73
+ if isStarted? then
74
+ raise RuntimeError, "already started"
75
+ end
76
+
77
+ setState(STARTED)
78
+
79
+ @firstLoad = true
80
+ @foundFiles = Hash.new()
81
+
82
+ # we watch in a new thread
83
+ @watchThread = Thread.new {
84
+ # we will be stopped if someone calls stop or if someone set a stopWhen that becomes true
85
+ while !isStopped? do
86
+ if (!@directories.empty?) or (!@files.empty?) then
87
+ # this will hold the list of the files we looked at this iteration
88
+ # allows us to not look at the same file again and also to compare
89
+ # with the foundFile list to see if something was deleted
90
+ alreadyExamined = Hash.new()
91
+
92
+ # check the files in each watched directory
93
+ if not @directories.empty? then
94
+ @directories.each { |dirObj|
95
+ examineFileList(dirObj.getFiles(), alreadyExamined, &block)
96
+ }
97
+ end
98
+
99
+ # now examine any files the user wants to specifically watch
100
+ examineFileList(@files, alreadyExamined, &block) if not @files.empty?
101
+
102
+ # see if we have to delete files from our found list
103
+ if not @firstLoad then
104
+ if not @foundFiles.empty? then
105
+ # now diff the found files and the examined files to see if
106
+ # something has been deleted
107
+ allFoundFiles = @foundFiles.keys()
108
+ allExaminedFiles = alreadyExamined.keys()
109
+ intersection = allFoundFiles - allExaminedFiles
110
+ intersection.each { |fileName|
111
+ # callback
112
+ block.call(DELETED, fileName)
113
+ # remove deleted file from the foundFiles list
114
+ @foundFiles.delete(fileName)
115
+ }
116
+ end
117
+ else
118
+ @firstLoad = false
119
+ end
120
+ end
121
+
122
+ # go to sleep
123
+ sleep(@sleepTime)
124
+ end
125
+ }
126
+
127
+ # set the watch thread priority
128
+ @watchThread.priority = @priority
129
+
130
+ end
131
+
132
+ # kill the filewatcher thread
133
+ def stop()
134
+ setState(STOPPED)
135
+ @watchThread.wakeup()
136
+ end
137
+
138
+ # wait for the filewatcher to finish
139
+ def join()
140
+ @watchThread.join() if @watchThread
141
+ end
142
+
143
+
144
+ private
145
+
146
+ # loops over the file list check for new or modified files
147
+ def examineFileList(fileList, alreadyExamined, &block)
148
+ fileList.each { |fileName|
149
+ # expand the file name to the fully qual path
150
+ fullFileName = File.expand_path(fileName)
151
+
152
+ # dont examine the same file 2 times
153
+ if not alreadyExamined.has_key?(fullFileName) then
154
+ # we cant do much if the file isnt readable anyway
155
+ if File.readable?(fullFileName) then
156
+ # set that we have seen this file
157
+ alreadyExamined[fullFileName] = true
158
+
159
+ # get the file info
160
+ modTime, size = File.mtime(fullFileName), File.size(fullFileName)
161
+
162
+ # on the first iteration just load all of the files into the foundList
163
+ if @firstLoad then
164
+ @foundFiles[fullFileName] = FSWatcher::FoundFile.new(fullFileName, modTime, size, false, @useMD5)
165
+ else
166
+ # see if we have found this file already
167
+ foundFile = @foundFiles[fullFileName]
168
+
169
+ if foundFile then
170
+
171
+ # if a file is marked as new, we still need to make sure it isnt still
172
+ # being written to. we do this by checking the file sizes.
173
+ if foundFile.isNew? then
174
+
175
+ # if the file size is the same then it is probably done being written to
176
+ # unless the writer is really slow
177
+ if size == foundFile.size then
178
+
179
+ # callback
180
+ block.call(CREATED, fullFileName)
181
+
182
+ # mark this file as a changed file now
183
+ foundFile.updateModTime(modTime)
184
+
185
+ # generate the md5 for the file since we know it is done
186
+ # being written to
187
+ foundFile.genMD5() if @useMD5
188
+
189
+ else
190
+
191
+ # just update the size so we can check again at the next iteration
192
+ foundFile.updateSize(size)
193
+
194
+ end
195
+
196
+ elsif modTime > foundFile.modTime then
197
+
198
+ # if the mod times are different on files we already have
199
+ # found this is an update
200
+ willYield = true
201
+
202
+ # if we are using md5's then compare them
203
+ if @useMD5 then
204
+ filesMD5 = FSWatcher.genFileMD5(fullFileName)
205
+ if filesMD5 && foundFile.md5 then
206
+ if filesMD5.to_s == foundFile.md5.to_s then
207
+ willYield = false
208
+ end
209
+ end
210
+
211
+ # if we are yielding then the md5s are dif so
212
+ # update the cached md5 value
213
+ foundFile.setMD5(filesMD5) if willYield
214
+
215
+ end
216
+
217
+ block.call(MODIFIED, fullFileName) if willYield
218
+ foundFile.updateModTime(modTime)
219
+
220
+ end
221
+
222
+ else
223
+
224
+ # this is a new file for our list. dont update the md5 here since
225
+ # the file might not yet be done being written to
226
+ @foundFiles[fullFileName] = FSWatcher::FoundFile.new(fullFileName, modTime, size)
227
+
228
+ end
229
+ end
230
+ end
231
+ end
232
+ }
233
+ end
234
+ end
235
+
236
+ # Util classes for the FileSystemWatcher
237
+ module FSWatcher
238
+ # The directory to watch
239
+ class Directory
240
+ attr_reader :dir, :expression
241
+
242
+ def initialize(dir, expression)
243
+ @dir, @expression = dir, expression
244
+ @dir.chop! if @dir =~ %r{/$}
245
+ end
246
+
247
+ def getFiles()
248
+ return Dir[@dir + "/" + @expression]
249
+ end
250
+ end
251
+
252
+ # A FoundFile entry for the FileSystemWatcher
253
+ class FoundFile
254
+ attr_reader :status, :fileName, :modTime, :size, :md5
255
+
256
+ def initialize(fileName, modTime, size, isNewFile=true, useMD5=false)
257
+ @fileName, @modTime, @size, @isNewFile = fileName, modTime, size, isNewFile
258
+ @md5 = nil
259
+ if useMD5 then
260
+ genMD5()
261
+ end
262
+ end
263
+
264
+ def updateModTime(modTime)
265
+ @modTime = modTime
266
+ @isNewFile = false
267
+ end
268
+
269
+ def updateSize(size)
270
+ @size = size
271
+ end
272
+
273
+ def isNew?
274
+ return @isNewFile
275
+ end
276
+
277
+ def setMD5(newMD5)
278
+ @md5 = newMD5
279
+ end
280
+
281
+ # generate my files md5 value
282
+ def genMD5()
283
+ @md5 = FSWatcher.genFileMD5(@fileName)
284
+ end
285
+ end
286
+
287
+ # utility function for generating md5s from a files contents
288
+ def FSWatcher.genFileMD5(fileName)
289
+ if FileTest.file?(fileName) then
290
+ f = File.open(fileName)
291
+ contents = f.read()
292
+ f.close()
293
+ return MD5.new(contents) if contents
294
+ end
295
+ return nil
296
+ end
297
+
298
+ # if the directory you want to watch doesnt exist or isnt readable this is thrown
299
+ class InvalidDirectoryError < StandardError; end
300
+
301
+ # if the file you want to watch doesnt exist or isnt readable this is thrown
302
+ class InvalidFileError < StandardError; end
303
+ end
304
+
305
+ #--- main program ----
306
+ if __FILE__ == $0
307
+ watcher = FileSystemWatcher.new()
308
+ watcher.addDirectory("/cygdrive/c/Inetpub/ftproot/", "*.xml")
309
+ watcher.sleepTime = 3
310
+ watcher.useMD5 = true
311
+
312
+ test = false
313
+ watcher.stopWhen {
314
+ test == true
315
+ }
316
+
317
+ watcher.start() { |status,file|
318
+ if status == FileSystemWatcher::CREATED then
319
+ puts "created: #{file}"
320
+ elsif status == FileSystemWatcher::MODIFIED then
321
+ puts "modified: #{file}"
322
+ elsif status == FileSystemWatcher::DELETED then
323
+ puts "deleted: #{file}"
324
+ end
325
+ }
326
+
327
+ sleep(10)
328
+ test = true
329
+ watcher.join()
330
+ end
@@ -0,0 +1,75 @@
1
+ require 'thread'
2
+
3
+ # The Runnable module is a generic mixin for including state and
4
+ # status information in a class
5
+ module ServiceState
6
+ # state constants
7
+ NOT_STARTED = 0
8
+ STARTED = 1
9
+ STOPPED = 2
10
+ CONFIGURED = 3
11
+
12
+ attr_reader :startTime, :endTime
13
+
14
+ # Initialize the state information
15
+ def initializeState()
16
+ @configured = false
17
+ @startTime = 0
18
+ @stopTime = 0
19
+
20
+ @stateMutex = Mutex.new()
21
+ @stopWhen = nil
22
+ setState(NOT_STARTED)
23
+ end
24
+
25
+ # Set the callback for when someone calls setState. You
26
+ # will be passed the state CONSTANT being set
27
+ def onStateChange(&callbackBlock)
28
+ @stateCallback = callbackBlock
29
+ end
30
+
31
+ # All methods, inside this class or not, should use this
32
+ # method to change the state of the JobRunner
33
+ # @param newState The new state value
34
+ def setState(newState)
35
+ @stateMutex.synchronize {
36
+ if newState == CONFIGURED then
37
+ @configured = true
38
+ else
39
+ @state = newState
40
+ if isStarted? then
41
+ @startTime = Time.now()
42
+ elsif isStopped?
43
+ @stopTime = Time.now()
44
+ end
45
+ end
46
+ }
47
+
48
+ if defined?(@stateCallback) then
49
+ @stateCallback.call(newState)
50
+ end
51
+ end
52
+
53
+ def isConfigured?
54
+ return @configured
55
+ end
56
+
57
+ def isStarted?
58
+ return @state == STARTED
59
+ end
60
+
61
+ def isStopped?
62
+ if @state == STOPPED then
63
+ return true
64
+ elsif @stopWhen && @stopWhen.call() then
65
+ setState(STOPPED)
66
+ return true
67
+ else
68
+ return false
69
+ end
70
+ end
71
+
72
+ def stopWhen(&block)
73
+ @stopWhen = block
74
+ end
75
+ end
metadata CHANGED
@@ -1,27 +1,45 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: chisel
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
4
5
  prerelease:
5
- version: 0.0.1
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Rockwell Schrock
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-06-07 00:00:00 Z
14
- dependencies: []
15
-
12
+ date: 2011-06-20 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: RedCloth
16
+ requirement: &2152864840 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 4.2.7
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2152864840
25
+ - !ruby/object:Gem::Dependency
26
+ name: maruku
27
+ requirement: &2152864360 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.6.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2152864360
16
36
  description: Chisel is tool to generate simple, resource-based static Web sites.
17
37
  email: schrockwell@gmail.com
18
- executables:
38
+ executables:
19
39
  - chisel
20
40
  extensions: []
21
-
22
41
  extra_rdoc_files: []
23
-
24
- files:
42
+ files:
25
43
  - lib/chisel/new_site/_config.yml
26
44
  - lib/chisel/new_site/_views/_layout/main.html.erb
27
45
  - lib/chisel/new_site/index.html.erb
@@ -36,33 +54,31 @@ files:
36
54
  - lib/chisel/view.rb
37
55
  - lib/chisel/view_helper.rb
38
56
  - lib/chisel/wrapper.rb
57
+ - lib/filesystemwatcher/filesystemwatcher.rb
58
+ - lib/filesystemwatcher/servicestate.rb
39
59
  - bin/chisel
40
60
  homepage: https://github.com/schrockwell/chisel
41
61
  licenses: []
42
-
43
62
  post_install_message:
44
63
  rdoc_options: []
45
-
46
- require_paths:
64
+ require_paths:
47
65
  - lib
48
- required_ruby_version: !ruby/object:Gem::Requirement
66
+ required_ruby_version: !ruby/object:Gem::Requirement
49
67
  none: false
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: "0"
54
- required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
73
  none: false
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- version: "0"
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
60
78
  requirements: []
61
-
62
79
  rubyforge_project:
63
80
  rubygems_version: 1.8.5
64
81
  signing_key:
65
82
  specification_version: 3
66
83
  summary: A static Web site generator.
67
84
  test_files: []
68
-