Request for feedback on Vials (wrappers for mix tasks)

EDIT: I forgot to link the repo, ha: GitHub - sodapopcan/vials: Wrappers for mix tasks

This project is not quite ready for prime-time but I wanted to solicit some early feedback to see if it’s something people would want to use.

This started out with the idea of being able to manipulate the output of phx_new but I ended up making it a more general wrapper around mix tasks.

A “vial” is a file that is named after a mix task and saved in ~/vials (this is configurable).

Here is one for mix phx.new:

# ~/vials/phx.new.ex

defmodule SomeModuleName.ItDoesNot.Matter.WhatItsCalled do
  use Vials

  base_path @target

  remove "priv/static/favicon.ico"

  remove_comments() # remove comments from all .ex and .exs file.
                    # remove_comments/1 with accept a filename or list of filenames.

  add_dep {:some_dep, "~> 0.0.1"}

  if @opts[:binary_id] do
    create "lib/#{@target}/schema.ex", """
    defmodule #{Macro.camelize(@target)}.Schema do
      defmacro __using__(_) do
        quote do
          use Ecto.Schema, warn: false

          @primary_key {:id, :binary_id, autogenerate: true}
          @foreign_key_type :binary_id
        end
      end
    end
    """
  end

  edit "lib/#{@target}/router.ex", fn contents ->
    # Crudely manipulate the contents of `router.ex` with Elixir's standard library.
  end
end

The next step is to add a few more DSL functions (like move) and then some helper functions for editing files. I’m very new to AST manipulation and have been getting comfortable with Sourceror. This actually works right now:

def SomeMod do
  use Vials

  create "some_file.ex" do
    defmodule Foo do
      def bar do
        "baz"
      end
    end
  end
end

…though there is no way to use the injected module attributes. I’m getting there!

Anyway, I just wanted some early feedback. I’m also open to another name. I don’t mind Vials but there is already a project on hexpm called Vial (singular), I’m not sure how active it is.

I’m currently in the midst of refactoring so I’m not too open to PRs atm but I would really appreciate feedback and generally what the appetite for something like this is.

There’s more info in the README.

Thanks!

8 Likes

This seems very nice to me. Less burden to keep custom generators up-to-date with upstream and possibly working with different versions of Phoenix.

Pre-mature note: a place to share Vials would be nice :slight_smile:

1 Like

That’s the idea although isn’t possible in the current version. I wanted to keep a more transparent way of calling a default vial but am planning on something like

