diff --git a/bin/manifold b/bin/manifold index 9a6193e..96a884d 100755 --- a/bin/manifold +++ b/bin/manifold @@ -1,9 +1,10 @@ #!/usr/bin/env ruby # frozen_string_literal: true -lib_path = File.expand_path("../lib", Dir.pwd) -$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path) +lib_path = File.expand_path("../lib", __dir__) +puts lib_path +$LOAD_PATH.unshift lib_path -require "manifold/cli" +require "manifold" Manifold::CLI.start(ARGV) diff --git a/lib/manifold.rb b/lib/manifold.rb index f1dcd62..33598e3 100644 --- a/lib/manifold.rb +++ b/lib/manifold.rb @@ -3,6 +3,7 @@ require "pathname" require "thor" require "yaml" +require "logger" Dir[File.join(__dir__, "manifold", "**", "*.rb")].sort.each do |file| require file diff --git a/lib/manifold/cli.rb b/lib/manifold/cli.rb index 5eaed4d..2cd572d 100644 --- a/lib/manifold/cli.rb +++ b/lib/manifold/cli.rb @@ -4,21 +4,22 @@ module Manifold # CLI provides command line interface functionality # for creating and managing umbrella projects for data management. class CLI < Thor - attr_accessor :logger, :bq_service + attr_accessor :logger, :bq_service, :project - def initialize(*args, logger: Logger.new($stdout)) + def initialize(*args, logger: Logger.new($stdout), + project: Manifold::API::Project.new(config: DEFAULT_PROJECT_CONFIG)) super(*args) + self.project = project + self.bq_service = Services::BigQueryService.new(logger) self.logger = logger logger.level = Logger::INFO - - self.bq_service = Services::BigQueryService.new(logger) end - desc "init NAME", "Generate a new umbrella project for data management" - def init(name) - Manifold::API::Project.create(name) - logger.info "Created umbrella project '#{name}' with projects and vectors directories." + desc "init PROJECT_NAME", "Initializes a new project within the current directory" + def init + project.create + logger.info "Created new project manifold in #{project.directory}." end desc "vectors SUBCOMMAND ...ARGS", "Manage vectors" @@ -33,7 +34,7 @@ def initialize(*args, logger: Logger.new($stdout)) end desc "add VECTOR_NAME", "Add a new vector configuration" - def add(name, project: API::Project.new(File.basename(Dir.getwd))) + def add(name) vector = API::Vector.new(name, project: project) vector.add logger.info "Created vector configuration for '#{name}'." @@ -41,7 +42,7 @@ def add(name, project: API::Project.new(File.basename(Dir.getwd))) } desc "add WORKSPACE_NAME", "Add a new workspace to a project" - def add(name, project: API::Project.new(File.basename(Dir.getwd))) + def add(name) workspace = API::Workspace.new(name, project: project) workspace.add logger.info "Added workspace '#{name}' with tables and routines directories." diff --git a/lib/manifold/project/project.rb b/lib/manifold/project/project.rb index abc27ea..36b3807 100644 --- a/lib/manifold/project/project.rb +++ b/lib/manifold/project/project.rb @@ -4,17 +4,23 @@ module Manifold module API # Projects API class Project - attr_reader :name, :directory + DEFAULT_CONFIG = { + name: File.basename(Dir.getwd) + } - def initialize(name, directory: Pathname.pwd.join(name)) - self.name = name - self.directory = Pathname(directory) + attr_reader :config_path + + def initialize(config: Pathname.pwd.join("project.yaml")) + self.config_path = config + end + + def create + File.open(config_path, "w") { |file| file.write DEFAULT_CONFIG.to_yaml } + [workspaces_directory, vectors_directory].each(&:mkpath) end - def self.create(name, directory: Pathname.pwd.join(name)) - new(name, directory: directory).tap do |project| - [project.workspaces_directory, project.vectors_directory].each(&:mkpath) - end + def directory + Pathname.new(Dir.pwd) end def workspaces_directory @@ -25,9 +31,19 @@ def vectors_directory directory.join("vectors") end + def created? + File.exist? config_path + end + + def config + return nil unless created? + + @config ||= YAML.safe_load_file(config_path, permitted_classes: [Symbol]) + end + private - attr_writer :name, :directory + attr_writer :config_path end end end diff --git a/lib/manifold/project/workspace.rb b/lib/manifold/project/workspace.rb index 8ec385d..796872e 100644 --- a/lib/manifold/project/workspace.rb +++ b/lib/manifold/project/workspace.rb @@ -8,7 +8,7 @@ class Workspace DEFAULT_TEMPLATE_PATH = Pathname.pwd.join( "lib", "manifold", "templates", "workspace_template.yml" - ) + ).freeze def initialize(name, project:, template_path: DEFAULT_TEMPLATE_PATH) self.name = name diff --git a/spec/manifold/api/project_spec.rb b/spec/manifold/api/project_spec.rb index 62b764f..8e0fe3f 100644 --- a/spec/manifold/api/project_spec.rb +++ b/spec/manifold/api/project_spec.rb @@ -1,24 +1,16 @@ # frozen_string_literal: true -RSpec.describe Manifold::API::Project do +RSpec.describe(Manifold::API::Project) do include FakeFS::SpecHelpers - subject(:project) { described_class.new(name) } + subject(:project) { described_class.new(config: config) } - let(:name) { "wetland" } + let(:config) { Pathname.pwd.join("project.yaml") } - it { is_expected.to have_attributes(name: name) } + it { is_expected.to have_attributes(config_path: config) } - describe ".create" do - before { described_class.create(name) } - - it "creates the vectors directory" do - expect(project.vectors_directory).to be_directory - end - - it "creates the workspaces directory" do - expect(project.workspaces_directory).to be_directory - end + describe ".directory" do + it { expect(project.directory).to be_an_instance_of(Pathname) } end describe ".workspaces_directory" do @@ -29,19 +21,33 @@ it { expect(project.vectors_directory).to be_an_instance_of(Pathname) } end - context "with directory" do - subject(:project) { described_class.new(name, directory: directory) } + context "when not created" do + describe ".created?" do + it { expect(project.created?).to be false } + end + + describe ".config" do + it { expect(project.config).to be nil } + end + + describe ".create" do + before { project.create } - let(:directory) { Pathname.pwd.join("supplied_directory") } + it { expect(project.config_path).to be_file } + it { expect(project.vectors_directory).to be_directory } + it { expect(project.workspaces_directory).to be_directory } + end + end - it { is_expected.to have_attributes(directory: directory) } + context "when created" do + before { project.create } - it "uses it as the base for the vectors directory" do - expect(project.vectors_directory).to eq directory.join("vectors") + describe ".created?" do + it { expect(project.created?).to be true } end - it "uses it as the base for the workspaces directory" do - expect(project.workspaces_directory).to eq directory.join("workspaces") + describe ".config" do + it { expect(project.config).to be_a(Hash) } end end end diff --git a/spec/manifold/cli_spec.rb b/spec/manifold/cli_spec.rb index 85e6cbc..15ba210 100644 --- a/spec/manifold/cli_spec.rb +++ b/spec/manifold/cli_spec.rb @@ -2,97 +2,60 @@ RSpec.describe Manifold::CLI do include FakeFS::SpecHelpers + subject(:cli) { described_class.new(logger: logger, project: project) } - let(:null_logger) { instance_double(Logger) } - let(:mock_project) { instance_double(Manifold::API::Project) } - let(:mock_workspace) { instance_double(Manifold::API::Workspace) } - let(:mock_vector) { instance_double(Manifold::API::Vector) } + let(:project) { Manifold::API::Project.new } + let(:logger) { Logger.new(IO::NULL) } - before do - allow(Manifold::API::Project).to receive(:new).and_return(mock_project) - allow(Manifold::API::Workspace).to receive(:new).and_return(mock_workspace) - allow(Manifold::API::Vector).to receive(:new).and_return(mock_vector) - allow(null_logger).to receive(:info) - allow(null_logger).to receive(:level=) - end - - describe "#init" do - subject(:cli) { described_class.new(logger: null_logger) } - - let(:project_name) { "wetland" } - - context "when initializing a new project" do - before do - allow(Manifold::API::Project).to receive(:create).and_return(mock_project) - end + # let(:null_logger) { instance_double(Logger) } + # let(:mock_project) { instance_double(Manifold::API::Project) } + # let(:mock_workspace) { instance_double(Manifold::API::Workspace) } + # let(:mock_vector) { instance_double(Manifold::API::Vector) } - it "creates a new project through the API" do - cli.init(project_name) - expect(Manifold::API::Project).to have_received(:create).with(project_name) - end + # before do + # allow(Manifold::API::Project).to receive(:new).and_return(mock_project) + # allow(Manifold::API::Workspace).to receive(:new).and_return(mock_workspace) + # allow(Manifold::API::Vector).to receive(:new).and_return(mock_vector) + # allow(null_logger).to receive(:info) + # allow(null_logger).to receive(:level=) + # end - it "logs the project creation" do - cli.init(project_name) - expect(null_logger).to have_received(:info) - .with("Created umbrella project '#{project_name}' with projects and vectors directories.") - end + describe "#init" do + before do + allow(project).to receive(:create) + allow(logger).to receive(:info) + cli.init end + + it { expect(project).to have_received(:create) } + it { expect(logger).to have_received(:info) } end describe "#add" do - subject(:cli) { described_class.new(logger: null_logger) } - - let(:workspace_name) { "Commerce" } - - context "when adding a workspace" do - before do - allow(mock_workspace).to receive(:add) - cli.add(workspace_name) - end - - it "instantiates a new workspace through the API" do - expect(Manifold::API::Workspace).to have_received(:new) - .with(workspace_name, project: mock_project) - end + let(:name) { "workspace_name" } + let(:workspace) { Manifold::API::Workspace.new(name, project: project) } - it "adds the workspace through the API" do - expect(mock_workspace).to have_received(:add) - end - - it "logs the workspace creation" do - expect(null_logger).to have_received(:info) - .with("Added workspace '#{workspace_name}' with tables and routines directories.") - end + before do + allow(workspace).to receive(:add) + cli.add(name) end - end - describe "vectors#add" do - subject(:cli) do - subcommands = described_class.new.class.subcommand_classes - subcommands["vectors"].new(logger: null_logger) - end + it { expect(workspace).to have_received(:add) } + it { expect(logger).to have_received(:info) } + end - let(:vector_name) { "page" } + describe "vector subcommand" do + let(:name) { "vector_name" } + let(:vector) { Manifold::API::Vector.new(name, project: project) } - context "when adding a vector" do + describe "#add" do before do - allow(mock_vector).to receive(:add) - cli.add(vector_name) - end - - it "instantiates a new vector through the API" do - expect(Manifold::API::Vector).to have_received(:new) - .with(vector_name, project: mock_project) + allow(vector).to receive(:add) + cli.add(name) end - it "adds the vector through the API" do - expect(mock_vector).to have_received(:add) - end - - it "logs the vector creation" do - expect(null_logger).to have_received(:info) - .with("Created vector configuration for '#{vector_name}'.") - end + it { expect(vector).to have_received(:add) } + it { expect(logger).to have_received(:info) } end end end