webdesserts / lazy_files

A Library to make File-Handling in Ruby simple(r).
MIT License
0 stars 0 forks source link

better mode decision with Lazy::File ::open and ::on_io #12

Closed webdesserts closed 11 years ago

webdesserts commented 12 years ago

So apparently file access modes aren't as simple as "read" and "write" and the current setup doesn't support stuff like 'a+' well nor allow users to specify these modes easily. Fix that.

webdesserts commented 12 years ago

Idea: `file.mode('a+').puts

This might also need a method like mode_ok?, that you could make do mode_ok?(['r','w+','r+']) and possibly shorten to mode_ok?(:readable)

webdesserts commented 12 years ago

You could also hide as much of this as possible, although this has the danger of turning the library into another jQuery. ex:

# instead of
file.mode('a+').puts "hello world"

# you could have a method like
file.append "hello world"

# and methods like ...
file.open('w')

# would just be
file.open

# and all methods afterwards would dynamically change the file mode

This runs the risk of causing confusion when a default mode conflicts with the current mode of the file. Do you raise an error or do you switch modes and switch back? If you switch what's the point of modes anymore? Are we just going to make everything 'a+'?

Also I'm not totally confident in my understanding of modes still. Other than denying access to reading or writing a file, the only other difference between modes is the position of the cursor as far as I understand. Would having a file.jump_to_end.puts 'hello world' be a better option here? Can I even do that? XD

Some research is needed. brb.

webdesserts commented 12 years ago

I think this is a good opportunity to define the purpose of this library:

1) Simplify file handling so its not so harsh on new rubyists/programmers 2) Unify the API between File, Dir, and common Linux commands 3) Empower the user with tools that make common file handling tasks easy and readable.

And I think we should define some things we want to avoid:

1) A reduction in functionality* 2) A reduction in performance 3) Hiding common file handling concepts**

* This is obviously not going to be achievable out of the box, but should be sought after as the library matures ** Things like File#close should NOT be hidden. At least not in the documentation. The goal shouldn't be to hide these type of standards, but instead to clarify them and their purpose.

webdesserts commented 11 years ago

Current thoughts:

You should be able to specify a mode on File creation...

Lazy.file('hello.txt', 'a+')

...or with File#open

Lazy.file('hello.txt').open('w+')

If no mode is specified, the next method called, should interpret the desired mode...

Lazy.file('hello.txt').puts('hello world') # would open in 'a'
# while...
Lazy.file('hello.txt').readline #would open in 'r'

Since modes could possibly be completely hidden to the user. Mode checkers are a definite must.

f.mode #=> 'w'
f.mode?('r') #=> false
f.is_r? #=> false
f.is_w? #=> true

This is going to require some decisions on what to do with modes like 'a+' and 'w+' vs. 'r+'. Also you might have noticed that I decided that File#puts starts in 'a' mode rather than 'w'. This is because I'm assuming most people don't want to overwrite a file when they're using a 'puts' command. This could very well be argued against and might change.

Next post.... thoughts on each individual modes.

webdesserts commented 11 years ago

The following are the list of definitions for modes as listed on IO's docs page with comments on how they might be approached.

Note LazyFiles will be suppressing file creation and leaving that up to the mkfile method.

Modes

read-only

r - starts at beginning of file (default mode).

write-only

w - truncates existing file to zero length or creates a new file for writing. a - starts at end of file if file exists, otherwise creates a new file for writing.

read-write

r+ - starts at beginning of file. w+ - truncates existing file to zero length or creates a new file for reading and writing. a+ - starts at end of file if file exists, otherwise creates a new file for reading and writing.

extra

b - Binary file mode (may appear with any of the key letters listed above). Suppresses EOL <-> CRLF conversion on Windows. And sets external encoding to ASCII-8BIT unless explicitly specified. t - Text file mode (may appear with any of the key letters listed above except "b").

Observations

How to Handle Extras

First off as far as I can tell the extra modes are there specifically to deal with differences in Windows. I'm going to try not to mess with them and just leave them to be handled by #open normally.

Modes Should be Immutable Until Closed

Just like in a normal File object. I want errors to be thrown if someone tries to write a read stream.

f = Lazy.file('hello_world')
f.read #=> 'hello world'
f.append 'and Saturn too!' #=> should throw an Error

I do think that I should make it easy to switch modes though. How about a more useful reopen method?

f = Lazy.file('hello_world')
msg = f.read
if msg == 'goodbye world!'
  f.reopen('w+').print 'hello world!'
end

Using File's default Errors should handle this without the need of a mode_ok? method.

w vs. a

One of the main problems is going to be deciding when to use write vs. append. Write can be very dangerous when invisible because it completely overwrites a file. Anyone who has worked with IOs before would be familiar with this, but newguns might blow something up if they aren't prepared.

For example: on the issue of what to do with #puts & #print ...

# with a hello world file...
Lazy.file('hello_world.txt').read #=> "hello world"
f = Lazy.file('hello_world.txt')

# Someone familiar to IOs might expect #print to enter write mode
f.print "hello mars" # would read "hello mars"

# While someone new to IOs might expect it to append
f.print "and mars too!" # would read "and mars too!" NOT "hello world and mars too!"

I think it very important that LazyFiles translates easily over to the normal behaviour of IOs if a newgun learns with the framework first. That's why I think if a user wants to append, then they need to declare it.

f = Lazy.file('hello_world.txt', 'a')
f.print 'and mars too!'

But I also still believe in convenience methods

f = Lazy.file('hello_world.txt')
f.append 'and mars too!'

This would at least get them used to the terminology even if they never touch the modes.

On the Topic of Read-Writes

I think any and all defaults should be either r,w, or a in the case of #append. I don't want to open an I or O that the user doesn't need. I cringe at wasted resources. There is one possible exception to this though. Mikeal has a nice file-handling library written for Node.js called filed. In filed's documentation he gives the added feature of having #pipe auto detect as a rw if the object is piping to itself. If I implement a #pipe method. This shouldn't be too hard to work into the method.

webdesserts commented 11 years ago

continued...

More on Appending

It should be noted that there are TONS of print methods (p,print,puts,putc,printf, etc.. ). This makes adding more print methods feel like overkill. So if we use #append as mentioned above it has the positive notion of reinforcing the a mode. However, it will also have the negative notion of breaking convention which might also confuse the purpose of the method.

Example: is #append a puts or a print? Well we could make an #append for print and an #appendln for puts, but then that's two more method names attached to the ever expanding list of print methods.

Another option would be to copy the convention of tacking on an abbreviation to represent a small change in functionality(e.g puts vs. putc and print vs. printf). Most naturally that would be an a to represent the mode:

f.aputs
f.aprint
f.aputc

Obviously unless this is clarified in the documentation, the meaning of these methods might be obscure.

Mobility

I currently still lack a lot of knowledge in this area, but I think this is something that is going to be important to look into for the future.

One of the things that I see lacking in the current set of functions is mobility as far as pointer movement. For example another solution to the previous issue would be a #jump_to method.

f = Lazy.file('hello_world.txt', 'rw')
f.jump_to(:eof).puts('goodbye world!') # jump to end of file
f.jump_to(:bof).puts('hello world!') # jump to beginning of file

This doesn't have to be limited to major file jumps. This could provide a Vim feel as well.

f.jump_to :eow # end of word
f.jump_to :bow # beginning of word
f.jump_to :eol # end of line
f.jump_to :bol # beginning of line

Of course a decision would have to be made in the case of what to do if a #jump_to was used on an unopened file. This might need some further research into pointer movement.

webdesserts commented 11 years ago

Many of the core changes discussed here were fixed with 30e3480. Everything else was moved to its own issue.