diru 0.0.1

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +3 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +260 -0
  5. data/bin/diru +1010 -0
  6. data/lib/version.rb +6 -0
  7. data/test/dir_0/dir_0_0/dir_0_0_0/empty.txt +0 -0
  8. data/test/dir_0/dir_0_0/dir_0_0_1/empty.txt +0 -0
  9. data/test/dir_0/dir_0_0/dir_0_0_2/empty.txt +0 -0
  10. data/test/dir_0/dir_0_1/dir_0_1_0/empty.txt +0 -0
  11. data/test/dir_0/dir_0_1/dir_0_1_1/empty.txt +0 -0
  12. data/test/dir_0/dir_0_1/dir_0_1_2/empty.txt +0 -0
  13. data/test/dir_0/dir_0_2/dir_0_2_0/empty.txt +0 -0
  14. data/test/dir_0/dir_0_2/dir_0_2_1/empty.txt +0 -0
  15. data/test/dir_0/dir_0_2/dir_0_2_2/empty.txt +0 -0
  16. data/test/dir_0/dir_0_3/dir_0_3_0/empty.txt +0 -0
  17. data/test/dir_0/dir_0_3/dir_0_3_1/empty.txt +0 -0
  18. data/test/dir_0/dir_0_3/dir_0_3_2/empty.txt +0 -0
  19. data/test/dir_0/dir_0_4/dir_0_4_0/empty.txt +0 -0
  20. data/test/dir_0/dir_0_4/dir_0_4_1/empty.txt +0 -0
  21. data/test/dir_0/dir_0_4/dir_0_4_2/empty.txt +0 -0
  22. data/test/dir_1/dir_1_0/dir_1_0_0/empty.txt +0 -0
  23. data/test/dir_1/dir_1_0/dir_1_0_1/empty.txt +0 -0
  24. data/test/dir_1/dir_1_0/dir_1_0_2/empty.txt +0 -0
  25. data/test/dir_1/dir_1_1/dir_1_1_0/empty.txt +0 -0
  26. data/test/dir_1/dir_1_1/dir_1_1_1/empty.txt +0 -0
  27. data/test/dir_1/dir_1_1/dir_1_1_2/empty.txt +0 -0
  28. data/test/dir_1/dir_1_2/dir_1_2_0/empty.txt +0 -0
  29. data/test/dir_1/dir_1_2/dir_1_2_1/empty.txt +0 -0
  30. data/test/dir_1/dir_1_2/dir_1_2_2/empty.txt +0 -0
  31. data/test/dir_1/dir_1_3/dir_1_3_0/empty.txt +0 -0
  32. data/test/dir_1/dir_1_3/dir_1_3_1/empty.txt +0 -0
  33. data/test/dir_1/dir_1_3/dir_1_3_2/empty.txt +0 -0
  34. data/test/dir_1/dir_1_4/dir_1_4_0/empty.txt +0 -0
  35. data/test/dir_1/dir_1_4/dir_1_4_1/empty.txt +0 -0
  36. data/test/dir_1/dir_1_4/dir_1_4_2/empty.txt +0 -0
  37. data/test/golden.log +243 -0
  38. data/test/test_diru.rb +72 -0
  39. data/test/test_diru.sh +84 -0
  40. metadata +111 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 909b1ab4f9f1edcafd5b5a7e0e9f025663dc753c
