chisel 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-