Moubry Blog

The place where Moubries blog

Ruby Reading List

I’ve been developing Ruby for years. I’ve read a lot of books on the topic. These are my Top 10 favorites. They’re ranked in the order you should read them.

  1. Agile Web Development with Rails — Learning Rails is a great introduction to the Ruby programming language. Ruby is much bigger than Rails, but starting with this book gives you instant gratification. You make a real website in the first chapter!
  2. Programming Ruby — This is affectionately called “the pickaxe book” by Ruby developers. It’s an in depth introduction to the Ruby programming language.
  3. Eloquent Ruby — Russ Olsen is my favorite writer about Ruby. This book is amazing at capturing the idiomatic style Ruby developers write in. Great real-world advice.
  4. The RSpec Book — The best introduction to testing for a Ruby developer.
  5. The Ruby Way — This is a GIANT cookbook that takes you through solving over 400 problems in “The Ruby Way”.
  6. Practical Object-Oriented Design in Ruby — This fantastic book is an introduction to object-oriented design in Ruby. One of my favorites.
  7. Design Patterns in Ruby — Learning design patterns gives you a language through which to communicate complicated design decisions to other developers.
  8. Refactoring: Ruby Edition — Martin Fowler’s classic on refactoring adapted for Ruby. Fowler considers this to be the true 2nd edition to “Refactoring”.
  9. Metaprogramming Ruby — Learning how to metaprogram will teach you how Ruby works as a language. You’ll learn about the object model and other Ruby internal concepts.
  10. Exceptional Ruby — Yeah, this book is just about exceptions. Turns out, that’s a pretty important part of programming that few programming books have the space to capture. Writing real-world resilient code is about managing failure.

TunTap Doesn’t Work on Yosemite

When I upgraded to Yosemite, I could no longer use vpnc to VPN into work. I got the following error:

can't initialise tunnel interface: No such file or directory

If you Google “tuntap does not work on yosemite” you might find this blog post. You’ll also find this issue on GitHub for the Homebrew project. Which links to this umbrella issue on the Homebrew project.

What’s wrong?

TunTap (a dependency of the VPNC) does not work on Yosemite because OS X no longer allows unsigned kernel extensions.

Previous versions (like Mavericks) allowed unsigned kernel extensions to run.

The easiest solution to this issue is globally disabling the system security policy that requires kernel extensions to be signed.

How to disable kext signing requirement

You should not do this. But if you’re like me, you need to VPN into work right now, and are willing to accept the risks. Because worst case scenario, it’s exactly as insecure as Mavericks, which you were using yesterday.

These instructions are adapted from this and this.

Uninstall TunTap:

brew unintall tuntap

Boot into recovery mode by holding Cmd+R during reboot. Open terminal and further ensure tuntap is gone:

rm -rf /Library/Extensions/tap.kext
rm -rf /Library/Extensions/tun.kext
rm -rf /Library/StartupItems/tap
rm -rf /Library/StartupItems/tun

List all of the existing boot-args you’ve set:

nvram boot-args

Does it say “kext-dev-mode=1”? If so, just restart because you’re already good!

Disable required signing of kernel extensions:

nvram boot-args="-v kext-dev-mode=1"


Install TunTap:

brew install tuntap

Run the commands listed in Homebrew/TunTap’s installation output.

Start TunTap by loading its kernel extensions:

sudo kextload /Library/Extensions/tap.kext
sudo kextload /Library/Extensions/tun.kext

Verify by running ls /dev/tun* and confirm that it lists ~10 virtual interfaces.

If you ever get the can't initialise tunnel interface error again, check to see if TunTap’s kexts are loaded by running the above command again. You may need to configure OS X to load them at startup.


Boot into recovery mode.

Remove all arguments:

nvram -d boot-args


Contextual Taskbars With Powershell


I use my personal laptop (Windows 8) for a variety of tasks from perusing social networks to writing blogs to developing software. Each of these requires a completely different set of applications and bookmarks. I don’t need Excel when I’m writing a blog, and I don’t need Picasa when I’m developing an app.

I want my desktop to be tailored to the task at hand to promote focus. I also have more apps than will fit on my taskbar, but I like having them there to remind me of the tools I have available (out of sight out of mind).