$ vials -f phx_1.6.ex phx.new project`

I thought it made more sense to look for a default because, at least as I understand it, you can only have one version of phx_new installed. Then if you want customizations for different scenarios, you can code around it with conditionals.

I think need to put that this is built with generators in mind front-and-centre. That’s all I’m focused on at the moment and isn’t super clear in the “wrappers for mix tasks” tagline.

Thanks for feedback!

Per machine, in a given time frame.

But the line to remove the favicon remains the same for many Phoenix versions. So the Vials-plug will function on multiple versions of Phoenix and no custom generators have to be made per version which is a big win of using Vials over customized generators!

Ah right, multiple machines, of course.

I actually wasn’t aware of custom generators since my primary focus of this is for phx_new then I thought, “ehn why not just make this work for any mix task.” Is it possible to have a custom generator for phx_new? I asked about this a while ago but I can’t find the thread. But otherwise what you say is true that it can work across multiple versions and you can just do some tweaks when things change in the generators!

If you fork whole thing and make adjustments, you have your own phx_new (or customized_phx_new). But then you need to keep it aligned with upstream, hence every Phoenix version needs your attention as you need to update your fork.

Vials takes this all away :slight_smile:

So this is to personalize, individualize, adapt, tweak the result of a mix task? "If so, “aftercare” comes to my mind.

There are always certain steps of changes that everyone undertakes after creating a new phoenix app (e.g. change password in config/dev.exs for the db). I would be interested in other people’s vials, too.

1 Like

Yeah, I’m really just writing it with generators in mind, though theoretically you could use it to wrap any mix task. I think once I cut a 0.1.0 release I’ll make an new “Introducing” thread and make that clearer as I don’t think “wrappers for mix tasks” is particularly appealing at a glance.

I like that name but I think I’m going to stick with Vials as I like being able to have a name for the files that is related to the name. That would be hard with aftercare.

I was talking about with with @BartOtten. I’m not sure exactly how to handle that yet. I’m a little wary of accepting PRs in the project as it could get out of hand, but if this project gets any real use then there should definitely be a solution for sharing vials. I’m open to suggestions, of course!

1 Like

After reading the comments it surely is a tool with a lot of potential!

The thing that would make it real interesting would be to be able to access the task code before/after it is executed? The only sad thing about this is that the classic mix task is just a run function and it has side effects in it.

Maybe an interesting approach would be to build a data structure at the end of the task and have some kind of “server” execute everything afterwards? this would open possibilities to mix and pipe these kind of tasks.

1 Like

That’s interesting. I’m currently just focused on a DSL, well, basically for creating and manipulating source files but I’m open to other use-cases

Can you give an example of what you’re describing? Like metaprogramming a mix task itself?

Otherwise currently Vial files work like you describe in that they just build up a list of messages which get processed by a “Runner”. Currently it uses an Agent to store up the messages but I’m likely going to refactor to use a module attribute—the Agent got me going quicker as I didn’t have to figure out how to store anonymous functions in a module attribute but I think I have a solution. I’m also in the midst of refactoring to clean some stuff up (namely the guys of add_dep shouldn’t be in the DSL module.

1 Like

Sadly it won’t be either easy or smart to manipulate classical mix tasks code, so modifying them is out of the question.

It seems you are already operating with the things I described, basically I was thinking about a system that works exactly like Phoenix Plugs, you have a pipeline and you have your data structure that you can modify and pass it on.

The use-case would be to make generic pluggable Vials, an example would be using your phx_new vial but having my custom spin on some things, without executing 10 different tasks, but just one.

I didn’t think that’s what you were saying but I wasn’t sure :sweat_smile:

That’s the idea! I was thinking of having “recipes” or “extensions” or something where you can do something like:

  recipes [:utc_datetime_usec]

Outside of this thread, @BartOtten suggested making them composable which is also a possibility. I’m thinking on it happy for more suggestions.

I might offer some of these out of the box but also want to provide a DSL to make custom changes a little easier, though part of that is just to help myself level up my metaprogramming.

1 Like

The only thing I find it a very hard fit with a composable system like this is the file modification.

For example I want phx_new to generate a modified version of the layout with perhaps bootstrap alerts, in this case you will have to fully rewrite the template, witch might not work well on new versions of phoenix.

Didn’t see this thread getting bigger. Here the content (slight tweaked) of my latest PM to @sodapopcan

I see what you’re and yes, good point. That would be tricky to get right there is always the chance of referencing a part of the file that has been removed (when it comes to composing a third party’s code).

I’m not super worried about this just yet as I don’t even know how much use this is going to get and people can just share certain actions and tweak them. But I’ll keep this in mind when expanding the DSL.

I’m thinking something along the lines of (but by no means exactly like):

edit "lib/#{@target}_web/router.ex", fn contents ->
  contents
  |> insert_after_line_containing "PageController" do
    live "/", HomeLive, :index
  end
  |> remove_line_containing("PageController")
end

So that would obviously break if you were to plug in another operation that references PageController.

For the first release I’m just going to focus on simple string manipulation with maybe a couple of helpers. It also looks like there is some good generalized code rewriting tools coming down the pipes!

But ya, it’s definitely something to think about.

But @BartOtten yes I definitely want to make it extendable and avoid adding specific task-related code the project. I’ll take a lot at how you did it with Routex.

1 Like