4
+ data.tar.gz: 2d6cee00599bc252645141e237cf9a51e092ba2c
5
+ SHA512:
6
+ metadata.gz: cba95aeefd39cf86ed3c6ecce6413d8ff995bc2d85431b23f1bf8a32faa1d0ed173a5dc5f8b4569ab9001f74e695151ff4ad49d2c74d5f97feae832435b42586
7
+ data.tar.gz: f462f73de40bf425a332cacd7ac32d74e227e64a318bb9e97c877872e6e8b5740bb17f2f03f33d4ea0e1c67a88a04a9c28df15bc001735bb4771775ebe22ab5a
@@ -0,0 +1,3 @@
1
+ = Version history
2
+
3
+ [0.0.1] Initial version.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 tero.isannainen@gmail.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,260 @@
1
+ = Diru
2
+
3
+ * Introduction
4
+ * Getting started
5
+ * Client commands
6
+ * Configuration
7
+ * Practical usage
8
+
9
+ = Introduction
10
+
11
+ Diru is a Change Directory (cd) utility for augmenting Unix Shell
12
+ functionality. Diru uses server/client architecture, which enables
13
+ sharing of directory info between terminal sessions.
14
+
15
+ Each Server serves one Project, which is a tree of related directories
16
+ where user wants to jump around and which has a logical root. There
17
+ can be multiple Servers, if user needs to access multiple Projects
18
+ concurrently.
19
+
20
+ Client queries directory info from Server and directory change is
21
+ pushed to Shell in order to change the current directory within
22
+ Shell. Diru is not able to change the Shell directory by itself. User
23
+ must define a Shell function which can actually change the state of
24
+ the Shell.
25
+
26
+ Diru features:
27
+
28
+ * Support for multiple concurrent Projects.
29
+ * User and Project specific options (configuration).
30
+ * Jump to Project root.
31
+ * Find and jump to dir under Project root (regexp match).
32
+ * Find and jump to dir under current dir (regexp match).
33
+ * Favorite directories in options (configuration), for permanent
34
+ favorites.
35
+ * Bookmark saving and referencing, for current favorites.
36
+ * Directory change history and history referencing.
37
+ * Scratch Pad for directory copy/paste.
38
+ * Peer directory jumping, i.e. peer of current (regexp match).
39
+ * Online help.
40
+
41
+
42
+ = Getting started
43
+
44
+ The first thing to do is to start Diru Hub. Hub is a server process
45
+ that enables the Project specific Servers to be started. Hub will give
46
+ each new Server an unique port to operate with, starting from Hub Port
47
+ plus one.
48
+
49
+ Start Hub to port 42323:
50
+
51
+ shell> diru --hub --hport 42323
52
+
53
+ Start Server for a Project:
54
+
55
+ shell> cd <project_root_dir>
56
+ shell> diru --hport 42323 -s
57
+
58
+ Server will be listening to port "42324" (HubPort + 1). Following
59
+ Servers will get "42325", "42326" etc. Server displays the Port at
60
+ startup.
61
+
62
+ Server is a thread in Hub process. Servers can be started and killed
63
+ at will. Complete list of running Servers can be seen with:
64
+
65
+ shell> diru --hport 42323 -l
66
+
67
+ When Server starts, it will detect the Project root. It can be a file
68
+ or defined as environment variable. If ".diru_root_dir" file or a
69
+ ".diru.yml" file is found from current dir or some dir above, the dir
70
+ containing either of the files will become the Project root. If no files
71
+ are found, then DIRU_ROOT environment variable is used.
72
+
73
+ Client communicates with the Server in order to get and save directory
74
+ info. As mentioned above, Diru is more or less useless unless user has
75
+ defined a Shell function to realize directory changing.
76
+
77
+ Here is an example of such function for Bourne Shell based shells:
78
+
79
+ dr()
80
+ {
81
+ ret=`diru -p 42324 -c $*`
82
+ if test $? -eq 0; then
83
+ cd $ret
84
+ fi
85
+ }
86
+
87
+ Diru Client command is called now "dr". We specify with "-p" that port
88
+ "42324" is used towards the Server and with "-c" that we want to issue
89
+ directory "change" commands. In practice only part of the commands
90
+ will change directory, since some commands are only for queries.
91
+
92
+ If you want to see all Diru options, perform:
93
+
94
+ shell> diru -h
95
+
96
+ To jump to Project root, we do:
97
+
98
+ shell> dr r
99
+
100
+ If we want to jump to a subdir called "dir_0_0" (under root), we do:
101
+
102
+ shell> dr r dir_0_0
103
+
104
+ Note that "dir_0_0" does not have to be directly under Project root,
105
+ the directory is searched from the complete tree of directories under
106
+ Project root using regexp matching.
107
+
108
+ Now that we have changed directory for a couple of times, we can look
109
+ at the directory change history:
110
+
111
+ shell> dr h
112
+
113
+ We can reference the history by listed numbers. In order to jump back
114
+ to previous dir, we do:
115
+
116
+ shell> dr h 0
117
+
118
+
119
+ = Client commands
120
+
121
+ Client commands either change the current directory or query directory
122
+ info from Server.
123
+
124
+ Search commands:
125
+
126
+ * "dr r" - change to Project root dir.
127
+ * "dr r <dir>" - change to <dir> (somewhere) under Project root dir.
128
+ * "dr t <dir>" - change to <dir> (somewhere) under current dir.
129
+
130
+ Search can match to multiple directories. First match is used and the
131
+ rest (Left-overs) are displayed to the user. Left-overs are also
132
+ stored, and they can be referenced and used in order of appearance
133
+ with simply issuing "dr".
134
+
135
+ Search pattern can also be constructed from multiple pieces.
136
+
137
+ shell> dr r dir 1_0_1
138
+
139
+ See "dr i" for alternatives for single letter commands. For example
140
+ "r" can be replaced with "/".
141
+
142
+
143
+ Bookmark commands:
144
+
145
+ * "dr b" - display bookmarks.
146
+ * "dr b ." - add current dir to bookmarks.
147
+ * "dr b !" - reset (clear) bookmarks.
148
+ * "dr b s <file>" - store bookmarks to <file>.
149
+ * "dr b l <file>" - load bookmarks from <file>.
150
+ * "dr b d <num>" - delete bookmark with <num>.
151
+ * "dr b <num>" - change dir to bookmark <num>.
152
+
153
+ History commands:
154
+
155
+ * "dr h" - display history.
156
+ * "dr h ." - add current dir to history.
157
+ * "dr h !" - reset (clear) historys.
158
+ * "dr h ," - reference last history item.
159
+ * "dr h <num>" - change dir to history <num>.
160
+
161
+ Scratch Pad commands:
162
+
163
+ * "dr s ." - store current dir to Scratch Pad.
164
+ * "dr s <dir>" - store <dir> to Scratch Pad.
165
+ * "dr s" - change dir to Scratch Pad dir.
166
+
167
+ Misc commands:
168
+
169
+ * "dr p" - jump to peer dir, i.e. the peer of current (from options).
170
+ * "dr c <cmd>" - issue RPC command to server (reset etc., see below).
171
+ * "dr f" - list favorites dirs (from options).
172
+ * "dr f <dir>" - change dir to favorite <dir>.
173
+ * "dr i" - show command info.
174
+ * "dr <dir>" - change to given dir (must be under current).
175
+ * "dr" - change to next "Left-over" directory.
176
+
177
+ RPC (Remote Procedure Call) commands:
178
+
179
+ * "reset" - Reset Server state, i.e. History, Boorkmarks, Left-overs.
180
+ * "abook" - Add current dir to Bookmarks.
181
+ * "lbook" - List Bookmarks.
182
+ * "rbook" - Reset Bookmarks.
183
+ * "doc" - Show command documentation.
184
+
185
+
186
+ = Configuration
187
+
188
+ Diru uses several environment variables, which are optional, and
189
+ provides thereby backup values for non-specified command line info.
190
+
191
+ * DIRU_HUB_PORT - Hub Port, if nothing, 41114 is used.
192
+ * DIRU_PORT - Server Port, if nothing, ~/.diru.prt is used (Port File).
193
+ * DIRU_OPTS - options file, if nothing, ~/.diru.yml is used.
194
+ * DIRU_ROOT - Project root, used if ".diru_root_dir" and ".diru.yml"
195
+ are missing.
196
+
197
+ If Hub is started without "--hport" option, Diru checks if
198
+ DIRU_HUB_PORT is defined. If not, port 41114 is used.
199
+
200
+ If Client is used without "-p" option, Diru checks if DIRU_PORT is
201
+ defined. If not, Port File, i.e. "~/.diru.prt", is used. Client does
202
+ not work if no port info is given.
203
+
204
+ If Server is started in a directory where (or above) a file called
205
+ ".diru_root_dir" is found, then Project root is the directory
206
+ containing ".diru_root_dir". If ".diru_root_dir" is not found, but
207
+ ".diru.yml" is found, then Project root is the directory containing
208
+ ".diru.yml", and Options for Project are taken from that file. Last
209
+ resort for defining Project root, is DIRU_ROOT definition.
210
+
211
+ If ".diru_root_dir" or DIRU_ROOT is used to define Project root, then
212
+ DIRU_OPTS is used to define Options File. However, if not defined,
213
+ then "~/.diru.yml" is used for options.
214
+
215
+ An example Options file can be displayed with:
216
+
217
+ shell> diru -t
218
+
219
+
220
+ Available options in Options File:
221
+
222
+ * :sync - Options File and Project directory hierarchy polling period
223
+ (for Server).
224
+ * :hist - number of entries in history (max).
225
+ * :favs - tagged favorite dirs.
226
+ * :peers - <regexp>,<str> pairs for peer matching (String#sub method).
227
+
228
+
229
+ = Practical usage
230
+
231
+ After Hub has been started, user can start Server for each
232
+ Project. Each Server runs on its own Port, and user can either define
233
+ a Shell function for each Server with specific name, or define
234
+ environment variable (DIRU_PORT) and change it according to
235
+ Project. Depending on the usage pattern, user might also want to use
236
+ the Port File.
237
+
238
+ Each Project might benefit from specific Options File, and thus user
239
+ could mark the Project Root with ".diru.yml" and specify the Project
240
+ setup there.
241
+
242
+ If user is working with a programming project, it is fairly common to
243
+ have separate sub-directories for source code and build targets. Let's
244
+ assume user starts with editing source code and has a terminal for
245
+ accessing files. When program is ready for running, user opens a new
246
+ terminal for handling the compile-run-debug iterations.
247
+
248
+ User can copy source terminal directory to Scratch Pad. Then in the
249
+ new terminal, user can refer to Scratch Pad and change to same
250
+ directory as in source terminal. Finally user can change to a Peer of
251
+ current dir, i.e. where program running and debugging takes place.
252
+
253
+ If programming project is big enough, there might be several sub-dirs
254
+ where source files reside. User might setup favorites to Options File,
255
+ in order to easily reference the popular source code directories.
256
+
257
+ Options File can be edited while Server is running. Server reads the
258
+ Options File every 5 seconds (configurable with ":sync") and refreshes
259
+ its internal state. Server also updates its internal cache of Project
260
+ directory content at each refresh cycle.
@@ -0,0 +1,1010 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # diru is a tool for moving around in the users directory
4
+ # structure. diru provides input for the shell cd command for entering
5
+ # a new directory. However some commands do not produce input for cd.
6
+ #
7
+ # diru has hub (daemon) which creates new service (server) threads for
8
+ # diru clients. Before user can start using diru, a server should be
9
+ # created.
10
+ #
11
+ # diru has server side and client side. The server maintains
12
+ # information about users directories and also the state of the
13
+ # queries issued by the client. Client is what the end user uses.
14
+ #
15
+ # diru client command has to be integrated to the currently used
16
+ # shell in order to force the shell to change the current
17
+ # directory. Lets assume that the shell alias/function that user is
18
+ # actually using is "dr" for now. If diru returns success, then the
19
+ # diru output should be used as new directory. If diru status is
20
+ # failure, then directory should not be changed.
21
+ #
22
+ # Example of diru use:
23
+ # shell> dr / build src
24
+ #
25
+ # This command means that the client sends to the server a request for
26
+ # directory search. The search starts from Diru root ("/") and the
27
+ # rest of the path should include the words "build", and
28
+ # "src". If path with this spec is found, the shell directory gets
29
+ # updated.
30
+ #
31
+ # Perform:
32
+ # shell> dr @ doc
33
+ # or
34
+ # shell> dr i
35
+ #
36
+ # for some documentation.
37
+ #
38
+ # Diru setup:
39
+ #
40
+ # Start hub (deamon)
41
+ # * shell> diru --hub
42
+ #
43
+ # Open server for client.
44
+ # * shell> diru -s
45
+ #
46
+ # Use client
47
+ # * shell> diru -p 41115 -c r
48
+ #
49
+ #
50
+ # Feature list:
51
+ # * User root dir (downwards) search
52
+ # * Current directory (downwards) dir search
53
+ # * Bookmarking directories
54
+ # * Left-over search with multiple matches
55
+ # * Fast jump to specific common directories
56
+ # * Peer dir jump
57
+ # * Server state reset
58
+ # * Revert mode, unknown commands to cd
59
+ #
60
+
61
+
62
+ require 'como'
63
+ include Como
64
+ require 'drb'
65
+ require 'yaml'
66
+
67
+ # require 'byebug'
68
+
69
+
70
+ Spec.command( 'diru', 'Tero Isannainen', '2017',
71
+ [
72
+ [ :switch, 'hub', nil, "H: Start hub." ],
73
+ [ :opt_single, 'hport', nil, "H: Hub port (default: DIRU_HUB_PORT or 41114)." ],
74
+ [ :switch, 'hkill', nil, "H: Kill hub." ],
75
+ [ :switch, 'nodaemon', nil, "H: No daemon for server." ],
76
+ [ :switch, 'server', '-s', "S: Open server for client." ],
77
+ [ :opt_single, 'kill', '-k', "S: Close server for client." ],
78
+ [ :switch, 'list', '-l', "S: List servers." ],
79
+ [ :opt_single, 'options', '-o', "SC: Options File." ],
80
+ [ :opt_single, 'port', '-p', "SC: Server port for client (default: DIRU_PORT or ~/.diru.prt)." ],
81
+ [ :switch, 'change', '-c', "C: Change dir (i.e. target is to change dir)." ],
82
+ [ :exclusive, 'template', '-t', "C: Display Options File template." ],
83
+ [ :default, nil, nil, "C: Client commands (try: diru i)." ],
84
+ ] )
85
+
86
+
87
+
88
+ # ------------------------------------------------------------
89
+ # Common:
90
+ # ------------------------------------------------------------
91
+
92
+
93
+ # Diru common features.
94
+ module Diru
95
+
96
+ # Default port.
97
+ DIRU_HUB_PORT = ENV['DIRU_HUB_PORT'] || 41114
98
+
99
+ # Load yaml configuration file (if exists).
100
+ def Diru.load_conf( conf_file )
101
+ conf = {}
102
+ if File.exist?( conf_file )
103
+ conf = YAML.load( File.read( conf_file ) )
104
+ end
105
+ conf
106
+ end
107
+
108
+
109
+ # See: Diru.load_conf
110
+ def load_conf( conf_file )
111
+ @conf = Diru.load_conf( conf_file )
112
+ end
113
+
114
+
115
+ # Error message display.
116
+ def Diru.error( msg )
117
+ STDERR.puts "Diru Error: #{msg}"
118
+ exit( false )
119
+ end
120
+
121
+
122
+ # List the dir content (default: pwd).
123
+ def Diru.list( dir = '.', glob = '*' )
124
+ Dir.glob( "#{dir}/#{glob}" )
125
+ end
126
+
127
+ # List the dir files (default: pwd).
128
+ def Diru.list_files( dir = '.', glob = '*' )
129
+ Diru.list( dir, glob ).select do |i| File.file?(i) == true end
130
+ end
131
+
132
+ # Find file entry from directory hierarchy upwards.
133
+ def Diru.find_upper_file( file, dir = Dir.pwd )
134
+ found = Diru.list_files( dir, file )
135
+ if found.empty?
136
+ dir = File.dirname( dir )
137
+ if dir == "/"
138
+ raise RuntimeError, "Could not find file \"#{file}\"!"
139
+ else
140
+ return Diru.find_upper_file( file, dir )
141
+ end
142
+ else
143
+ return found[0]
144
+ end
145
+ end
146
+
147
+
148
+ # Find file entry from directory hierarchy upwards, exit if not found.
149
+ def Diru.must_find_upper_file( file, dir = Dir.pwd )
150
+ begin
151
+ Diru.find_upper_file( file, dir )
152
+ rescue RuntimeError
153
+ STDERR.puts "Could not find file \"#{file}\"!"
154
+ exit( false )
155
+ end
156
+ end
157
+
158
+ def Diru.get_opts_file( file = nil )
159
+ opts_file = nil
160
+ if Opt['options'].given
161
+ opts_file = Opt['options'].value
162
+ elsif file
163
+ opts_file = file
164
+ elsif ENV['DIRU_OPTS']
165
+ opts_file = ENV['DIRU_OPTS']
166
+ else
167
+ opts_file = "#{ENV['HOME']}/.diru.yml"
168
+ end
169
+
170
+ opts_file
171
+ end
172
+
173
+ end
174
+
175
+
176
+ # Diru Server State.
177
+ class Search
178
+
179
+ include Diru
180
+
181
+ def initialize( root, opts_file = nil )
182
+
183
+ # Server root.
184
+ @root = root
185
+
186
+ # Change directory to DIRU root.
187
+ Dir.chdir( @root )
188
+
189
+ # List of old matches.
190
+ @old = []
191
+
192
+ # Numbered bookmarks.
193
+ @book = []
194
+
195
+ # Numbered history.
196
+ @hist = []
197
+
198
+ # Favorites.
199
+ @fav = {}
200
+
201
+ # History limit.
202
+ @histlimit = 20
203
+
204
+ # Sync period.
205
+ @sync = 5
206
+
207
+ # Options File.
208
+ @opts_file = opts_file
209
+
210
+ # Temporary storage (scratch pad).
211
+ @scratch = nil
212
+
213
+ # Glob pattern for DB build.
214
+ @glob = "**/*"
215
+
216
+ @logging = false
217
+ # if Opt['log'].given
218
+ # @logging = true
219
+ # end
220
+
221
+ # Initial data update.
222
+ update_data
223
+ update_conf
224
+
225
+ # Lock for DB access.
226
+ @datalock = Mutex.new
227
+
228
+ # Start periodic DB updates.
229
+ periodic_update
230
+ end
231
+
232
+
233
+ def opts_file
234
+ @opts_file
235
+ end
236
+
237
+ # Return root.
238
+ def root
239
+ @root
240
+ end
241
+
242
+
243
+ # Add bookmark (if not already).
244
+ def abook( dir )
245
+ idx = @book.index dir
246
+ unless idx
247
+ @book.push dir
248
+ end
249
+ end
250
+
251
+
252
+ # Delete bookmark.
253
+ def dbook( idx )
254
+ @book.delete_at idx
255
+ end
256
+
257
+
258
+ # Get bookmark.
259
+ def gbook( idx )
260
+ @book[ idx ]
261
+ end
262
+
263
+
264
+ # Get all bookmarks.
265
+ def book
266
+ @book
267
+ end
268
+
269
+
270
+ # Reset bookmarks.
271
+ def rbook
272
+ @book = []
273
+ end
274
+
275
+
276
+ # Save bookmarks to file.
277
+ def savebook( file )
278
+ File.write( file, @book.join("\n") + "\n" )
279
+ end
280
+
281
+
282
+ # Load bookmarks from file.
283
+ def loadbook( file )
284
+ @book = File.read( file ).split("\n")
285
+ end
286
+
287
+
288
+ # Add history.
289
+ def ahist( dir )
290
+ if dir != @hist[0]
291
+ if @hist.length >= @histlimit
292
+ @hist = [ dir ] + @hist[ 0..(@histlimit-2) ]
293
+ else
294
+ @hist = [ dir ] + @hist
295
+ end
296
+ end
297
+ end
298
+
299
+
300
+ # Get history.
301
+ def ghist( idx )
302
+ @hist[ idx ]
303
+ end
304
+
305
+
306
+ # Get all historys.
307
+ def hist
308
+ @hist
309
+ end
310
+
311
+
312
+ # Reset history.
313
+ def rhist
314
+ @hist = []
315
+ end
316
+
317
+
318
+ # Get (and update) favorite.
319
+ def gfav( tag )
320
+ "#{@root}/#{@fav[ tag ]}"
321
+ end
322
+
323
+
324
+ # Return the whole fav.
325
+ def fav
326
+ @fav
327
+ end
328
+
329
+
330
+ # Update options.
331
+ def update_conf
332
+ if @opts_file
333
+
334
+ load_conf( @opts_file )
335
+
336
+ if @conf[:sync] && @conf[:sync] >= 1
337
+ @sync = @conf[:sync]
338
+ end
339
+
340
+ if @conf[:hist] && ( @conf[:hist] >= 2 )
341
+ @histlimit = @conf[:hist]
342
+ else
343
+ @histlimit = 20
344
+ end
345
+
346
+ if @conf[ :favs ]
347
+ @fav = @conf[ :favs ]
348
+ end
349
+
350
+ end
351
+ end
352
+
353
+
354
+ # Set scratch dir.
355
+ def settmp( dir )
356
+ @scratch = dir
357
+ end
358
+
359
+
360
+ # Get scratch dir.
361
+ def gettmp
362
+ @scratch
363
+ end
364
+
365
+
366
+ # Match dir/pattern from DB.
367
+ def match( dir, pattern )
368
+ list = []
369
+
370
+ if dir
371
+ rem = "#{@root}/"
372
+ begin
373
+ dir[rem] = ''
374
+ rescue
375
+ return []
376
+ end
377
+ r = dir + '.*' + pattern
378
+ else
379
+ r = pattern
380
+ end
381
+
382
+ @datalock.synchronize do
383
+ @data.each do |i|
384
+ if i.match( r )
385
+ list.push "#{@root}/#{i}"
386
+ end
387
+ end
388
+ end
389
+
390
+ # Update old list.
391
+ @old = list.rotate
392
+
393
+ list
394
+ end
395
+
396
+
397
+ # Get next match from old.
398
+ def next
399
+ ret = @old[0]
400
+ @old.rotate!
401
+ ret
402
+ end
403
+
404
+
405
+ # Update directory DB.
406
+ def update_data
407
+ @data = Dir.glob( @glob ).select{|ent| File.directory?( ent )}
408
+ @data = @data.sort
409
+ end
410
+
411
+
412
+ # Update DB with MT sync.
413
+ def update_data_sync
414
+ @datalock.synchronize do
415
+ update_data
416
+ update_conf
417
+ end
418
+ end
419
+
420
+
421
+ # Reset dynamic state.
422
+ def reset
423
+ log "reset"
424
+ @old = []
425
+ @book = []
426
+ @hist = []
427
+ update_data_sync
428
+ end
429
+
430
+
431
+ # Update DB periodically with background thread.
432
+ def periodic_update
433
+ # Spawn More data updates.
434
+ @th = Thread.new do
435
+ loop do
436
+ # puts "data update..."
437
+ sleep @sync
438
+ update_data_sync
439
+ end
440
+ end
441
+ end
442
+
443
+
444
+ # Enable logging.
445
+ def logon
446
+ @logging = true
447
+ end
448
+
449
+ # Disable logging.
450
+ def logoff
451
+ @logging = false
452
+ end
453
+
454
+
455
+ # Log events.
456
+ def log( msg )
457
+ if @logging
458
+ fh = File.open( "#{ENV['HOME']}/.diru.log", "a" )
459
+ fh.puts "#{Time.timestamp()}: #{msg}"
460
+ fh.close
461
+ end
462
+ end
463
+ end
464
+
465
+
466
+
467
+ # ------------------------------------------------------------
468
+ # Server program.
469
+ # ------------------------------------------------------------
470
+
471
+ # Diru hub.
472
+ class Hub
473
+
474
+ # Start in daemon or direct mode.
475
+ def Hub.start( port, daemon = true )
476
+ if daemon
477
+ p = fork do
478
+ Hub.new( port )
479
+ end
480
+ Process.detach( p )
481
+ else
482
+ Hub.new( port )
483
+ end
484
+ end
485
+
486
+
487
+ # Server collection.
488
+ @@servers = {}
489
+
490
+ attr_reader :port
491
+
492
+ def initialize( port )
493
+ @port = port
494
+
495
+ # Start the service
496
+ @hub = DRb.start_service( "druby://localhost:#{@port}", self )
497
+ DRb.thread.join
498
+ end
499
+
500
+
501
+ # Kill hub.
502
+ def kill
503
+ @th = Thread.new do
504
+ STDERR.puts "Diru Hub: Exiting sooooon..."
505
+ sleep 3
506
+ kill_servers
507
+ sleep 3
508
+ STDERR.puts "Diru Hub: Exit done..."
509
+ @hub.stop_service
510
+ exit( false )
511
+ end
512
+ end
513
+
514
+
515
+ # Create new server and return port for the server.
516
+ def get_server( root, opts_file )
517
+
518
+ 50.times do |i|
519
+ if @@servers[ @port+1+i ] == nil
520
+ port = @port+1+i
521
+ s = Service.new( root, port, opts_file )
522
+ @@servers[ port ] = s
523
+ return port
524
+ end
525
+ end
526
+
527
+ 0
528
+ end
529
+
530
+
531
+ # List all server ports.
532
+ def list_servers
533
+ @@servers.keys.map{ |i| [ i, @@servers[i].root ] }
534
+ end
535
+
536
+
537
+ # Kill all servers.
538
+ def kill_servers
539
+ @@servers.keys.each do |s|
540
+ kill_server( s )
541
+ end
542
+ @@servers = {}
543
+ end
544
+
545
+ # Kill server with the given port.
546
+ def kill_server( s_port )
547
+ s = @@servers[ s_port ]
548
+ if s
549
+ s.kill
550
+ @@servers.delete( s_port )
551
+ else
552
+ nil
553
+ end
554
+ end
555
+
556
+ end
557
+
558
+
559
+
560
+ # diru client service (server) thread.
561
+ class Service
562
+
563
+ attr_reader :root
564
+
565
+ # Initialize and start service.
566
+ def initialize( root, port, opts_file )
567
+ @root = root
568
+ @port = port
569
+ @drb = nil
570
+ @opts_file = opts_file
571
+ start
572
+ self
573
+ end
574
+
575
+ # Kill service.
576
+ def kill
577
+ @drb.stop_service
578
+ Thread.kill( @th )
579
+ end
580
+
581
+ # Start service.
582
+ def start
583
+ @th = Thread.new do
584
+
585
+ # Create "front" object.
586
+ @search = Search.new( @root, @opts_file )
587
+
588
+ # Start the service
589
+ @drb = DRb::DRbServer.new( "druby://localhost:#{@port}", @search )
590
+ @drb.thread.join
591
+ end
592
+ end
593
+
594
+ end
595
+
596
+
597
+ if Opt['template'].given
598
+ puts %q{---
599
+ :hist: 20
600
+ :sync: 10
601
+ :favs:
602
+ f: dir_0/dir_0_4/dir_0_4_0
603
+ g: dir_1/dir_1_2
604
+ :peers:
605
+ - - "(.*/dir_0/.*)/dir_0_2_0"
606
+ - "\\1"
607
+ - - "(.*/dir_0/.*)/dir_0_1_0"
608
+ - "\\1/dir_0_1_1"
609
+ }
610
+ exit( false )
611
+ end
612
+
613
+
614
+ hport = nil
615
+ if Opt['hport'].given
616
+ hport = Opt['hport'].value.to_i
617
+ else
618
+ hport = Diru::DIRU_HUB_PORT
619
+ end
620
+
621
+
622
+ if Opt['hub'].given
623
+
624
+ # Hub:
625
+ h = Hub.start( hport, !Opt['nodaemon'].given )
626
+ exit( true )
627
+ end
628
+
629
+
630
+ if false
631
+ # For hub maintenance (irb):
632
+ require 'drb'
633
+ hub = DRbObject.new( nil, "druby://localhost:41114" )
634
+ hub.list_servers
635
+ end
636
+
637
+
638
+ if false
639
+ # Test client
640
+ require 'drb'
641
+ @port = 41115
642
+ @search = DRbObject.new( nil, "druby://localhost:#{@port}" )
643
+ end
644
+
645
+
646
+ if Opt['hkill'].given
647
+ hub = DRbObject.new( nil, "druby://localhost:#{hport}" )
648
+ hub.kill
649
+ exit( true )
650
+ end
651
+
652
+
653
+ if Opt['server'].given
654
+
655
+ # Setup client server.
656
+ hub = DRbObject.new( nil, "druby://localhost:#{hport}" )
657
+
658
+ root = nil
659
+ opts_file = nil
660
+
661
+ if ENV['DIRU_ROOT']
662
+ root = ENV['DIRU_ROOT']
663
+ else
664
+ # First search for .diru_root_dir file.
665
+ begin
666
+ root = File.dirname( Diru.find_upper_file( '.diru_root_dir' ) )
667
+ rescue
668
+ root = nil
669
+ end
670
+
671
+ unless root
672
+ # Next search for .diru.yml file.
673
+ begin
674
+ yml_file = Diru.find_upper_file( '.diru.yml' )
675
+ root = File.dirname( yml_file )
676
+ opts_file = yml_file
677
+ rescue
678
+ Diru.error "Could not find user directory root!"
679
+ end
680
+ end
681
+ end
682
+
683
+ opts_file = Diru.get_opts_file( opts_file )
684
+
685
+ begin
686
+ s_port = hub.get_server( root, opts_file )
687
+ rescue
688
+ Diru.error "Access to Hub failed!"
689
+ end
690
+
691
+ if s_port == 0
692
+ Diru.error "Could not start server!"
693
+ else
694
+ # File.write( port_file, s_port.to_s ) if port_file
695
+ puts "Using server port: #{s_port}..."
696
+ end
697
+
698
+ exit( true )
699
+ end
700
+
701
+
702
+ if Opt['kill'].given
703
+
704
+ hub = DRbObject.new( nil, "druby://localhost:#{hport}" )
705
+ port = Opt['kill'].value.to_i
706
+ begin
707
+ hub.kill_server port
708
+ rescue
709
+ Diru.error "Access to Hub failed!"
710
+ end
711
+
712
+ exit( true )
713
+ end
714
+
715
+
716
+ if Opt['list'].given
717
+
718
+ hub = DRbObject.new( nil, "druby://localhost:#{hport}" )
719
+ begin
720
+ hub.list_servers.each do |i|
721
+ puts format( "%-12.d %s", i[0], i[1] )
722
+ end
723
+ rescue
724
+ Diru.error "Access to Hub failed!"
725
+ end
726
+
727
+ exit( true )
728
+ end
729
+
730
+
731
+
732
+ # ------------------------------------------------------------
733
+ # Client program.
734
+ # ------------------------------------------------------------
735
+
736
+
737
+ class Client
738
+
739
+ include Diru
740
+
741
+ def initialize( port )
742
+
743
+ @port = port
744
+
745
+ # Create a DRbObject instance that is connected to server. All methods
746
+ # executed on this object will be executed to the remote one.
747
+ @search = DRbObject.new( nil, "druby://localhost:#{@port}" )
748
+
749
+ load_conf( @search.opts_file )
750
+ end
751
+
752
+
753
+ # Perform cd (i.e. return true) and store current dir to history.
754
+ def do_cd
755
+ @search.ahist( Dir.pwd )
756
+ true
757
+ end
758
+
759
+
760
+ # No directory change, i.e. return false.
761
+ def no_cd
762
+ false
763
+ end
764
+
765
+
766
+ # Process command.
767
+ def command( input )
768
+
769
+ if input.empty?
770
+
771
+ # Refer to last search list.
772
+ puts @search.next
773
+ do_cd
774
+
775
+ else
776
+
777
+ cmd = input[0]
778
+ arg = input[1..-1]
779
+
780
+ # Shell specials chars to prevent:
781
+ # * ? [ ] ' " \ $ ; & ( ) | ^ < >
782
+
783
+ # Thus allowed:
784
+ # ! % + , - . / : = @ _ ~
785
+ # ^ ^ ^ ^ ^ ^ ^ ^ ^
786
+
787
+ # Decode user command.
788
+ ret = \
789
+ case cmd
790
+
791
+ when '/', 'r';
792
+ if arg.empty?
793
+ disp @search.root
794
+ else
795
+ disp @search.match( nil, arg.join('.*') )
796
+ end
797
+
798
+ when ':', 't'; disp @search.match( Dir.pwd, arg.join('.*') )
799
+
800
+ when '.', 'b';
801
+ if arg[0] == nil
802
+ lbook( @search.book )
803
+ no_cd
804
+ elsif arg[0] == '.'
805
+ @search.abook( Dir.pwd )
806
+ no_cd
807
+ elsif arg[0] == '!'
808
+ @search.rbook
809
+ no_cd
810
+ elsif arg[0] == 's'
811
+ @search.savebook( arg[1] )
812
+ no_cd
813
+ elsif arg[0] == 'l'
814
+ @search.loadbook( arg[1] )
815
+ no_cd
816
+ elsif arg[0] == 'd'
817
+ @search.dbook( arg[1].to_i )
818
+ no_cd
819
+ else
820
+ disp @search.gbook( arg[0].to_i )
821
+ end
822
+
823
+ when ',', 'h';
824
+ if arg[0] == nil
825
+ lbook( @search.hist )
826
+ no_cd
827
+ elsif arg[0] == '.'
828
+ @search.ahist( Dir.pwd )
829
+ no_cd
830
+ elsif arg[0] == '!'
831
+ @search.rhist
832
+ no_cd
833
+ elsif arg[0] == ','
834
+ disp @search.ghist( 0 )
835
+ else
836
+ disp @search.ghist( arg[0].to_i )
837
+ end
838
+
839
+ when '_', 's';
840
+ if arg[0] == nil
841
+ disp @search.gettmp
842
+ elsif arg[0]
843
+ if arg[0] == '.'
844
+ dir = Dir.pwd
845
+ else
846
+ dir = arg[0]
847
+ end
848
+ disp @search.settmp( dir )
849
+ no_cd
850
+ else
851
+ no_cd
852
+ end
853
+
854
+ when '=', 'p'; disp peer
855
+
856
+ when '@', 'c'; rpc( arg )
857
+
858
+ when 'f';
859
+ if arg[0] == nil
860
+ @search.fav.each do |k,v|
861
+ STDERR.puts format( "%-6s %s", k, v )
862
+ end
863
+ no_cd
864
+ else
865
+ fav_map( arg )
866
+ end
867
+
868
+ when 'i';
869
+ rpc( [ 'doc' ] )
870
+
871
+ else
872
+
873
+ if Opt['change'].given
874
+ all = [cmd] + arg
875
+ puts "#{all.join(' ')}"
876
+ do_cd
877
+ else
878
+ no_cd
879
+ end
880
+
881
+ end
882
+
883
+ exit( ret )
884
+ end
885
+
886
+ end
887
+
888
+
889
+ # Display directories. One to STDOUT and others to STDERR.
890
+ def disp( resp )
891
+ if resp == nil
892
+ no_cd
893
+ elsif resp.kind_of? Array
894
+ puts resp[0]
895
+ if resp.length > 1
896
+ resp[1..-1].each do |i|
897
+ STDERR.puts i
898
+ end
899
+ end
900
+ do_cd
901
+ else
902
+ puts resp
903
+ do_cd
904
+ end
905
+ end
906
+
907
+
908
+ # Get peer directory. Search list of re-pairs. Match either of the
909
+ # pair, and switch to the pair dir.
910
+ #
911
+ def peer
912
+ cur = Dir.pwd
913
+ peers = @conf[ :peers ]
914
+ peers.each do |pair|
915
+ re = Regexp.new( pair[0] )
916
+ if ( re.match( cur ) )
917
+ return cur.sub( re, pair[1] )
918
+ end
919
+ end
920
+ nil
921
+ end
922
+
923
+
924
+ # Display short command doc.
925
+ def doc
926
+ STDERR.puts "
927
+ r / - search from root (or to root)
928
+ t : - search from this (current)
929
+ b . - bookmark access
930
+ h , - history access
931
+ s _ - scratch pad access
932
+ p = - peer of current
933
+ c @ - command (reset, doc etc.)
934
+ f - favorites
935
+ i - short info
936
+ "
937
+ end
938
+
939
+
940
+ # List bookmarks with index number (in reverse order).
941
+ def lbook( book )
942
+ idx = book.length-1
943
+ (0..idx).to_a.reverse.each do |i|
944
+ STDERR.puts format( "%2d: %s", i, book[i] )
945
+ end
946
+ end
947
+
948
+
949
+ # Remove Procedure Call towards server.
950
+ #
951
+ # Example:
952
+ # shell> dr @ rbook
953
+ #
954
+ def rpc( arg )
955
+ case arg[0]
956
+ when 'reset'; @search.reset
957
+ when 'abook'; @search.abook( Dir.pwd )
958
+ when 'lbook'; lbook( @search.book )
959
+ when 'rbook'; @search.rbook
960
+ when 'doc'; doc
961
+ else return no_cd
962
+ end
963
+ no_cd
964
+ end
965
+
966
+
967
+ # Direct directory jump.
968
+ def fav_map( arg )
969
+ ret = @search.gfav( arg[0] )
970
+ if ret
971
+ disp ret
972
+ do_cd
973
+ else
974
+ no_cd
975
+ end
976
+ end
977
+
978
+ end
979
+
980
+
981
+ if Opt['port'].given
982
+ port = Opt['port'].value.to_i
983
+ elsif ENV['DIRU_PORT']
984
+ port = ENV['DIRU_PORT'].to_i
985
+ elsif File.exist?( "#{ENV['HOME']}/.diru.prt" )
986
+ port = File.read( "#{ENV['HOME']}/.diru.prt" ).to_i
987
+ else
988
+ Diru.error "Server port info missing..."
989
+ end
990
+
991
+
992
+ # User command content.
993
+ input = Opt[nil].value
994
+
995
+ begin
996
+ client = Client.new( port )
997
+ rescue
998
+ Diru.error "Server not available!"
999
+ exit( false )
1000
+ end
1001
+
1002
+ ret = false
1003
+ begin
1004
+ ret = client.command( input )
1005
+ rescue
1006
+ Diru.error "Command failure!"
1007
+ exit( false )
1008
+ end
1009
+
1010
+ exit( ret )