Write a script to save and restore sets of taskbar icons.

I wanted to throw something together quickly and this script will only ever be useful on a Windows machine so I chose Powershell. I discovered that Windows persists taskbar data in two different locations.

The paths to the applications and application start settings are stored in the registry under:


Funny how they call it the taskband in the registry but everywhere else it’s the taskbar. The actual icons are stored in:

C:\Users\$username\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar

This makes no sense to me–what does the taskbar have to do with Internet Explorer or Quick Launch? Windows Store Apps don’t require an icon in that folder.

The script I ended up with uses a Windows Forms interface (remember I said I wanted something quick, not pretty) and basically just exports and imports the taskbar registry/icons. It uses the reg command to export the taskband registry to a file and it copies the icons into a folder and then import does the reverse. View the script on GitHub.

I also added a shortcut to the taskbar that runs my script as an administrator when you click on it so that I can quickly switch between taskbar configurations:

Blogging Desktop

Web Development Desktop


When I have time, these are the next steps for the script:

  • Improve on the UI by switching to WPF–there are several Powershell modules out there to help with that.
  • Generate a visual overview of what each of the taskbar contexts that have been saved (exported) look like.
  • Launch Chrome with some bookmarks pre-opened based on the selected context.

How Git Grows

If you feed me, Seymour, I can grow up big and strong.

The more you commit to a Git repository, the bigger the repository gets.

A project’s repository lives in a .git folder at its root. Our Rails project at TripCase has a 60M Git folder:

$ du -sh ~/sabre/tripcase-rails/.git
 60M  /Users/sean/sabre/tripcase-rails/.git

This folder could’ve been a lot bigger, but Git has clever compression mechanisms to only store the diffs between files instead of the full file with every commit.

However, binaries can’t be diffed by Git. If you’re committing a lot of these files, even if the changes aren’t great, your repository can grow quite large.

An Exercise

Create a new repository:

$ mkdir -p ~/Developer/backgrounds
$ cd ~/Developer/backgrounds
$ git init

Let’s make our first commit—we’ll throw in an image from elsewhere on the disk. Let’s find a good one:

$ ls -lahS /Library/Desktop\ Pictures/

Beach.jpg looks good. Let’s copy it into our project:

$ cp /Library/Desktop\ Pictures/Beach.jpg background.jpg

Commit it:

$ git add background.jpg
$ git commit -am "Initial commit"

Now check out the objects folder:

