It appears that Confreaks has posted the video of my ‘Recovering from Enterprise’ talk from RubyConf 2008. As usual, Confreaks did a great job recording the talks at RubyConf.
For those who missed it, I also posted an article version of my presentation, titled Legos, Play-doh, and Programming.
So, it’s been over a month and a half since I switched back to Vim, and I figured I’d post a bit about how things are going.
I love it. Though the future is notoriously difficult to foretell, I think it’s safe to say that I won’t be switching editors again anytime soon. Vim is where it’s at, for me.
Here’s the combination of plugins and such that I’ve found work best for me.
MacVim FTWAny Vim will do, really, but if you’re on a Mac, you really ought to consider MacVim. Like any self-respecting Vim port, it does the command-line really well, but it also has a great OS X GUI interface.
SettingsI’ve got my <Leader> character (:h mapleader) mapped to the comma (since it’s easier to reach than the backspace character).
| let mapleader = "," |
I’ve installed ack and have configured Vim to use ack for grep.
| 1 2 | set grepprg=ack set grepformat=%f:%l:%m |
I’m a believer in spaces instead of tabs (let’s just agree not to go there), and I prefer a tab size of 2. I also like editors to try and guess the indentation level.
| 1 2 3 4 5 6 | set tabstop=2 set smarttab set shiftwidth=2 set autoindent set expandtab set backspace=start,indent |
But, some file types really do require explicit tabs, and not spaces:
| 1 2 | autocmd FileType make set noexpandtab autocmd FileType python set noexpandtab |
(Update: apparently, python can do tabs or spaces. All my python vimming for the last 8 years has been read-only, so it never actually came up.)
I like my lines numbered, syntax highlighting on, and highlighted searches:
| 1 2 3 | set number set hlsearch syntax on |
I’ve got quite a few other things tweaked in my .vimrc, but those are the major biggest ones.
Plugins FuzzyFinderThe FuzzyFinder and FuzzyFinder TextMate plugins have become essential for me. For now, installation of the TextMate-like behavior is kind of painful, but I plan to get something up on the Vim scripts index in the nearish future.
My settings for FuzzyFinder TextMate:
| 1 2 3 4 5 | let g:fuzzy_ignore = "*.log" let g:fuzzy_matching_limit = 70 map <leader>t :FuzzyFinderTextMate<CR> map <leader>b :FuzzyFinderBuffer<CR> |
Thanks to all who recommended the NERD tree plugin by Marty Grenfell. It really is fantastic, definitely the best-of-breed of VIM project/directory explorers. I especially like that it is easily toggled away. I usually keep it hidden, and toggle it open only when I need to browse to something. (Thanks to the fuzzyfinder stuff, my need for a project browser is pretty small, but when I need one, NERD_tree works great.)
| 1 | map <leader>d :execute 'NERDTreeToggle ' . getcwd()<CR> |
The rails.vim plugin is pretty extensive, and I’m currently only scratching the surface. I don’t use snippets at all (never did in TextMate, either), but the Rake integration is pretty handy, and I’ve used the migration generator pretty often lately.
I need to spend some more time reading the docs for this one, and practicing some of the commands, since I’m sure it could turn into a real time-saver for programmers (like myself) who spend a good part of their day in Rails code.
scratch.vimI’ve really come to love scratch.vim. Sometimes I just need to jot down some numbers, or paste the result of some query, or even take notes on a phone call. Writing any of that on a scrap of paper is a sure way to lose the info. The scratch plugin lets me take notes right where I am all day: in Vim. It doesn’t come with a way to toggle the scratch buffer (which is odd), so I wrote a quick one:
| 1 2 3 4 5 6 7 8 9 | function! ToggleScratch() if expand('%') == g:ScratchBufferName quit else Sscratch endif endfunction map <leader>s :call ToggleScratch()<CR> |
Maybe it’s just my own coding style, but I find myself commenting and uncommenting large blocks of code on a daily basis. The tComment plugin from Tom Link is perfect for this. You can toggle comments by using the Vim motion operators, or just do a quick visual block followed by “gc”. Good stuff!
OthersThere are a few other plugins I’m experimenting with (surround.vim, vcscommand.vim), but which I use infrequently enough that they haven’t become muscle memory yet.
WorkflowBecause I prefer the GUI version of MacVim, I typically have only one or two terminal windows open. I then type “mvim” to open vim from the root of whichever project(s) I’m working on, and then use ”,t” (my FuzzyFinder TextMate trigger) to search for and open files. I also use ”,b” (my FuzzyFinderBuffer trigger) to search my buffer list if I know the file is already open.
Switching between buffers with the carat character ”^” is a huge time saver. I used to use :ls (to list open buffers) and :buffer (to jump to a buffer), but the FuzzyFinderBuffer has really taken the place of both of those.
Ironically, split windows (which I missed most of all when I switched to TextMate) have taken me the longest to fit back into my workflow. TextMate trained them out of me. :) That said, I’m still trying to fit them back in, and when I remember to use them, I love them.
You?So, that’s me. How does Vim fit in your own workflow? What settings do you prefer, and why? And what customizations are you using in your own setup?
This article is based on a talk I gave at the 2008 RubyConf in Orlando, Florida, entitled “Recovering from Enterprise: how to embrace Ruby’s idioms and say goodbye to bad habits”.
The other day I went to Target with my son. Like most kids, I think, he’s convinced that Target is a toy store, which just happens to sell towels and shoes and cleaning supplies, too, so in his eyes it’d be criminal to not walk through the bare handful of toy aisles.
Besides, the toy section is across from the electronics section, which all geeks know is where the real toys are.
So, we went to the toy section and started browsing. I’ve always loved LEGO sets, and it’s a good thing they’re so expensive or I’d come home with a new box of bricks every time. At the Target near our home, they have half of an entire aisle devoted to boxes and boxes of LEGO sets. Need a battle-axe-wielding LEGO dwarf figure? A LEGO shark? How about a giant LEGO skull, a la Indiana Jones? And who could pass a LEGO Star Wars’ Star Destroyer model without a wistful thought or two?
It struck me at that time, though, how incredibly specific so many of these pieces are. With all of those sets in your possession, you could build a secret agent headquarters with a boulder trap that crushes angry battle-axe-wielding dwarves as they drive by in Martian exploration buggies. Which themelves are adorned with flower beds and creeper vines. And you could do all that in under 10 LEGO bricks! (Or, maybe a few more than that.)
Did you know that LEGO currently produces over 900 distinct LEGO pieces, or “elements” as they call them? Over the course of their history, there have been almost 13,000 distinct elements created. Now, that number includes variations in color and material, but even if you exclude those permutations, you’re still left with a staggering 2,800 different elements in the LEGO line.
It’s interesting that LEGO tends to encourage the use of specific pieces, rather than letting you build those pieces from more fundamental parts. It means that in order to master LEGO brick building, you have to know all of the pieces available to you, and have a good intuitive feel for how and when they should be used. That’s…a lot of information to keep tabs on. Myself, I just keep to the standard rectangular blocks and plug an exotic or two on as an afterthought when I see one that looks cool.
Also, if you’ve built up a model, and decide later that you want to change or extend some part of the model, you’ll often have to dismantle part (or all!) of it in order to do so. Kind of a pain.
Regardless, I still love building with LEGO bricks, and I suspect I always will.
Play-DohNow, my son being all of 6 years old, his attention span requires us to spend no more than a few minutes in any one toy aisle. So, long before I was ready to tear my eyes away from the LEGO sets, we found ourselves in the next aisle over. This was a much more colorful aisle, with bright pastels coloring various pre-school toys. My son, though, has nothing against pre-school toys, and was more than willing to drag me through them.
My eyes caught on the Play-Doh section.
The Play-Doh section at this Target is small, maybe 8 different hangers and a few square feet of shelf-space. You can get Play-Doh in as many as 50 different colors, but regardless of color, it’s all still the same thing: a bucket of malleable dough that you can pound, press, pinch, roll, and sculpt. (And rub into the carpet. And hair. And clothes. But we won’t get into that.)
Honestly, Play-Doh has a bad rap as a pre-schooler toy. It’s remarkably fun to play with. You can do all kinds of things with Play-Doh that you just can’t do with LEGO bricks. For example, the other day I built an arch out of cubes of Play-Doh that were held together only by friction. (You may not be impressed, but my 6-year-old was.)
The best part is that it doesn’t require so much memorization to become proficient in Play-Doh, though it might require more of an artistic streak than LEGO bricks do. Since I’m more engineer than artist, my Play-Doh creations tend to come out blocky and functional, rather than elegant and designed, but then, so do my LEGO creations.
Also, where LEGO models require significant work to alter or extend, Play-Doh models are dead-simple. If you want to add something to the base of your model, just graft more Play-Doh onto it. Want to change the shape of the keystone of your arch? Just pinch and mold in place. Simple!
Interestingly, I’ve found that while you can’t build with LEGO bricks using Play-Doh construction techniques, you can build with Play-Doh using LEGO construction techniques. Just build bricks out of Play-Doh. It’s unwiedly and impractical, but it can be done. The real question is: why would you want to? It’s pretty obvious that to build with Play-Doh, you should just embrace Play-Doh’s own strengths and run with it.
As obvious as that may seem, the lesson didn’t click for me for a long time. It’s not that I went about building Indiana Jones sets out of Play-Doh, one brick at a time. Rather, I didn’t realize that the same lesson applied to programming languages.
Java and LEGOsConsider Java. Most would consider it the poster child of “enterprise” environments (though .NET is giving it a run for its money). And would you believe, Java and LEGO bricks have several things in common?
As of Java 1.6, there are well over 11,000 different classes and interfaces available to programmers in the standard library. (That’s not even counting the inner and anonymous classes that are usually not publicly documented.) Eleven. Thousand. Classes.
This is readily apparent when you consider the set of collection implementations that Java ships with.
Collection Interfaces:Yes, that is FORTY-SIX different interfaces and implementations related to collections. Now, just like LEGO construction, having this volume of distinct elements on hand affects how you architect things. Writing software becomes more of a smorgasbord, where you pick and choose the specialized bricks you need, fitting them together just so. It also means that, in order to master Java, you need to have that intuitive grasp of how and when to use those thousands of classes. When do you use a HashSet versus a TreeSet? When would you use an ArrayDeque, and when would you want to subclass an AbstractQueue? It’s all part of the job.
Also, IDE’s are popular with Java in part because of the pain of refactoring. If you want to extend or modify a Java application, it can involve (like LEGO models) a lot of dismantling and reassembling.
Ruby as Play-DohBut if Java is the LEGO of programming languages, then it could be argued that Ruby is the Play-Doh. Just as Play-Doh has been typically considered a pre-school toy, so Ruby has had a bad rap as a “toy” language, not fit for the “real world”. Also, compared to Java’s library of 11,000 classes, Ruby’s meager 1,400 classes (which number does include internal and anonymous ones, but not modules) seems paltry. And collections! Look what Ruby has to offer:
Modules: * Enumerable * Comparable (for elements within a collection) Classes: * Hash * Array * Set * SortedSetJust 6 options, to Java’s 46. What if you need a queue? Well, Ruby’s Array class has a queue-like interface; you could just use that. What about a sorted map? In that case, you might need to make do with a sorted set, or you could write your own, but it’s not hard. Most data structures are not rocket science, and for those that are, you can bet someone else has implemented it already.
But when you need to extend or modify your application, Ruby is a dream. Like Play-Doh, you can often just “pinch and mold” in place, grafting new code on or pulling old code out.
Ruby’s philosophy is like that of Play-Doh’s: provide a basic set of tools and make it relatively simple to build something complex with them. The very Ruby language itself is designed for this: closures, super-simple introspection of objects, runtime modification of existing objects, and the use of modules for extending classes and objects all tend to result in an environment that is simple, malleable, and extensible.
And just as you cannot use Play-Doh construction techniques with LEGO bricks, you also really cannot use Ruby programming techniques with Java. Using closures for delayed execution, or iteration, is tricky (at best) in Java, when it’s possible at all. Extending objects at runtime typically requires bytecode modification. And Ruby’s use of modules to extend classes and objects, while similar to both inheritance and interfaces, is slightly different (and arguably more powerful) than either.
You can write Ruby programs using Java programming techniques, but just as using LEGO techniques with Play-Doh is unwieldy and overcomplicated, so is mimicking Java in Ruby.
This is the lesson that I was slow to learn.
CoplandConsider exhibit A, from my Copland library.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # copland/configuration/loader.rb (collapsed) module Copland module Configuration class Loader attr_reader :search_paths attr_reader :loaders def initialize( search_paths=[] ) def add_search_path( *paths ) def add_loader( loader ) def load( options={} ) def load_path( path, options ) def use_library( name ) end end end |
Copland was my first stab at a dependency injection (DI) framework, and is more-or-less a feature-for-feature port of the HiveMind project in Java. (Ironically, it was the subject of my first presentation at a Ruby conference, in 2004!)
It was designed to automatically scan directories in the load path for YAML configuration files (I’ll mention those shortly), and load them up and parse them. The thing is, I imagined a case where someone might want to use XML instead of YAML. I couldn’t just leave these folks behind! So I made the whole configuration loading framework extendible. Want XML config files? Fine! Just implement an XML parser system and register it with the configuration loader framework, and you’re good to go!
That’s just wrong on so many levels. Always, always, always build just what you need, and only when you need it. You’re in Ruby, the Play-Doh of programming languages, and the cost of adding features later is really, really low. Remember YAGNI! Obviously, this principle holds in Java, too, but it really seems like the opposite philosophy has become the standard among many Java projects. It’s too bad, because it has contributed to a bad reputation that Java probably doesn’t entirely deserve.
Here’s a classic Java pattern that just really doesn’t translate to Ruby:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # copland/class-factory.rb module Copland class ClassFactory include Singleton def initialize @pools, @constructors = Hash.new, Hash.new end def create_pool( name, &block ) block ||= proc { |k,*args| k.new( *args ) } @pools[ name ] = Hash.new @constructors[ name ] = block end def get_pool( name ) pool = @pools[ name ] or raise NoSuchPoolException, name return pool end def register( pool_name, name, klass ) pool = get_pool( pool_name ) pool[ name ] = klass end def get( pool_name, name, *args ) pool = get_pool( pool_name ) klass = pool[ name ] raise NoSuchRegisteredClassException, "#{pool_name}:#{name}" unless klass constructor = @constructors[ pool_name ] return constructor.call( klass, *args ) end end end |
This is an implementation of a class factory. In Ruby. The HiveMind project had a class factory, so the Copland project needed one, too!
But you know, class factories are absolutely pointless in Ruby. There are plenty of reasons for these in Java, but they just aren’t necessary in Ruby. Want a namespace? Declare the class in a module. Want the class to exist in multiple namespaces? Use constant assignment within whatever modules you desire. Need a dynamic lookup? Try #const_get. In the very worst case, just use a Hash if you need to map arbitrary strings to classes.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | module A class B end end # method #1, use const_get to dynamically look up classes name = "B" klass = A.const_get(name) object = klass.new # method #2, use a hash to map arbitrary strings to classes map = { "bimpl" => A::B } map["bimpl"].new |
Seriously. You don’t need explicit class factories in Ruby, because anything can be a class factory, implicitly.
I’ll mention one more painful Javaism that I ported to Copland. It’s so painful that I won’t even bother pasting it here—if you’re following along, look at examples/solitaire-cipher/lib/package.yml in the copland distribution.
If you do, what you’ll see are 106 lines of YAML describing how different Ruby objects in a simple, 250-line program should be initialized and connected. Yes. 106 lines of YAML. For 250 lines of Ruby.
Now, don’t get me wrong. YAML can be great for configuration. Rails, for instance, uses it for database connection information. The problem here, in Copland, was that I was using a static configuration for what would be better served with a block of code. Ruby reads elegantly; a YAML configuration file does not.
Fortunately, those wiser than myself showed me the way.
RubyConf 2004I still remember Rich Kilmer, sitting in the front row in the October 2004 RubyConf. As I wrapped up my presentation on Copland and dependency injection, I asked if there were any questions.
Rich raised his hand. “Why didn’t you just use Ruby?”
I was confused by his question, and he had to explain. Why did I use YAML instead of just doing the configuration in Ruby code?
I think I mumbled something like “that would be a neat idea”. To me, it was a novel concept. I’d never heard of it before. You’d never see a Java program that was configured by writing Java code. That screams “hard coding”! But Ruby, you see, is different.
Ruby lets you write these beautiful little mini-languages. You’ll hear them called “Domain Specific Languages”, or DSL’s. They are subsets of the Ruby language, and you’ll find them in Rake, Capistrano, rspec, shoulda, and more. They’re really everywhere in Ruby, to varying degrees.
Although Rich tried to open my eyes, I think I would have continued to try and push Copland if it weren’t for Jim Weirich. Jim took the idea of a Ruby-ish DSL for dependency injection and made something concrete of it. A few days after the conference he forwarded me a draft of an article he was writing, in which he described dependency injection and gave a very simple (and very elegant) implementation of a DI framework in Ruby. Instead of static configuration, he’d written a basic DSL for declaring how the dependencies related to each other.
It was a moment of epiphany for me. Suddenly, I got it. I understood what DSL’s were about. I asked Jim for permission to take his simple implemention and build upon it.
The result was Needle.
NeedleNow, I’m much prouder of Needle than of Copland, because it is much closer to Ruby’s philosophy than Java. There are some pretty cool designs in there, too, though I use the term “cool” here to mean “neat without having any real practical application.”
Needle, though better, was still far from the mark.
As an example of why it misses the mark, consider Needle’s “pipeline” concept. Conceptually, it allowed you to specify a sequence of post-processors that operated on an object, allowing you to wrap code around it and mimicking (among other things) AOP-like operations. It also let me (as the author of the library) easily implement things like deferred instantiation, singleton services, and the like.
For example, suppose you wanted to declare a “deferred singleton” service, that logged all accesses to one of the methods. Underneath, Needle will create a pipeline of processors that operate on the service, returning a proxy object. The first time the proxy is accessed, it will check to see if the object has been instantiated yet. If it hasn’t, it’ll instantiate it (and cache it). The instantiation, though, actually just hands control to the next element in the pipeline, which in this case checks to see that the “singleton” constraint is enforced (e.g., all requests for this service return the same object, rather than instantiating a new object). The next pipeline element in the chain will wrap the interceptor code around the method in question, and yet another pipeline element would perform the actual object instantiation.
Pipelines really were pretty slick in Needle.
The problem, though, is that instead of leaving them as an implementation detail, I advertised them as one of Needle’s features. “Implement your own service models!” I cried. But, how often, really, is that likely to happen? Instead of exposing only the bare minimum of Needle’s API, I exposed as much of it as I could, because I could.
That’s a bad idea. Expose only what you need. The rest can be there, available, but not formally exposed. Only when (and if) you discover a need to expose more, should you expose more. This helps for several reasons.
Now, I’ve since come to my senses, but at one time I was completely head-over-heels in love with dependency injection. Like any schoolboy crush, it embarrasses me now to think about it, but there’s no denying it. The proof is everywhere in my project history.
Net::SSH, in particular.
At the time, I was looking for a good demonstration of the flexibility and power of dependency injection, and since Net::SSH was another of my pet projects at the time, it seemed like the perfect candidate.
I was still stuck in the “just in case” mindset, though, and Net::SSH 1.x reflected that. Badly. For instance, I isolated all the OpenSSL crypto interfaces into their own module, because “what if someone wanted to plug in a different crypto lib?” Nevermind that there was no other crypto lib for Ruby (and still isn’t, 4 years later). But WHAT IF?!?
Now, separation of concerns and modularity are good things, when used in moderation. But like any design pattern, it becomes evil when taken to extremes. Too much modularity and you wind up with component soup (and I hope you’re hungry, because you’re going to have a lot of it). With lots of tiny components, the interactions between those components can become difficult to test.
It also fuzzes the line between the public, documented API and the internal, private API. When you have two large components, it is very easy to say “A is public, and B is private”, but when you have two hundred components, where do you draw the line? It’s far too easy to let the “public” boundary meander a bit further into “private” territory than it should.
Even worse, when I added dependency injection to the mix, it became very, very difficult to follow the the flow of the program, and to understand the dependencies. Pull up Net::SSH 1.1.4, for instance, and find net/ssh.rb. Just try and figure out how a connection session is instantiated. It’s a mess. Unless you’re familiar with Needle, it’ll probably take you a long time to discover that the actual services are configured in the various services.rb files, but even after you figure that out, you still have to figure out how the different services interrelate. It’s a mess.
But, isn’t that the opposite of what DI is supposed to do? Isn’t DI supposed to improve the maintainability and testability of your code? Yeah. The problem, though, was three-fold.
First, Net::SSH, though complex in its way, was not really complex enough to need a dependency injection framework. DI itself adds complexity, and a framework for doing dependency injection adds even more, so before you go that route you need to be very sure that the trade-off in complexity is worth it. If your project is too small, you’ll actually increase the complexity of your project by adding a framework for doing DI.
Secondly, I was using a DI framework at a level that was really too granular. I was using the framework to wire together everything. No component was too small! No object too insignificant! I was on the dependency injection horse, and riding it for all it was worth. If I’d taken the time to really understand the pattern, though, I would have learned that though the pattern itself may be applied at the micro level, using a framework to do so is like nuking a mosquito—it works, but it leaves a mess behind.
Which leads to the last problem with Net::SSH’s use of Needle: it is really only appropriate for wiring together components of an application. Very, very few (Ruby) libraries will ever be complex enough, in themselves, to justify adding a dependency injection framework to them. Rather, let the application wire the libraries together as (and when) it needs to. Any more granular than that, and you’ll run into the same quagmire I did, I promise you.
Dependency Injection in RubySo, is there no room for DI in Ruby? There definitely is. I use DI nearly every day in Ruby, but I do not use a DI framework. Ruby itself has sufficient power to represent any day-to-day DI idioms you need. Consider this one:
| 1 2 3 4 5 6 7 8 | class A end class B def new_client(with=A) with.new end end |
Here, B declares a factory method for generating new client objects. Because Ruby lets you declare default values for method arguments, you can let the default client implementation be A, which is the common case. But for testing, you can easily inject a mock into that method by passing an explicit parameter.
For cases where that doesn’t work, you can use a second factory method:
| 1 2 3 4 5 6 7 8 9 10 11 12 | class A end class B def new_client client.new end def client A end end |
Then, in your tests, you can subclass B, overriding the client method to return your mock client implementation. It’s dependency injection, Jim, but probably not as you’ve known it.
Hashes, too, are your friend. You can allow optional arguments via hashes to specify implementation classes, defaulting to the standard implementation classes but allowing clients to inject their own implementations where needed:
| 1 2 3 4 5 6 7 8 9 10 11 12 | class A end class B def initialize(options={}) @client_impl = options[:client] || A end def new_client @client_impl.new end end |
“Loose coupling” and “high cohesion” are terms you’ll hear bandied about in defense of dependency injection, and those traits are certainly desirable. But strike a balance with pragmatism. There will be some who call me heretic for saying this, but don’t be afraid to introduce tighter coupling when it makes sense. Loose coupling everywhere is what I had with Net::SSH 1.x, and the result was nearly unmaintainable.
Be wise. You’re competent. Trust your instincts.
Lessons learnedIf you read nothing else from this article, take to heart these bite-sized bullet-points:
Learning to program is a journey, and I’m still learning, myself. I’m not perfect at applying the rules above, but I’ve found that when I do, I’m much happier. I think you will be, too.
Over three years ago, I was faced with a dilemma. I had recently switched to the Mac (from Linux) and was still using my text editor of choice (vim), but at the time, vim’s “integration” with OS X was pretty minimal (and that’s putting it optimistically). I experimented with emacs, but it never clicked for me, and honestly, emacs on OS X wasn’t all that better than vim at the time. Sadly, reluctantly, I said good-bye to vim and switched to TextMate.
TextMate was (and certainly still is) a fantastic text editor. The project drawer was awesome, finding files via cmd-T was super powerful, and smarter auto-completion and snippets promised a new and faster way to pound code. After a couple of months of reteaching my fingers how to edit text, I was happy.
Sometimes, though, late at night, I would think again of vim.
Fast forward three years. The vim landscape is different now. There is actually a Mac-friendly GUI version of vim now, MacVim, which actually looks like it belongs on OS X. Vim 7 supports UI tabs, and a much more powerful auto-completion mechanism than before. And plugins like rails.vim and fuzzyfinder.vim mean that TextMate no longer has a corner on powerful project navigation.
For the last few weeks I’ve been toying with switching back to vim. TextMate’s “snippet” feature never clicked for me, and the only times I used it were by accident (when it annoyed me more than it helped me), but I really was hooked on the project browser, and cmd-T, and a few other things. I realized that, with a little work, perhaps a way could be found to reimplement most of the things I loved about TextMate, in vim.
This last week I’ve worked exclusively in vim, to test that theory. It’s like coming home. As I said, TextMate is a powerful and wonderful editor, too, but differently powerful and wonderful. Vim’s wonderfulness and power is the wonderfulness and power of git, or linux, where the learning curve is steep (ridiculously steep at times), but the rewards of mastery are sublime. I didn’t even realize I had missed a sane shift-J, or using the dot key to repeat the last command. Fix transposition typos with ‘xp’. Select a single word with ‘viw’. Drop bookmarks with ‘m’, and jump right back to them with single quote.
If any of that makes your stomach roil, then vim is not for you. :) But to me, it’s like being embraced by a long-lost friend after years apart. And vim holds no grudges.
There was still the issue of the TextMate features that I had particularly come to love. First to tackle was cmd-T, since my workflow had become so dependent on that for finding files. Takeshi NISHIDA’s fuzzyfinder.vim script seemed like exactly what I wanted…at first. It’s definitely a powerful tool, but the fuzzy finder for files was not TextMate’s cmd-T, and my instinctive attempts to treat it so were causing me a lot of aggravation.
So I took an evening and wrote fuzzy_file_finder, a Ruby library that mimics (and improves on, if I do say so myself) TextMate’s cmd-T functionality. Then, I extended fuzzyfinder.vim with fuzzyfinder_textmate, which bound the fuzzy_file_finder to vim. The result?
See for yourself: http://s3.amazonaws.com/buckblog/videos/fuzzyfinder_textmate.mov (600K, QuickTime video).
I’m still working on a solution for the project browser. Yes, I know there are several (“countless” might be a more accurate term) vim plugins that present a project drawer in a split window, but even before being spoiled by TextMate those didn’t feel right to me. I’m experimenting with a cocoa tree view that sends files to a specific vim server, and it mostly works, but I’m still not sure it’s the right solution. If I do come up with something, I’ll definitely open it up and share it. (On the other hand, if any of you out there in readerland already know of such a thing, please point me at it!)
So, I’m still reacquaiting myself with all my old muscle memories, but here are some commands I wasn’t previously very familiar with which are proving useful in conquering my TextMate habits:
And lastly, can I just say that Vim is seriously the poster-child for documentation? I recommend spending 15-30 minutes, every day, in :help, just exploring. There is a LOT there, and all excellently documented.
So, all you vimsters out there: what commands do you frequently use? What features of vim are you so dependent on that you’d be useless without them? Do share!
Capistrano 2.5.0 is now available! You can read the full release announcement on the capify.org news blog.
SQLite3-Ruby version 1.2.3 is now available. It is a maintenance release, fixing just a few things:
To install or upgrade:
| gem install sqlite3-ruby |
Thanks!
Are you currently using Capistrano 1.4.1? If so, drop everything (I mean it, do this RIGHT NOW) and install Capistrano 1.4.2.
Why, you ask?
Capistrano 1.4.1 will work just fine, right up until you decide you want to experiment with Capistrano 2. When you do that, Cap 2.3+ will install net-ssh 2.x, which kills Capistrano 1.4.1 in all kinds of really obscure ways.
The good news is that Cap 1.4.2 is completely compatible with Cap 1.4.1. It adds no new features. The only “bug” it fixes is that if you ever install net-ssh 2.x, Cap 1.4.2 will still happily continue to work.
Ultimately, an upgrade to Cap 2 is recommended, but I understand it’s not feasible for everyone. So, if you’re one of those who can’t go cap2 yet, please please please PLEASE PLEASE FOR THE LOVE OF ALL THAT IS HOLY upgrade to Cap 1.4.2. It’ll make your life easier, and it’ll make my life easier (because I won’t have to keep troubleshooting the same issues over and over). Thanks!
Having spent the better part of a day googling and struggling, I figured it would possibly benefit others if I took a minute to post the steps I took to clone a VMWare Fusion image. The image in question is of Ubuntu Server (Hardy). I’m using VMWare Fusion 2 (beta 2).
Just find your “Virtual Machines” folder (should be in your Documents folder), and copy the image in question to a new location. (The images are actually folders; a simple “cp -R” worked fine for me.)
Then, open the copied image in VMWare Fusion and boot it. VMWare Fusion will ask if you if you copied or moved the image—be sure to say you copied it (that let’s VMWare set up a new MAC address for your image).
Go ahead and log in once the server boots. You’ll find networking is all hosed. To fix networking, this worked for me:
Once your machine comes back up, you should have a network again! Now, if only VMWare Fusion could bake all this in somehow… :/
(Related: installing VMWare Tools on Ubuntu server. It’s from 2006, but it still worked well enough for me, though I followed the instructions for tweaking the network that the VMWare Tools install gave at the end, rather than what this gent said.)
So, the second Capistrano tutorial session has come and gone. It was a great session, though it started pretty rough. (I was very unprepared for some windows-related configuration issues during the first half-hour. That won’t happen again.) I appreciate everyone’s patience who participated!
I’m going to be quiet on the tutorial front for the next several weeks, but its not because I’ve given up on them. I’m going to be rethinking some aspects of them. I really want these to be opportunities for people to master capistrano, and while these first two tutorials have been very useful to those who attended (their words, not mine), I think they could be even better.
Stay tuned!
The first online Capistrano tutorial session was very successful, thanks to everyone who participated. Now, it’s time to try again!
The second online Capistrano tutorial session is now open for registration: http://events.capify.org/events/2.
It is scheduled for Wednesday, August 13th, at 7pm (Mountain time), and will last two hours (plus up to an hour at the end for Q&A). As before, this tutorial is targeted at beginning Capistrano users, those will little or no prior experience. (If you’re looking for a session for more advanced users, keep watching this space—I’ll probably do one of those eventually, as soon as I can write a curriculum for it.)
Tyler Bird, the director of the Capistrano documentation effort, has begun collecting links about Capistrano, to build a centralized de.licio.us account that people can use to find cap-related info around the web. You can read more about it on the capify.org/news site.
Today I’m pleased to announce the first online Capistrano tutorial session. It will be held via Campfire on July 15 at 7pm MDT, focusing on Capistrano basics for Rails deployment. The session will accomodate only ten people. For all details, read up at capify.org:
http://www.capify.org/2008/7/6/online-capistrano-tutorial-sessionIf this proves popular enough, and if the format works out well, I’ll consider hosting more of these on more advanced topics.
Capistrano 2.4.3 is now available!
gem install capistrano
This is a maintenance release. The changes since 2.4.0 include:
Please report bugs to the Capistrano bug tracker at Lighthouse:
http://capistrano.lighthouseapp.com/projects/8716/tickets
Patches should be generated against the latest version of the code at GitHub:
http://github.com/jamis/capistrano
You may submit patches via email, as submissions to the Capistrano bug tracker, or as GitHub pull-requests.
Net::SSH 2.0.3 is now available!
gem install net-ssh
This is a maintenance release that fixes the following issues:
Please report bugs to the bug tracker for net-ssh on rubyforge:
http://rubyforge.org/tracker/?atid=1123&group_id=274&func=browse
If you have a patch you want to submit, please make sure to create it against the latest version of the code in the Net::SSH repository on GitHub:
http://github.com/jamis/net-ssh/commits/master
Patches may be submitted via email, as GitHub pull-requests, or as posts to the net-ssh tracker on rubyforge.
Capistrano 2.4.0 is now available.
gem install capistranoReport bugs to Capistrano’s Lighthouse project. And if you have some ideas for patches, please patch against the code at Capistrano’s GitHub repository.
Here’s the skinny on 2.4.0:
death to “git fetch --tags“You git users out there will be pleased to learn that this release will no longer do “git fetch --tags”, meaning your git-based deploys will finally work again. Sorry that took so long to fix. It was really ridiculously simple. :(
cap -dMark Imbriaco added a debug switch for enabling conditional execution of commands. This is fantastic for debugging and testing tasks, since you can basically step through your tasks and approve or deny each remote command as it is executed. Just give cap the “-d” switch to enable this.
New and improved sudo helperNo, really. This time I mean it. I pulled the version of sudo introduced in 2.3.0 (where each sudo command was wrapped in an explicit sh invocation), and put it back almost exactly as it was. However, if you call sudo without a command to execute, it will instead return the sudo command to use. On top of that, if the run() helper detects that you’re using sudo, it will listen for password prompts. So you can now do arbitrarily complex sudo commands like this:
| 1 | run "if [ -d /some/directory ]; then #{sudo} chmod -R g+w /some/directory; fi" |
In other words, just dump the sudo() call into your command as an interpolated value, and the real sudo command gets substituted. You can pass options to it as well:
| 1 | run "#{sudo :as => "bob"} something" |
And, naturally, the original sudo() syntax we all know and love remains as before:
sudo "something", :as => "bob"The deployment recipes themselves have been updated to use this new syntax, as needed.
:runner vs. :admin_runnerSome cappers have noted that having deploy:setup and deploy:cleanup run as the :runner user messed up their carefully crafted permissions. I agreed that this was a problem. With this release, deploy:start, deploy:stop, and deploy:restart all continue to use the :runner user when sudoing, but deploy:setup and deploy:cleanup will use the :admin_runner user. The :admin_runner variable is unset, by default, meaning those tasks will sudo as root, but if you want them to run as :runner, just do “set :admin_runner, runner”.
deploy:upload with globsYou can now specify glob patterns with deploy:upload:
| 1 | $ cap deploy:upload FILES="config/apache/*.conf" |
As before, you can also specify entire folders to upload, but now the upload is being done via the upload() helper (introduced in 2.3.0) so the behavior is more standardized.
before/after hooks use well-defined server scopePrior to this release, if you defined a before hook using a block, the block would be executed within the same server scope as the original task that invoked task that this hook was attached to. (whew!)
In other words:
| 1 2 3 4 5 6 7 8 9 10 11 12 | task :first, :roles => :app do # ... second end task :second, :roles => :db do # ... end before :second do # ... end |
Prior to this release, when “first” calls “second”, the before hook at the bottom would get called, but it would get called with the :roles => :app server constraint active, instead of :roles => :db. This preview release makes those hooks use the server scope of the task they are attached to.
host reported correctly from SCM outputPrior to this release, when any output was processed by the :checkout, :export, or :remote_cache strategies, you’d not be able to tell what host the output was from (it’d just be prefixed [err] or [out]). With this release, that output is correctly tagged with the host that generated it, making it easier to troubleshoot issues with SCM command execution.
Disable asset timestamp normalizationSome SCM’s give you the option of forcing the modification times of files that are being checked out to be the time that they were last modified in the repository. If your SCM gives you that capability, and if you enable it (however that works in your SCM), then you don’t need the massive touch command that Capistrano’s default deployment tasks run on each deploy, which is cap’s way of forcing all assets on all of your servers to have the same timestamp.
To disable Capistrano’s default timestamp normalization step, just set the :normalize_asset_timestamps variable to false:
| 1 | set :normalize_asset_timestamps, false |
You could also do this if you are deploying to a single server only. Note, though, that if you do this when deploying to multiple servers, and you haven’t configured your SCM to set the timestamps on the files on checkout, then Rails may use inconsistent timestamps on the assets, causing caching of your assets to fail and resulting in longer load times for your users.
Other lesser fixes, straight from the CHANGELOGOn February 16 I set myself to travel a path that I hoped would eventually help me deal with the project overload I was feeling. Today I get to release Capistrano 2.3, which is very near to the end of that path!
gem install capistranoCapistrano 2.3 is primarily significant in that it switches to the new Net::SSH v2 library, which is faster and slimmer than the older Net::SSH v1 library. It also pulls in Net::SFTP v2, and the new Net::SSH::Gateway and Net::SCP libraries.
In addition to that, cap 2.3 adds several exciting (if I do say so myself) new features, and a few bug fixes that had lingered for far too long.
FEATURE: The :copy strategy has been significantly improved. Instead of doing a blind checkout/export, archiving the result, and copying it over, you can specify that a local cached copy of your repository should be used. If the cached copy does not exist, it is created (via a checkout), otherwise it is simply updated (a much faster operation than a checkout, typically). This functions much like the :remote_cache strategy, but locally. To use this, simply set the :copy_cache variable to true:
| 1 2 | set :deploy_via, :copy set :copy_cache, true |
By default, the cached copy will be in your machine’s temporary directory (/tmp, for example), but you can specify your own location by setting :copy_cache to the desired path:
| 1 2 | set :deploy_via, :copy set :copy_cache, "/u/caches/#{application}" |
But wait! There’s more! Suppose you have certain files that you don’t want to deploy, like photoshop files or your .git directory. You can set the :copy_exclude variable to a file glob (or an array of globs):
| 1 2 3 | set :deploy_via, :copy set :copy_cache, true set :copy_exclude, [".git", "materials"] |
This should make your deploys faster than ever, especially using a tip I hope to share in the next day or two that uses this in conjunction with some vendor/rails symlinking.
FEATURE: Even though I strenuously believe it is a mistake to deploy anything that is not under source control, I’ve finally caved and added a dumb :none SCM module. It can be used to deploy a specific directory if used with the :copy strategy:
| 1 2 3 | set :repository, "." set :scm, :none set :deploy_via, :copy |
Again, there are very, very few cases when I think use of this technique is justified, but because I ran into one myself a month ago, I decided it was worth adding.
FEATURE: Support was added for “depend :remote, file” to test for the existence of a specific file:
| depend :remote, "/etc/syslog.conf" |
This is used in conjunction with the deploy:check task.
FEATURE: You can specify ssh_options per-server, now, simply by giving an :ssh_options key and corresponding hash with the server definition:
| role "some.host", :ssh_options => { :keys => "/path/to/key" } |
FEATURE: There are two new file transfer helpers, upload and download, which are much more powerful and resource-friendly than the old ‘put’ and ‘get’ helpers. You can use upload and download to transfer single files, or entire directory trees:
| 1 2 | upload "/local/file", "/remote/file" download "/remote/file", "/local/file" |
This will transfer to or from all active servers, which is particular tricky when using the download helper, since it will download the file simultaneously from all active hosts. To make this work, you need to make sure each is downloaded to a different location:
| 1 2 | Dir.mkdir("destination") download "/remote/file", "destination/file-$CAPISTRANO:HOST$" |
The above will download the file from each host to a file on the local host, where the local file includes the name of the source host. Tricky!
Also, you can now specify that you want to upload or download via SCP instead of SFTP:
| 1 2 | upload "local", "remote", :via => :scp download "remote", "local", :via => :scp |
The default is :sftp.
The less exciting (but still mildly titillating) things are the bug fixes and pleasure-enhancing behavioral changes:
So, have at it! Remember to report bugs to http://capistrano.lighthouseapp.com. And patches are always welcome via git—just fork the capistrano repository at git://github.com/jamis/capistrano.git (thanks GitHub!).
Cheers!
At last! Net::SSH 2.0 is available! Also available are Net::SFTP 2.0, Net::SCP 1.0, Net::SSH::Gateway 1.0, and Net::SSH::Multi 1.0.
| 1 2 3 4 5 | $ gem install net-ssh \ net-sftp \ net-scp \ net-ssh-gateway \ net-ssh-multi |
All of these Ruby libraries are for communicating with remote servers via the SSH protocol in different ways.
Net::SSH and Net::SFTP are both significant upgrades from their previous incarnations; if you have used either library in the past, you’ll want to read the documentation (Net::SSH, Net::SFTP). The Net::SSH API is still fairly similar to the way it was before, but the Net::SFTP API is entirely different.
All have pretty complete RDoc documentation, so you should be able to employ “ri” to good effect to find your way around the libraries. (Try “ri Net::SSH”, for example, to get started.)
Per-library synopses follow.
Net::SSH 2.0This is a significant upgrade from Net::SSH 1.x. Changes from 1.x include (but are not limited to):
| 1 2 3 4 5 6 | require 'net/ssh' Net::SSH.start('localhost', 'jamis') do |ssh| ssh.exec('hostname') # prints the results to $stdout ssh.loop end |
This is a complete rewrite of the original Net::SFTP 1.x code, and shares very, very little in common with it. The new version has a much cleaner implementation and API, and provides some really handy methods for transferring files and directories.
| 1 2 3 4 5 6 7 8 9 10 | require 'net/sftp' Net::SFTP.start('localhost', 'jamis') do |sftp| sftp.upload! "/local/file", "/remote/file" sftp.download! "/remote/file", "/local/file" sftp.file.open("/remote/file", "w") do |file| file.puts "here is some data" end end |
This provides a way to transfer files and directories via the SCP protocol, over Net::SSH.
| 1 2 3 4 5 6 | require 'net/scp' Net::SCP.start('localhost', 'jamis') do |scp| scp.upload! "/local/file", "/remote/file" scp.download! "/remote/file", "/local/file" end |
This library makes it easy to tunnel connections though firewalls. You simply connect to the gateway machine, and then specify which ports you want forwarded.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | require 'net/ssh/gateway' gateway = Net::SSH::Gateway.new('host', 'user') gateway.ssh("host.private", "user") do |ssh| puts ssh.exec!("hostname") end gateway.open("host.private", 80) do |port| require 'net/http' Net::HTTP.get_print("127.0.0.1", "/path", port) end gateway.shutdown! |
This library makes it simple to open multiple Net::SSH connections and tie them all together, running commands in parallel (much like Capistrano does).
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | require 'net/ssh/multi' Net::SSH::Multi.start do |session| # access servers via a gateway session.via 'gateway', 'gateway-user' # define the servers we want to use session.use 'user1@host1' session.use 'user2@host2' # define servers in groups for more granular access session.group :app do session.use 'user@app1' session.use 'user@app2' end # execute commands on all servers session.exec "uptime" # execute commands on a subset of servers session.with(:app).exec "hostname" # run the aggregated event loop session.loop end |
Enjoy!
With the impending releases of the improved Net::SSH family of libraries, and the next Capistrano release that will depend on them, it was time to go back and make sure that the oldies-but-goodies will still work along side these new-comers.
To that end, I present:
Net::SSH 1.1.3This maintenance release will hopefully fix the hangs that people have been reporting when dealing with long-running requests in Net::SSH, Net::SFTP, and Capistrano.
gem install net-sshIt is strongly recommended that all Net::SSH users upgrade to this version.
Capistrano 1.4.2This release merely adds an explicit dependency on Net::SSH and Net::SFTP versions less than 1.99.0, allowing you to use both cap1 and cap2 side-by-side after Net::SSH v2 (and friends) are released.
gem install --version 1.4.2 capistranoIf you are needing to run cap1 and cap2 side-by-side (for instance, if you haven’t yet upgraded some recipes, but want to enjoy the cap2 goodies on newer projects), you should upgrade your cap1 installation to 1.4.2. If you are completely upgraded to cap2, you can safely ignore this release.
I’m almost ready to do a preview release of the next Capistrano version, which is built on top of the new Net::SSH library. To do that, though, I need to prime the pump by pushing out a few small fixes to Net::SSH. So, behold!
Net::SSH 2.0 Preview Release #4 (1.99.3): gem install --source http://gems.jamisbuck.org net-sshThis release has two minor new features and two fixed bugs:
Unrelated to Capistrano (since the next release of cap won’t be using it), I’m also pushing out a second preview release of Net::SSH::Multi, a library for managing multiple Net::SSH connections in parallel.
gem install --source http://gems.jamisbuck.org net-ssh-multiThis release has a single bug fix:
I’ll hopefully have a preview release for the next Capistrano version next week sometime. It’s close! I’m using it myself, locally, but I want to try it on a few more deploys to make sure it really works as advertised. I haven’t actually run any timings on it, but Capistrano with Net::SSH v2 feels significantly faster than it was on Net::SSH 1.x. Stay tuned!
Last night I released the first preview of Net::SSH::Multi (gem install --source http://gems.jamisbuck.org net-ssh-multi). Today, let me show you a tasty hint of what you can do with it.
Consider the following Rakefile:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | def remote @remote ||= begin require 'net/ssh/multi' session = Net::SSH::Multi.start session.via 'gateway.host', session.default_user session.group :web => session.use('web1', 'web2') session.group :app => session.use(*(1..8).map { |n| "app%02d" % n }) session.group :db => session.use('db1', :properties => { :primary => true }) session end end namespace :remote do task :hostnames do remote.exec("hostname").wait end task :app_hostnames do remote.with(:app).exec("hostname").wait end task :web_hostnames do remote.with(:web).exec("hostname").wait end end |
You can now do things like this:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | $ rake remote:hostnames (in /home/jamis) [web1] web1.host [app02] app02.host [app05] app05.host [app03] app03.host [app06] app06.host [app08] app08.host [db1] db1.host [app01] app01.host [web2] web2.host [app04] app04.host [app07] app07.host |
The Net::SSH::Multi library is still experimental, but it is stable and full-featured enough that I seriously considered implementing the next release of Capistrano on top of it. (I’ll probably put that off until cap3, though, due to the magnitude of the change.) If you do something cool with it, let me know!
I’ve got lots of code to share with you all today:
Net::SSH 2.0 Preview Release #3Not very many external changes, but channels now have an on_open_failure callback that you can employee to be told when a channel could not be opened.
| 1 2 3 4 5 6 7 | channel = ssh.open_channel do |ch| # ... end channel.on_open_failure do |ch, code, reason| puts "could not open channel because #{reason.inspect} #{code}" end |
This change was necessary to make port forwarding a bit more reliable for Net::SSH::Gateway (see below).
Also, individual Net::SSH sessions may contain their own custom properties, much as Net::SSH channel objects do, which can be quite handy when you need to keep some state associated with the session.
ssh[:key] = "value"This change (and several other internal refactorings) was necessary for the correct working of the Net::SSH::Multi library (see below).
To get the preview release #3 (version 1.99.2):
gem install --source http://gems.jamisbuck.org net-sshYou can follow development via GitHub:
http://github.com/jamis/net-ssh/tree/master Net::SFTP 2.0 Preview Release #2All that was added in this preview release was custom properties on Upload instances:
| 1 2 3 | uploader = sftp.upload("local.file", "remote.file") uploader[:failed] = false # ... |
This was necessary to get Capistrano file transfers to work with Net::SFTP 2.0.
To get the preview release #2 (version 1.99.1):
gem install --source http://gems.jamisbuck.org net-sftpYou can follow development via GitHub:
http://github.com/jamis/net-sftp/tree/master Net::SSH::Gateway 1.0 Preview Release #1Net::SSH::Gateway is (in essence) the extraction of the gateway code from Capistrano into its own library. It lets you tunnel SSH connections through some “gateway” server to servers that would be otherwise inaccessible.
| 1 2 3 4 5 6 | require 'net/ssh/gateway' gateway = Net::SSH::Gateway.new('gateway.host', 'username') gateway.ssh('remote.host', 'user') do |ssh| # ... end |
You can also use it as a general facilitator for forwarding connections over a local port, when you don’t care what port is to be used—you just want to connect to a remote server.
| 1 2 3 | gateway.open('remote.host', 80) do |port| Net::HTTP.get_print '127.0.0.1', '/path', port end |
The current thinking is that the next release of Capistrano will ditch its own gateway implementation in favor of Net::SSH::Gateway. To get this first preview release (version 0.99.0):
gem install --source http://gems.jamisbuck.org net-ssh-gatewayYou can follow development via GitHub:
http://github.com/jamis/net-ssh-gateway/tree/master Net::SSH::Multi 1.0 Preview Release #1Net::SSH::Multi is the guts of Capistrano, extracted into a library of their own. It allows you to define and categorize servers, and then execute commands in parallel on them, or on subsets of them, using an interface similar, if not identical, to that of Net::SSH::Connection::Session and Channel.
Eventually, Capistrano will be refactored to take advantage of Net::SSH::Multi, but doing so will require some significant changes to Capistrano’s innards, and would almost certainly break many third-party libraries. Thus, you won’t see Capistrano on Net::SSH::Multi until Capistrano 3.0 or so (which will not be the next release of Capistrano). However, you can use Net::SSH::Multi to implement most of Capistrano’s functionality in whatever form you like. Want Capistrano-in-a-rakefile? Have at it!
| 1 2 3 4 5 6 7 8 9 | require 'net/ssh/multi' Net::SSH::Multi.start do |session| session.use 'jamis@somewhere.com' session.use 'jamis@elsewhere.com' session.exec 'hostname' session.loop end |
Grab the first preview release (version 0.99.0):
gem install --source http://gems.jamisbuck.org net-ssh-multiYou can follow development via GitHub:
http://github.com/jamis/net-ssh-multi/tree/masterFor all of these libraries I’ve tried to make the rdoc and ri documentation as informative as possible, so please refer to those to get started. If those aren’t helpful enough, let me know what could make them more useful!
After far, far too long, I’m extremely pleased to announce the next preview release of Net::SSH v2 (preview release #2, actual version 1.99.1), as well as the first ever preview releases of Net::SFTP v2 (preview release #1, actual version 1.99.0) and Net::SCP v1 (preview release #1, actual version 0.99.0). You can grab them from my gem server:
| 1 2 3 | $ sudo gem install --source http://gems.jamisbuck.org net-ssh $ sudo gem install --source http://gems.jamisbuck.org net-sftp $ sudo gem install --source http://gems.jamisbuck.org net-scp |
I’ve taken Eric Hodel’s advice this time around and ditched the pretty documentation I had started for Net::SSH; I’m going to see how far I can get with ri and rdoc alone, this time. Give it a try after installing the above: “ri Net::SSH”, for instance.
There are several caveats about this release:
1. The new Net::SSH and Net::SFTP are not backwards compatible with the previous 1.x releases, so make sure any existing scripts are explicitly depending on versions less than 1.99.0 of these libraries. (The latest Capistrano already has that explicit dependency in it, so you should be safe installing these preview releases even if you are using Capistrano).
2. The “shell”, “open3” and “sync-shell” services are gone from Net::SSH. (There may be others, too, which aren’t coming to mind at the moment.) I do not intend to bring them back. If you used those services, I strongly encourage you to reimplement them for the new Net::SSH, and release them as third-party extensions.
3. These libraries are reentrant (or ought to be), but are not thread-safe. That is to say, if you are accessing the same Net::SSH connection from multiple threads, you’re definitely going to want to do something to protect that resource (mutexes, etc.).
That said, let me know what you think, what issues you have, what suggestions you have, and so forth. I’m so happy to have this release done, though. I can see the light at the end of the tunnel finally!
Oh, and lastly, if you happen to be drinking the git kool-aid, you can follow along via github:
Net::SSH 1.x is thread-safe. Net::SSH v2, as currently implemented in its pre-release state, is not.
I’ve debated this long and hard with myself. Wrapping code in mutexes and doing all the other stuff that thread-safety requires adds a surprising amount of overhead, and it feels painful to me to have to add all that when the most common use-case of the library will be in single-threaded environments. However, without the mutexes and all that overhead, using Net::SSH in a multi-threaded environment (where multiple threads are hitting the same Net::SSH connection) will result in some rather ugly errors, and would require those programs to add their own mutexes and such to protect the integrity of the connection.
I’m leaning towards leaving the library optimized for the single-thread experience, and requiring the people wanting multiple threads of execution to fend for themselves. However, in the interest of getting feedback from people who might actually use the library, I ask you: which would you prefer? A faster library in single-threaded programs? Or a simple program in multi-threaded ones? Is there a general best-practice in this case?
I think...I think I’m actually, done, more or less, with Net::SFTP v2!
I just committed a last bit of documentation tweaking, and although I’m sure there’s more of that to come, and bugs to fix and features to add, I’m calling this bit “good enough”, and I’m moving on now to Net::SCP.
I’m not actually releasing Net::SFTP v2 at this point—it’s pretty much useless without a corresponding Net::SSH v2 release. So why am I saying anything about it right now? Because it’s the first significant milestone that I’ve completed in my track to complete the new Net::SSH suite of libraries! And I’m excited! And I’ve learned something significant by finishing Net::SFTP v2 before completely finishing Net::SSH v2: by completing a dependency of the primary library first, I saw first-hand where the deficiencies in the primary library were. This has resulted in a much tigher, much more useful API than the Net::SSH v1 and Net::SFTP v1 libraries.
Now, there is no real release of Net::SSH v2 and friends, but that’s not to say that if you’re brave, and don’t mind exploring a bit, you couldn’t get the new Net::SSH and Net::SFTP to work. The new Net::SSH is actually mostly done—I just need to revisit the documentation and update a few things here and there. The code is all on github. If you don’t have git, you can download a tarball directly from github, even.
So, have at it, if you’re so inclined. (And if you’d like on the github bandwagon, I’ve still got two invites burning a hole in my pocket…email me at (my first name) at jamisbuck.org, and I’ll be happy to send you one.)
My son, Britton, woke up this morning a little before 4am. As I was getting back into bed after feeding him, I saw that the clock read “4:04”, and (being the geek I am) I immediately thought “heh, 404 Sleep Not Found”.
He woke up a few minutes later, needing a bit of comforting (he’s definitely not usually this needy!), and when I got back into bed, the clock read “4:22”, obviously telling me that sleep was an “Unprocessable Entity”.
And thus the Magic 8ttp Ball was born.
Go on! Ask it anything! It’ll give you a suitably ambiguous HTTP response code in return. I’ve even been told it works great on the iPhone for when you have those burnin