$ du -sh ~/Developer/backgrounds/.git/objects/*
4.0K  /Users/sean/Developer/backgrounds/.git/objects/52
4.0K  /Users/sean/Developer/backgrounds/.git/objects/68
10.0M /Users/sean/Developer/backgrounds/.git/objects/c7
  0B  /Users/sean/Developer/backgrounds/.git/objects/info
  0B  /Users/sean/Developer/backgrounds/.git/objects/pack

Notice the “10.0M” object.

Let’s overwrite that file with a new image (the largest one in the backgrounds directory Zebras.jpg) and commit it:

$ cp /Library/Desktop\ Pictures/Zebras.jpg background.jpg
$ git commit -am "Zebras over Beaches"

The objects again:

$ du -sh ~/Developer/backgrounds/.git/objects/*
4.0K  /Users/sean/Developer/backgrounds/.git/objects/27
4.0K  /Users/sean/Developer/backgrounds/.git/objects/52
4.0K  /Users/sean/Developer/backgrounds/.git/objects/68
4.0K  /Users/sean/Developer/backgrounds/.git/objects/7e
10.0M /Users/sean/Developer/backgrounds/.git/objects/c7
 25M  /Users/sean/Developer/backgrounds/.git/objects/f0
  0B  /Users/sean/Developer/backgrounds/.git/objects/info
  0B  /Users/sean/Developer/backgrounds/.git/objects/pack

Use to trivially change the content of the image:

$ open background.jpg

Add an arrow or some text somewhere on it. Just leave it mostly the same image.

Commit the change:

$ git commit -am "Adding an arrow to Zebras"

The objects again:

$ du -sh ~/Developer/backgrounds/.git/objects/*
4.0K  /Users/sean/Developer/backgrounds/.git/objects/0f
4.0K  /Users/sean/Developer/backgrounds/.git/objects/27
4.0K  /Users/sean/Developer/backgrounds/.git/objects/52
4.0K  /Users/sean/Developer/backgrounds/.git/objects/68
 28M  /Users/sean/Developer/backgrounds/.git/objects/7e
10.0M /Users/sean/Developer/backgrounds/.git/objects/c7
 25M  /Users/sean/Developer/backgrounds/.git/objects/f0
4.0K  /Users/sean/Developer/backgrounds/.git/objects/fa
  0B  /Users/sean/Developer/backgrounds/.git/objects/info
  0B  /Users/sean/Developer/backgrounds/.git/objects/pack

There’s a new 28M object right there after the 25M object. Why? They’re essentially the same image, except for I added an arrow to it. Shouldn’t Git get be fancy and know how to only store the diff?

Let’s use the git-gc command to cleanup the objects directory:

$ git gc
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), done.
Total 9 (delta 0), reused 0 (delta 0)

$ du -sh ~/Developer/backgrounds/.git/objects/*
4.0K  /Users/sean/Developer/backgrounds/.git/objects/info
 63M  /Users/sean/Developer/backgrounds/.git/objects/pack

Oh, so that’s what cleanup does. The pack folder there contains the following files:

$ du -sh ~/Developer/backgrounds/.git/objects/pack/*
$ 4.0K    /Users/sean/Developer/backgrounds/.git/objects/pack/pack-ba1ac2ddcbb7e3d283a6eae112a073564bcdbdfd.idx
$  63M    /Users/sean/Developer/backgrounds/.git/objects/pack/pack-ba1ac2ddcbb7e3d283a6eae112a073564bcdbdfd.pack

Notice that that pack file is 63M which is exactly the sum of the objects we had before (28M + 10M + 25M). Probably would’ve been more useful if there were unreachable objects or duplicate objects.

According to documentation, git-gc has an --aggressive option:

Usually git gc runs very quickly while providing good disk space utilization and performance. This option will cause git gc to more aggressively optimize the repository at the expense of taking much more time. The effects of this optimization are persistent, so this option only needs to be used occasionally; every few hundred changesets or so.

Let’s see if that helps:

$ git gc --aggressive
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), done.
Total 9 (delta 0), reused 9 (delta 0)

$ du -sh ~/Developer/backgrounds/.git/objects/*
4.0K  /Users/sean/Developer/backgrounds/.git/objects/info
 63M  /Users/sean/Developer/backgrounds/.git/objects/pack


We Do This To Ourselves

GitHub has super useful, world-class image diffing support. In order to diff images, you have to commit multiple versions of the same image. And as we demonstrated above, Git isn’t great at optimizing for this behavior.

This isn’t the only feature GitHub supports despite it being a pretty bad idea. From their “What is my disk quota?” page:

GitHub supports rendering design files like PSDs and 3D models.

Because these graphic file types can be very large, GitHub’s designers use a service like Dropbox to stay in sync. Only the final image assets are committed into our repositories.

GitHub isn’t alone:

What We’ve Learned

Git has features that optimize disk space for text data. It’s not great at binary files, though.

Maybe it’s not technically possible to only store the diff from an image change. Maybe Git just doesn’t have this feature. Maybe committing images inefficiently isn’t that big of a deal.

Committing large binary files is fine, I guess. The value seems to outweigh the costs, which appears to be performance, disk size, and clone time. It should be fine as long as you don’t anticipate that you’ll ever blow past your Git host’s disk quota. However, this seems like a hard thing to estimate for.

Going back in time to remove binary files does not look trivial.

Hit me up on Twitter if you have any feedback.

“Why Wasn’t I Consulted?”

That motherfucker.

Paul Ford, in his great essay The Web Is a Customer Service Medium:

“Why wasn’t I consulted,” which I abbreviate as WWIC, is the fundamental question of the web. It is the rule from which other rules are derived. Humans have a fundamental need to be consulted, engaged, to exercise their knowledge (and thus power), and no other medium that came before has been able to tap into that as effectively.

Yet even with things like Wikipedia as an outlet, people still feel outraged when they aren’t consulted about something. It’s an unsolvable problem. It’s like trying to eradicate fear; sure you can focus really hard on it, but it’s probably healthier to just realize you’ll be scared sometimes and that’s okay.

I see this WWIC outrage all the time. I especially see this at work. Like all the time.

Then there’s the variant: “Nobody asked me!” which is best invoked as a defense when someone else made a decision without you. Maybe it was a bad decision and people think you should’ve been there to stop it from being made. “Hey, they made that decision without me. I wasn’t involved. I wish they would’ve called me in because I really could’ve fixed everyone’s problems and saved the world. But alas. I had nothing to do with it. Therefore, I’m not really emotionally invested in helping with the fallout either.”

While this victim-card-playing character I’ve painted here is a little exaggerated (but not much), they seriously could’ve actually been able to prevent a fallout from a bad decision!

Oh, hell no.

So it’s worth considering: why weren’t you consulted?

I’ve thought a lot about this myself. And these are the reasons I came up with. In your scenario, it’s probably a combination of them.

They didn’t even consider you.

They probably just forgot. I guarantee that most of the time it will be because they forgot about you. You just didn’t cross their mind as someone to engage.

Okay, so why is that?

They don’t know you.

Maybe literally like you’ve never met. Or maybe like they see you around but can never remember your name.

You lack the expertise.

You probably don’t lack the expertise. Otherwise you wouldn’t feel as outraged as you do about not being consulted. That said, consider this might be the case all the same.

You lack the context.

You may have the expertise. You might not have the context, though. And maybe the time it would take to give you the context isn’t worth the perceived value your consulting would deliver.

You lack the authority.

This certainly isn’t a prerequisite for being consulted. But being the boss awards you certain things—like being involved in decision making—and signing-off on decisions. If you have authority, perhaps you haven’t made it clear you expect sign-off. Or maybe you’re a little lacking in context or expertise. Remember, they probably just forgot to loop you in. Or…

You lack the bandwidth.

“We didn’t want to waste your time, and figured you had better things you could be doing anyway!” Consider that this is much more likely to happen if you feign busyness or make a show of how much unrelated responsibility you’ve otherwise accumulated. No problem should be too small for you engage with.

This is not happening.

They don’t like you.

Ouch. A fancy way to say this might be “You lack the political capital” but that’s dressing-up the #realtalk. People like working with people they like. If you’re difficult, pretentious, overly verbose, or self-involved, people will avoid consulting you on anything, even if you have literally all of the above—expertise, context, and bandwidth.

A combination of these reasons are used when someone subconsciously decides who to engage.

You have to earn it.

This is all pointing to a common truth: the cost of including you outweighs the benefit in doing so.

One way to solve this problem in an organization is to bring down the cost of including people in a decision making process. For example, consider making decisions in a public place like a chat room that everyone has access to. You may be surprised at how well this scales. Plus, there’s a record of how the decision was made so you can later evaluate how a good of a decision was made. This probably only works if you have a blame free culture, though—and you probably don’t.

We’ve all been on both sides of this fence and constantly have the opportunity to see others raged over this stuff. It happens daily.

I wrote this for me. And you too. As a way to remember: it’s not a big deal. What you’re feeling is the result of a basic human need. Be aware of when you feel this. They probably just didn’t even think to ask you.

And that’s okay.

How to Order at McDonald’s

Order a #2—it comes with two small cheeseburgers but ask for it without cheese.

Do a hybrid up-size by asking for a large drink but sticking with the regular-sized fries. Do not order your meal to-go. If you do, the minute you leave the building, your meal will be instantly cold and therefore inedible. While waiting for your credit card transaction to process, quickly scan the restaurant for the Coca Cola Freestyle machine. If you see one, ask the cashier to fill up your diet soda at the drive-through fountain. You don’t want to risk your diet soda being contaminated by other flavors. Don’t even consider ordering a sugary soda, do I need to remind you how many calories are in the fries alone (380 which is significantly more than just one of the mini hamburgers you’re going to get)?

Make a bet on how competent the server is. You asked for a cheeseburger without cheese. Are you going to get a regular hamburger in the brown and white paper, or a cheeseburger in the yellow and red wrapping with a special order tag taped to it which reads “no cheese”.

Monitor the tray next to the cash register and upon the last item being added to the tray, ask the cashier for picante sauce. Be very prompt, otherwise the cashier will desert you and you will not have time to wait for the sauce before your food chills. Be specific, don’t ask for salsa, the package label reads “picante”, and you’ll have much better luck asking for what’s on the label. If you ask for hot sauce, you’ll get a confused look and then be handed Tabasco sauce.

Choose a seat near the front registers as far away from the playplace as possible. You’ll be annoyed by the constant beeping of fry and burger timers, but it’s much better than the smell of children’s feet and less sticky too.

Unwrap your burger and remove one side of the bun. Add a single solid layer of fries and replace the bun. Take a bite. Add picante sauce along the edge you’ve taken a bite out of and repeat. Finish both burgers, then start on the fries. Squirt all remaining picante sauce in a pile on the tray paper. Dip the fries into the sauce like you would ketchup.

LightSwitch to Android via Azure, OData, Basic Authentication, SSL, and Odata4j

Prior to this experiment, I was developing an ASP.NET MVC app hosted in Azure to serve as a REST API for feeding data to my Android app. I was really not into ASP.NET MVC, so when I learned that LightSwitch 2012 apps produce OData services, I jumped at the chance to try to replace my “homemade” REST API with OData.


  • You have an active Azure subscription and SQL database hosted in Azure with Firewall rules set to where you can access the database from your local machine
  • You have Visual Studio 2012
  • You know how to create a LightSwitch project
  • You have a basic Android app (a Hello World one will do)

Here are the steps I took to complete this proof-of-concept.

  1. Create a new LightSwitch project in Visual Studio 2012. Attach to an external data source – your Azure database. Create some screens.

  2. On the properties of your LightSwitch project, set the Access Control to use Forms Authentication.

  3. Publish the app with the following settings:

     Application Type: Web
     Application Server Configuration: Windows Azure
     Subscription: Download and then import your credentials file.
     Service Configuration - Common: choose or create a new cloud service
     Service Configuration - Advanced:  set the Deployment Name to the name you want for the OData service, such as "MyAppData".
     Security Settings - Application Adminstrator: Create an application administrator and provide the credentials.
     Security Settings - HTTPS: Create a new self-signed certificate.
     Data Connections: Specify the user and admin connection strings.
  4. Test the LightSwitch application in IE (the LightSwitch app won’t run in Chrome – hopefully this will be fixed in the final version of VS 2012). You can find the URL to the application in your Azure Management Portal under Hosted Services. Select the item with the service name you provided and use the URL listed in the DNS name property. For staging, the URL will be something like

  5. Test the OData service by appending the name you set for the service: You will get a certificate error since you self-signed the certificate, but that’s okay (you can trust yourself). This should list the names of your entities in the form of an atom feed. You can also try accessing where ‘Movies’ is the name of one of your entity sets.

  6. Open AndroidManifest.xml in your Android app and make sure you declare the internet permission.

    <uses-permission android:name="android.permission.INTERNET" />

  7. Locate a recent build of the odata4j project (the current stable version does not support LightSwitch OData). I used the build from 8/6/2012 9:29:04 PM. Download odata4j-dist-0.7.0-SNAPSHOT-clientbundle.jar and add it to your Android project.

  8. Use the following code to connect to the OData service in one of your Android activities and get a list of entities:

     ODataConsumer c = ODataConsumers.newBuilder("")
                             OClientBehaviors.basicAuth("MyUsername", "MyPassword"),
     List<OEntity> lstMovieEntities = c.getEntities("Movies").execute().toList();
  9. Then, you can grab the entity properties using the following code:

     List<MyMovie> movies = new ArrayList<MyMovie>();
     for (OEntity movie : lstMovieEntities) {
         MyMovie m = new MyMovie();
         m.Title = movie.getProperty("Title").getValue().toString();
         m.Year = movie.getProperty("Year").getValue().toString();

Note: Azure SQL Tables can produce an OData service on their own, but I imagine setting up basic authentication for various users would be more difficult.

Why I’m Dumping Eclipse Forever

During college we were strongly encouraged to use Eclipse for Java development. Even then, I felt like Eclipse was difficult to use, so I chose to use JCreator. Or maybe I just wanted to rebel, not wanting to take suggestions from the same people who forced me to use vi for C++ development. I guess I didn’t have that appreciation that others got from memorizing vi commands and knowing that only a subset of people know how to use that text editor.

Fortunately, my career led me down the Microsoft path, so I never had to deal with Eclipse—until Android showed up.

Having an Android phone and being a software developer, of course I wanted to make apps for it. I decided to give Eclipse another try, maybe I had unfairly judged it in college, maybe it was better now, and all the tutorials were based on using Eclipse anyway. I developed the first version of Worth Watching using Eclipse and had relatively few problems with the IDE. As time went on, I spent more time developing the app in Eclipse and naturally there were software updates to Eclipse and the Android tools. I began to see more and more problems. First, when debugging on a real device, the Log Cat would frequently stop showing logs, especially when I cleared the log screen, no more logs would appear. With Android, you have folders for layouts with common filenames among the folders. When I had multiple files open in Eclipse that had the same filename, I could double-click on a file in the navigator, and it would bring the wrong file into view. For a while, when I selected the AVD Manager from the file menu, it would open the Android SDK Manager instead. At least a couple of times, my projects would out-of-nowhere fail to compile. This is what ultimately led me to give up on Eclipse.

I was so frustrated with Eclipse, that I decided to use a text editor and command line to do my development. Shortly after choosing the best text editor for the job—Sublime Text—I discovered IntelliJ’s Community Edition which supports Android development. After struggling with Eclipse for almost a year, I was continuously surprised by how easy IntelliJ was to use. Remember how my projects wouldn’t compile in Eclipse? Well, IntelliJ was more than happy to build them without any changes. I had experimented with a few Git plug-ins for Eclipse, but they were even harder to use than Eclipse was. IntelliJ has Git build-in AND it was extremely easy to figure out.

Everyone should dump Eclipse. Even if you can’t convince your team to switch, IntelliJ supports compatibility with Eclipse and even has an Export to Eclipse feature. If I’m ever locked into Eclipse because it’s the only IDE for a certain product, I will definitely go command-line only.

Farewell, Eclipse!

Nokia Lumia 900 vs. Nexus One

Things I Miss about My Nexus One

  • trackball - The trackball was especially handy for cursor positioning in text fields. I also liked that I could set the trackball light to flash when I had notifications.
  • search button - In-app search via hardware button. It’s going to take me awhile to get used to the Windows Phone search button always taking me to Bing.
  • tab keyboard button - The “tab” keyboard button was excellent for entering form data on the web.
  • Mint App - Luckily the Mint website is fully functional in the Windows Phone browser (parts of the website did not work on the Nexus One).
  • Remember the Milk - There are two decent app-substitutes that use the Remember the Milk API, but they are not good enough - I still find myself using the Remember the Milk full website instead.
  • Gmail App - Particularly the “undo archive” feature.
  • Alarm App - The default alarm app on the Nexus One would display how many minutes until an alarm would sound when you set an alarm. This saved me a few times when I accidentally set the alarm to PM instead of AM, because it was really obvious when it showed 20 hours until the alarm went off instead of 8.

Things I Love about My Lumia 900

  • large screen size - The Nexus One and iPhone screens look so miniature to me now. I don’t know how I ever survived with such a small screen. With the Lumia 900, It’s so much easier to use the on-screen keyboard and surf the web.
  • People Hub App - People Hub is my preferred method of posting to social networks. It was several weeks before I installed the Facebook app, and I only did so because I needed the messaging feature. Even now that I have it installed, I only use it for viewing messages and checking out events. And it does not get the privilege of being a tile on my home screen. I still haven’t installed the Twitter app.
  • edit search keywords - After using the search functionality on the Nexus One, the search box disappears. So if I had searched for “wikipedia tree” and then wanted to narrow the search or correct a typo in the search terms, change the keywords to “wikipedia treehouse”, I would have to type the whole phrase in a again. With the Lumia 900, I can easily edit the search terms and repeat a search.
  • text selection - I love being able to tap a word and it selects the whole word automatically. The cursor positioning is pretty nice too, but I don’t like the long delay for it to figure out that you want to place the cursor somewhere while you’re holding your finger on the screen.

One thing that drives me crazy—the smiley face symbol on the Lumia 900 keyboard is in the same location as the @ symbol was on the Nexus One, so I hit the smiley face symbol on accident all the time. They look really similar if you are just glancing at the keyboard.