diff --git a/README.md b/README.md index cd3dd00..197f90c 100644 --- a/README.md +++ b/README.md @@ -121,10 +121,10 @@ View the analysis results in a browser. This gem is specifically designed to analyze large applications with a modular monolithic architecture. It allows users to categorize each analyzed file into specified modules directly through the web interface. - `--definition-dir` Specifies the directory where the analysis results are stored. -- `--module-store-path` Designates a path to save the results that include details on which module each file belongs to. If this option is not specified, the results will be temporarily stored in a default temporary file. +- `--metadata` Designates a path to save the results that include details on which module each file belongs to. If this option is not specified, the results will be temporarily stored in a default temporary file. ```sh -bundle exec diver_down_web --definition-dir tmp/diver_down --module-store-path tmp/module_store.yml +bundle exec diver_down_web --definition-dir tmp/diver_down --metadata tmp/metadata.yml open http://localhost:8080 ``` @@ -151,7 +151,7 @@ $ pnpm run dev # Start server for backend $ bundle install -$ DIVER_DOWN_DIR=/path/to/definitions_dir DIVER_DOWN_MODULE_STORE=/path/to/module_store.yml bundle exec puma +$ DIVER_DOWN_DIR=/path/to/definitions_dir DIVER_DOWN_METADATA=/path/to/metadata.yml bundle exec puma ``` ## Contributing diff --git a/config.ru b/config.ru index 4e9a0d3..7a4ec58 100644 --- a/config.ru +++ b/config.ru @@ -6,9 +6,9 @@ require 'rack/reloader' require 'rack/contrib' definition_dir = ENV.fetch('DIVER_DOWN_DIR') -module_store = DiverDown::Web::ModuleStore.new(ENV.fetch('DIVER_DOWN_MODULE_STORE')) +metadata = DiverDown::Web::Metadata.new(ENV.fetch('DIVER_DOWN_METADATA')) use Rack::Reloader use Rack::JSONBodyParser use DiverDown::Web::DevServerMiddleware, host: 'localhost', port: 5173 # Proxy to vite(pnpm run dev) -run DiverDown::Web.new(definition_dir:, module_store:) +run DiverDown::Web.new(definition_dir:, metadata:) diff --git a/exe/diver_down_web b/exe/diver_down_web index 1601484..b223e2b 100755 --- a/exe/diver_down_web +++ b/exe/diver_down_web @@ -14,7 +14,7 @@ option_parser = OptionParser.new do |opts| Usage: diver_down_web [options] Example: - diver_down_web --definition-dir /path/to/definitions --module-store /path/to/module_store.yml + diver_down_web --definition-dir /path/to/definitions --metadata /path/to/metadata.yml Options: BANNER @@ -23,8 +23,8 @@ option_parser = OptionParser.new do |opts| options[:definition_dir] = path end - opts.on('--module-store PATH', 'Path to the module store') do |path| - options[:module_store] = path + opts.on('--metadata PATH', 'Path to the metadata.yml') do |path| + options[:metadata] = path end end option_parser.parse!(ARGV) @@ -39,7 +39,7 @@ end app = Rack::JSONBodyParser.new( DiverDown::Web.new( definition_dir: options.fetch(:definition_dir), - module_store: DiverDown::Web::ModuleStore.new(options[:module_store] || Tempfile.new(['module_store', '.yaml'])) + metadata: DiverDown::Web::Metadata.new(options[:metadata] || Tempfile.new(['metadata', '.yaml']).path) ) ) diff --git a/lib/diver_down/web.rb b/lib/diver_down/web.rb index a922eb7..585bd34 100644 --- a/lib/diver_down/web.rb +++ b/lib/diver_down/web.rb @@ -8,10 +8,11 @@ class Web WEB_DIR = File.expand_path('../../web', __dir__) require 'diver_down/web/action' + require 'diver_down/web/alias_store' require 'diver_down/web/definition_to_dot' require 'diver_down/web/definition_enumerator' require 'diver_down/web/bit_id' - require 'diver_down/web/module_store' + require 'diver_down/web/metadata' require 'diver_down/web/indented_string_io' require 'diver_down/web/definition_store' require 'diver_down/web/definition_loader' @@ -20,11 +21,11 @@ class Web autoload :DevServerMiddleware, 'diver_down/web/dev_server_middleware' # @param definition_dir [String] - # @param module_store [DiverDown::ModuleStore] + # @param metadata [DiverDown::Web::Metadata] # @param store [DiverDown::Web::DefinitionStore] - def initialize(definition_dir:, module_store:, store: DiverDown::Web::DefinitionStore.new) + def initialize(definition_dir:, metadata:, store: DiverDown::Web::DefinitionStore.new) @store = store - @module_store = module_store + @metadata = metadata @files_server = Rack::Files.new(File.join(WEB_DIR)) definition_files = ::Dir["#{definition_dir}/**/*.{yml,yaml,msgpack,json}"].sort @@ -37,7 +38,7 @@ def initialize(definition_dir:, module_store:, store: DiverDown::Web::Definition # @return [Array[Integer, Hash, Array]] def call(env) request = Rack::Request.new(env) - action = DiverDown::Web::Action.new(store: @store, module_store: @module_store, request:) + action = DiverDown::Web::Action.new(store: @store, metadata: @metadata, request:) case [request.request_method, request.path] in ['GET', %r{\A/api/definitions\.json\z}] diff --git a/lib/diver_down/web/action.rb b/lib/diver_down/web/action.rb index 646cb60..bc6ea8f 100644 --- a/lib/diver_down/web/action.rb +++ b/lib/diver_down/web/action.rb @@ -13,11 +13,11 @@ class Action ) # @param store [DiverDown::Definition::Store] - # @param module_store [DiverDown::Web::ModuleStore] + # @param metadata [DiverDown::Web::Metadata] # @param request [Rack::Request] - def initialize(store:, module_store:, request:) + def initialize(store:, metadata:, request:) @store = store - @module_store = module_store + @metadata = metadata @request = request end @@ -33,14 +33,16 @@ def sources end # rubocop:enable Style/HashEachMethods - classified_sources_count = source_names.count { @module_store.classified?(_1) } + classified_sources_count = source_names.count { @metadata.source(_1).modules? } json( sources: source_names.sort.map do |source_name| + source = @metadata.source(source_name) + { source_name:, - memo: @module_store.get_memo(source_name), - modules: @module_store.get_modules(source_name).map do |module_name| + memo: source.memo, + modules: source.modules.map do |module_name| { module_name: } end, } @@ -57,7 +59,7 @@ def modules # rubocop:disable Style/HashEachMethods @store.each do |_, definition| definition.sources.each do |source| - modules = @module_store.get_modules(source.source_name) + modules = @metadata.source(source.source_name).modules module_set.add(modules) unless modules.empty? end end @@ -84,7 +86,7 @@ def module(module_names) # rubocop:disable Style/HashEachMethods @store.each do |_, definition| definition.sources.each do |source| - source_module_names = @module_store.get_modules(source.source_name) + source_module_names = @metadata.source(source.source_name).modules next unless source_module_names[0..module_names.size - 1] == module_names @@ -109,7 +111,7 @@ def module(module_names) sources: source_names.sort.map do |source_name| { source_name:, - memo: @module_store.get_memo(source_name), + memo: @metadata.source(source_name).memo, } end, related_definitions: related_definitions.map do |definition| @@ -139,7 +141,7 @@ def definitions(page:, per:, title:, source:, definition_group:) definition_group: definition.definition_group, title: definition.title, sources_count: definition.sources.size, - unclassified_sources_count: definition.sources.reject { @module_store.classified?(_1.source_name) }.size, + unclassified_sources_count: definition.sources.reject { @metadata.source(_1.source_name).modules? }.size, } end, pagination: pagination.to_h @@ -187,18 +189,18 @@ def combine_definitions(bit_id, compound, concentrate, only_module) end if definition - definition_to_dot = DiverDown::Web::DefinitionToDot.new(definition, @module_store, compound:, concentrate:, only_module:) + definition_to_dot = DiverDown::Web::DefinitionToDot.new(definition, @metadata, compound:, concentrate:, only_module:) json( titles:, bit_id: DiverDown::Web::BitId.ids_to_bit_id(valid_ids).to_s, dot: definition_to_dot.to_s, - dot_metadata: definition_to_dot.metadata, + dot_metadata: definition_to_dot.dot_metadata, sources: definition.sources.map do { source_name: _1.source_name, - memo: @module_store.get_memo(_1.source_name), - modules: @module_store.get_modules(_1.source_name).map do |module_name| + memo: @metadata.source(_1.source_name).memo, + modules: @metadata.source(_1.source_name).modules.map do |module_name| { module_name: } end, } @@ -249,12 +251,12 @@ def source(source_name) [] else source = DiverDown::Definition::Source.combine(*found_sources) - @module_store.get_modules(source.source_name) + @metadata.source(source.source_name).modules end json( source_name:, - memo: @module_store.get_memo(source_name), + memo: @metadata.source(source_name).memo, modules: module_names.map do { module_name: _1 } end, @@ -291,8 +293,8 @@ def set_modules(source_name, modules) end if found_source - @module_store.set_modules(source_name, modules) - @module_store.flush + @metadata.source(source_name).modules = modules + @metadata.flush json({}) else @@ -312,8 +314,8 @@ def set_memo(source_name, memo) end if found_source - @module_store.set_memo(source_name, memo) - @module_store.flush + @metadata.source(source_name).memo = memo + @metadata.flush json({}) else diff --git a/lib/diver_down/web/definition_to_dot.rb b/lib/diver_down/web/definition_to_dot.rb index 95c1c07..d4c1ad3 100644 --- a/lib/diver_down/web/definition_to_dot.rb +++ b/lib/diver_down/web/definition_to_dot.rb @@ -12,8 +12,8 @@ class DefinitionToDot # Between modules is prominently distanced MODULE_MINLEN = 3 - class MetadataStore - Metadata = Data.define(:id, :type, :data, :module_store) do + class DotMetadataStore + DotMetadata = Data.define(:id, :type, :data, :metadata) do # @return [Hash] def to_h case type @@ -31,7 +31,7 @@ def to_h private def source_to_h - modules = module_store.get_modules(data.source_name).map do + modules = metadata.source(data.source_name).modules.map do { module_name: _1, } @@ -41,7 +41,7 @@ def source_to_h id:, type: 'source', source_name: data.source_name, - memo: module_store.get_memo(data.source_name), + memo: metadata.source(data.source_name).memo, modules:, } end @@ -77,11 +77,11 @@ def module_to_h end end - def initialize(module_store) + def initialize(metadata) @prefix = 'graph_' - @module_store = module_store + @metadata = metadata - # Hash{ id => Metadata } + # Hash{ id => DotMetadata } @to_h = {} end @@ -89,13 +89,13 @@ def initialize(module_store) # @param record [DiverDown::Definition::Source] # @return [String] def issue_source_id(source) - build_metadata_and_return_id(:source, source) + build_dot_metadata_and_return_id(:source, source) end # @param dependency [DiverDown::Definition::Dependency] # @return [String] def issue_dependency_id(dependency) - build_metadata_and_return_id(:dependency, [dependency]) + build_dot_metadata_and_return_id(:dependency, [dependency]) end # @param module_names [Array] @@ -106,17 +106,17 @@ def issue_modules_id(module_names) if issued_metadata issued_metadata.id else - build_metadata_and_return_id(:module, module_names) + build_dot_metadata_and_return_id(:module, module_names) end end # @param id [String] # @param dependency [DiverDown::Definition::Dependency] def append_dependency(id, dependency) - metadata = @to_h.fetch(id) - dependencies = metadata.data + dot_metadata = @to_h.fetch(id) + dependencies = dot_metadata.data combined_dependencies = DiverDown::Definition::Dependency.combine(*dependencies, dependency) - metadata.data.replace(combined_dependencies) + dot_metadata.data.replace(combined_dependencies) end # @return [Array] @@ -126,10 +126,10 @@ def to_a private - def build_metadata_and_return_id(type, data) + def build_dot_metadata_and_return_id(type, data) id = "#{@prefix}#{length + 1}" - metadata = Metadata.new(id:, type:, data:, module_store: @module_store) - @to_h[id] = metadata + dot_metadata = DotMetadata.new(id:, type:, data:, metadata: @metadata) + @to_h[id] = dot_metadata id end @@ -140,24 +140,24 @@ def length end # @param definition [DiverDown::Definition] - # @param module_store [DiverDown::ModuleStore] + # @param metadata [DiverDown::Web::Metadata] # @param compound [Boolean] # @param concentrate [Boolean] https://graphviz.org/docs/attrs/concentrate/ - def initialize(definition, module_store, compound: false, concentrate: false, only_module: false) + def initialize(definition, metadata, compound: false, concentrate: false, only_module: false) @definition = definition - @module_store = module_store + @metadata = metadata @io = DiverDown::Web::IndentedStringIo.new @indent = 0 @compound = compound || only_module # When only-module is enabled, dependencies between modules are displayed as compound. @compound_map = Hash.new { |h, k| h[k] = {} } # Hash{ ltail => Hash{ lhead => issued id } } @concentrate = concentrate @only_module = only_module - @metadata_store = MetadataStore.new(module_store) + @dot_metadata_store = DotMetadataStore.new(metadata) end # @return [Array] - def metadata - @metadata_store.to_a + def dot_metadata + @dot_metadata_store.to_a end # @return [String] @@ -179,18 +179,18 @@ def to_s private - attr_reader :definition, :module_store, :io + attr_reader :definition, :metadata, :io def render_only_modules # Hash{ from_module => { to_module => Array } } dependency_map = Hash.new { |h, k| h[k] = Hash.new { |hi, ki| hi[ki] = [] } } definition.sources.sort_by(&:source_name).each do |source| - source_modules = module_store.get_modules(source.source_name) + source_modules = metadata.source(source.source_name).modules next if source_modules.empty? source.dependencies.each do |dependency| - dependency_modules = module_store.get_modules(dependency.source_name) + dependency_modules = metadata.source(dependency.source_name).modules next if dependency_modules.empty? dependency_map[source_modules][dependency_modules].push(dependency) @@ -215,9 +215,9 @@ def render_only_modules io.puts %(subgraph "#{escape_quote(module_label(module_names))}" {) io.indented do - io.puts %(id="#{@metadata_store.issue_modules_id(module_names)}") + io.puts %(id="#{@dot_metadata_store.issue_modules_id(module_names)}") io.puts %(label="#{escape_quote(module_name)}") - io.puts %("#{escape_quote(module_name)}" #{build_attributes(label: module_name, id: @metadata_store.issue_modules_id(module_names))}) + io.puts %("#{escape_quote(module_name)}" #{build_attributes(label: module_name, id: @dot_metadata_store.issue_modules_id(module_names))}) next_proc&.call end @@ -245,11 +245,11 @@ def render_only_modules # Add the dependency to the edge of the compound if @compound_map[ltail].include?(lhead) compound_id = @compound_map[ltail][lhead] - @metadata_store.append_dependency(compound_id, _1) + @dot_metadata_store.append_dependency(compound_id, _1) next end - compound_id = @metadata_store.issue_dependency_id(_1) + compound_id = @dot_metadata_store.issue_dependency_id(_1) @compound_map[ltail][lhead] = compound_id attributes.merge!( @@ -269,7 +269,7 @@ def render_only_modules def render_sources by_modules = definition.sources.group_by do |source| - module_store.get_modules(source.source_name) + metadata.source(source.source_name).modules end # Remove duplicated prefix modules @@ -297,7 +297,7 @@ def render_sources io.puts %(subgraph "#{escape_quote(module_label(module_names))}" {) io.indented do - io.puts %(id="#{@metadata_store.issue_modules_id(module_names)}") + io.puts %(id="#{@dot_metadata_store.issue_modules_id(module_names)}") io.puts %(label="#{escape_quote(module_name)}") sources = (by_modules[module_names] || []).sort_by(&:source_name) @@ -321,14 +321,14 @@ def render_sources end def insert_source(source) - io.puts %("#{escape_quote(source.source_name)}" #{build_attributes(label: source.source_name, id: @metadata_store.issue_source_id(source))}) + io.puts %("#{escape_quote(source.source_name)}" #{build_attributes(label: source.source_name, id: @dot_metadata_store.issue_source_id(source))}) end def insert_dependencies(source) source.dependencies.each do attributes = {} - ltail = module_label(*module_store.get_modules(source.source_name)) - lhead = module_label(*module_store.get_modules(_1.source_name)) + ltail = module_label(*metadata.source(source.source_name).modules) + lhead = module_label(*metadata.source(_1.source_name).modules) if @compound && (ltail || lhead) # Rendering of dependencies between modules is done only once @@ -338,11 +338,11 @@ def insert_dependencies(source) # Add the dependency to the edge of the compound if between_modules && @compound_map[ltail].include?(lhead) compound_id = @compound_map[ltail][lhead] - @metadata_store.append_dependency(compound_id, _1) + @dot_metadata_store.append_dependency(compound_id, _1) next end - compound_id = @metadata_store.issue_dependency_id(_1) + compound_id = @dot_metadata_store.issue_dependency_id(_1) @compound_map[ltail][lhead] = compound_id attributes.merge!( @@ -353,7 +353,7 @@ def insert_dependencies(source) ) else attributes.merge!( - id: @metadata_store.issue_dependency_id(_1) + id: @dot_metadata_store.issue_dependency_id(_1) ) end diff --git a/lib/diver_down/web/metadata.rb b/lib/diver_down/web/metadata.rb new file mode 100644 index 0000000..7de52bf --- /dev/null +++ b/lib/diver_down/web/metadata.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module DiverDown + class Web + class Metadata + require 'diver_down/web/metadata/source_metadata' + + # @param path [String] + def initialize(path) + @path = path + load + end + + # @param source_name [String] + # @return [DiverDown::Web::Metadata::SourceMetadata] + def source(source_name) + @source_map[source_name] + end + + # @return [Hash] + def to_h + source_names = @source_map.keys.sort + sources = source_names.to_h { [_1, source(_1).to_h] } + + { + sources:, + } + end + + # Write store to file + # @return [void] + def flush + File.write(@path, to_h.to_yaml) + end + + private + + def load + @source_map = Hash.new { |h, source_name| h[source_name] = DiverDown::Web::Metadata::SourceMetadata.new } + + begin + loaded = YAML.load_file(@path) + + return if loaded.nil? + + # NOTE: This is for backward compatibility. It will be removed in the future. + sources = loaded[:sources] || loaded + + sources.each do |source_name, source_hash| + @source_map[source_name].memo = source_hash[:memo] if source_hash[:memo] + @source_map[source_name].modules = source_hash[:modules] if source_hash[:modules] + end + rescue StandardError + # Ignore error + end + end + end + end +end diff --git a/lib/diver_down/web/metadata/source_metadata.rb b/lib/diver_down/web/metadata/source_metadata.rb new file mode 100644 index 0000000..3b5c61a --- /dev/null +++ b/lib/diver_down/web/metadata/source_metadata.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module DiverDown + class Web + class Metadata + class SourceMetadata + BLANK_MEMO = '' + BLANK_RE = /\A\s*\z/ + BLANK_ARRAY = [].freeze + private_constant(:BLANK_MEMO) + private_constant(:BLANK_RE) + private_constant(:BLANK_ARRAY) + + attr_reader :memo, :modules + + # @param source_name [String] + def initialize(memo: BLANK_MEMO, modules: BLANK_ARRAY) + @memo = memo + @modules = modules + end + + # @param memo [String] + # @return [void] + def memo=(memo) + @memo = memo.strip + end + + # @param module_names [Array] + # @return [void] + def modules=(module_names) + @modules = module_names.reject do + BLANK_RE.match?(_1) + end.freeze + end + + # @return [Boolean] + def modules? + @modules.any? + end + + # @return [Hash] + def to_h + hash = {} + + hash[:memo] = memo unless memo == BLANK_MEMO + hash[:modules] = modules unless modules.empty? + + hash + end + + private + + def by_source_name(source_name) + @store[source_name] ||= {} + end + end + end + end +end diff --git a/lib/diver_down/web/module_store.rb b/lib/diver_down/web/module_store.rb deleted file mode 100644 index 6875ab0..0000000 --- a/lib/diver_down/web/module_store.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -require 'yaml' - -module DiverDown - class Web - class ModuleStore - BLANK_ARRAY = [].freeze - BLANK_MEMO = '' - BLANK_RE = /\A\s*\z/ - - private_constant(:BLANK_RE) - - def initialize(path) - @path = path - @store = load - end - - # @param source_name [String] - # @param module_names [Array] - def set_modules(source_name, module_names) - source = (@store[source_name] ||= {}) - - source[:modules] = module_names.dup.reject do - BLANK_RE.match?(_1) - end.freeze - end - - # @param source_name [String] - # @return [Array] - def get_modules(source_name) - return BLANK_ARRAY unless @store.key?(source_name) - - @store[source_name][:modules] || BLANK_ARRAY - end - - # @param source_name [String] - # @param memo [String] - # @return [void] - def set_memo(source_name, memo) - source = (@store[source_name] ||= {}) - source[:memo] = memo.strip - end - - # @param source_name [String] - # @return [String] - def get_memo(source_name) - return BLANK_MEMO unless @store.key?(source_name) - - @store[source_name][:memo] || BLANK_MEMO - end - - # @param source_name [String] - # @return [Boolean] - def classified?(source_name) - get_modules(source_name).any? - end - - # @return [Hash] - def to_h - sorted_store = {} - - @store.keys.sort.each do |key| - sorted_store[key] = @store[key] - end - - sorted_store - end - - # Write store to file - # @return [void] - def flush - File.write(@path, to_h.to_yaml) - end - - private - - def load - store = {} - - begin - loaded = YAML.load_file(@path) - store.merge!(loaded) if loaded - rescue StandardError - # Ignore error - end - - store - end - end - end -end diff --git a/spec/diver_down/web/definition_to_dot_spec.rb b/spec/diver_down/web/definition_to_dot_spec.rb index 5d5da7b..02fa5fe 100644 --- a/spec/diver_down/web/definition_to_dot_spec.rb +++ b/spec/diver_down/web/definition_to_dot_spec.rb @@ -11,9 +11,9 @@ def build_definition(title: 'title', sources: []) DiverDown::Definition.new(title:, sources: definition_sources) end - let(:module_store) do + let(:metadata) do path = Tempfile.new(['test', '.yaml']).path - DiverDown::Web::ModuleStore.new(path) + DiverDown::Web::Metadata.new(path) end context 'when definition is blank' do @@ -22,7 +22,7 @@ def build_definition(title: 'title', sources: []) title: 'title' ) - expect(described_class.new(definition, module_store).to_s).to eq(<<~DOT) + expect(described_class.new(definition, metadata).to_s).to eq(<<~DOT) strict digraph "title" { } DOT @@ -35,7 +35,7 @@ def build_definition(title: 'title', sources: []) title: '"title"' ) - expect(described_class.new(definition, module_store).to_s).to eq(<<~DOT) + expect(described_class.new(definition, metadata).to_s).to eq(<<~DOT) strict digraph "\\"title\\"" { } DOT @@ -53,16 +53,16 @@ def build_definition(title: 'title', sources: []) ] ) - module_store.set_memo('a.rb', 'memo') + metadata.source('a.rb').memo = 'memo' - instance = described_class.new(definition, module_store) + instance = described_class.new(definition, metadata) expect(instance.to_s).to eq(<<~DOT) strict digraph "title" { "a.rb" [label="a.rb" id="graph_1"] } DOT - expect(instance.metadata).to eq( + expect(instance.dot_metadata).to eq( [ { id: 'graph_1', @@ -96,7 +96,7 @@ def build_definition(title: 'title', sources: []) ] ) - instance = described_class.new(definition, module_store) + instance = described_class.new(definition, metadata) expect(instance.to_s).to eq(<<~DOT) strict digraph "title" { "a.rb" [label="a.rb" id="graph_1"] @@ -105,7 +105,7 @@ def build_definition(title: 'title', sources: []) } DOT - expect(instance.metadata).to eq( + expect(instance.dot_metadata).to eq( [ { id: 'graph_1', @@ -144,9 +144,9 @@ def build_definition(title: 'title', sources: []) ] ) - module_store.set_modules('a.rb', ['A', 'B']) + metadata.source('a.rb').modules = ['A', 'B'] - instance = described_class.new(definition, module_store) + instance = described_class.new(definition, metadata) expect(instance.to_s).to eq(<<~DOT) strict digraph "title" { @@ -162,7 +162,7 @@ def build_definition(title: 'title', sources: []) } DOT - expect(instance.metadata).to eq( + expect(instance.dot_metadata).to eq( [ { id: 'graph_1', @@ -216,11 +216,11 @@ def build_definition(title: 'title', sources: []) ] ) - module_store.set_modules('a.rb', ['A']) - module_store.set_modules('b.rb', ['B']) - module_store.set_modules('c.rb', ['B']) + metadata.source('a.rb').modules = ['A'] + metadata.source('b.rb').modules = ['B'] + metadata.source('c.rb').modules = ['B'] - instance = described_class.new(definition, module_store, compound: true) + instance = described_class.new(definition, metadata, compound: true) expect(instance.to_s).to eq(<<~DOT) strict digraph "title" { compound=true @@ -239,7 +239,7 @@ def build_definition(title: 'title', sources: []) } DOT - expect(instance.metadata).to eq( + expect(instance.dot_metadata).to eq( [ { id: 'graph_1', @@ -332,11 +332,11 @@ def build_definition(title: 'title', sources: []) ] ) - module_store.set_modules('a.rb', ['A']) - module_store.set_modules('b.rb', ['B']) - module_store.set_modules('c.rb', ['B']) + metadata.source('a.rb').modules = ['A'] + metadata.source('b.rb').modules = ['B'] + metadata.source('c.rb').modules = ['B'] - instance = described_class.new(definition, module_store, compound: true) + instance = described_class.new(definition, metadata, compound: true) expect(instance.to_s).to eq(<<~DOT) strict digraph "title" { compound=true @@ -355,7 +355,7 @@ def build_definition(title: 'title', sources: []) } DOT - expect(instance.metadata).to eq( + expect(instance.dot_metadata).to eq( [ { id: 'graph_1', @@ -469,13 +469,13 @@ def build_definition(title: 'title', sources: []) ] ) - module_store.set_modules('a.rb', ['A']) - module_store.set_modules('b.rb', ['B']) - module_store.set_modules('c.rb', ['B']) - module_store.set_modules('d.rb', ['B', 'C']) - module_store.set_modules('unknown.rb', ['Unknown']) + metadata.source('a.rb').modules = ['A'] + metadata.source('b.rb').modules = ['B'] + metadata.source('c.rb').modules = ['B'] + metadata.source('d.rb').modules = ['B', 'C'] + metadata.source('unknown.rb').modules = ['Unknown'] - instance = described_class.new(definition, module_store, only_module: true) + instance = described_class.new(definition, metadata, only_module: true) expect(instance.to_s).to eq(<<~DOT) strict digraph "title" { compound=true @@ -499,7 +499,7 @@ def build_definition(title: 'title', sources: []) } DOT - expect(instance.metadata).to eq( + expect(instance.dot_metadata).to eq( [ { id: 'graph_1', type: 'module', modules: [{ module_name: 'A' }] }, { id: 'graph_2', type: 'module', modules: [{ module_name: 'B' }] }, @@ -536,7 +536,7 @@ def build_definition(title: 'title', sources: []) ] ) - instance = described_class.new(definition, module_store, concentrate: true) + instance = described_class.new(definition, metadata, concentrate: true) expect(instance.to_s).to eq(<<~DOT) strict digraph "title" { concentrate=true @@ -544,7 +544,7 @@ def build_definition(title: 'title', sources: []) } DOT - expect(instance.metadata).to eq( + expect(instance.dot_metadata).to eq( [ { id: 'graph_1', @@ -575,11 +575,11 @@ def build_definition(title: 'title', sources: []) ] ) - module_store.set_modules('b.rb', ['A']) - module_store.set_modules('c.rb', ['A', 'C']) - module_store.set_modules('d.rb', ['B']) + metadata.source('b.rb').modules = ['A'] + metadata.source('c.rb').modules = ['A', 'C'] + metadata.source('d.rb').modules = ['B'] - instance = described_class.new(definition, module_store, concentrate: true) + instance = described_class.new(definition, metadata, concentrate: true) expect(instance.to_s).to eq(<<~DOT) strict digraph "title" { concentrate=true @@ -602,7 +602,7 @@ def build_definition(title: 'title', sources: []) } DOT - expect(instance.metadata).to eq( + expect(instance.dot_metadata).to eq( [ { id: 'graph_1', type: 'source', source_name: 'a.rb', memo: '', modules: [] }, { id: 'graph_2', type: 'module', modules: [{ module_name: 'A' }] }, diff --git a/spec/diver_down/web/metadata/source_metadata_spec.rb b/spec/diver_down/web/metadata/source_metadata_spec.rb new file mode 100644 index 0000000..41ce0d9 --- /dev/null +++ b/spec/diver_down/web/metadata/source_metadata_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +RSpec.describe DiverDown::Web::Metadata::SourceMetadata do + describe 'InstanceMethods' do + describe '#memo/#memo=' do + it 'read/write memo' do + instance = described_class.new + + expect { + instance.memo = 'memo' + }.to change { + instance.memo + }.from('').to('memo') + end + end + + describe '#modules/modules=' do + it 'read/write modules' do + instance = described_class.new + + expect { + instance.modules = ['A', 'B'] + }.to change { + instance.modules + }.from([]).to(['A', 'B']) + end + end + + describe '#modules?' do + it 'returns bool' do + instance = described_class.new + + expect { + instance.modules = ['A', 'B'] + }.to change { + instance.modules? + }.from(false).to(true) + end + end + + describe '#to_h' do + it 'returns hash' do + instance = described_class.new + expect(instance.to_h).to eq({}) + + instance.memo = 'a' + instance.modules = ['A'] + + expect(instance.to_h).to eq({ memo: 'a', modules: ['A'] }) + end + end + end +end diff --git a/spec/diver_down/web/metadata_spec.rb b/spec/diver_down/web/metadata_spec.rb new file mode 100644 index 0000000..3542e3f --- /dev/null +++ b/spec/diver_down/web/metadata_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +RSpec.describe DiverDown::Web::Metadata do + describe 'InstanceMethods' do + def build_temp_path + Tempfile.new(['test', '.yaml']).path + end + + describe '#load' do + it "does not raise error if file doesn't exist" do + path = build_temp_path + expect { described_class.new(path) }.to_not raise_error + end + end + + describe '#source' do + it 'returns DiverDown::Web::Metadata::SourceMetadata' do + path = build_temp_path + instance = described_class.new(path) + + source_a = instance.source('a.rb') + source_b = instance.source('b.rb') + expect(source_a).to be_a(DiverDown::Web::Metadata::SourceMetadata) + expect(source_b).to be_a(DiverDown::Web::Metadata::SourceMetadata) + end + end + + describe '#flush' do + it 'writes modules to path' do + tempfile = Tempfile.new(['test', '.yaml']) + instance = described_class.new(tempfile.path) + instance.source('a.rb').modules = ['A', 'B'] + + expect { + instance.flush + }.to change { + described_class.new(tempfile.path).source('a.rb').modules + }.from([]).to(['A', 'B']) + end + + it 'writes memo to path' do + tempfile = Tempfile.new(['test', '.yaml']) + instance = described_class.new(tempfile.path) + instance.source('a.rb').memo = 'memo' + + expect { + instance.flush + }.to change { + described_class.new(tempfile.path).source('a.rb').memo + }.from('').to('memo') + end + + it 'sorts by source name' do + tempfile = Tempfile.new(['test', '.yaml']) + instance = described_class.new(tempfile.path) + + sources = ['a.rb', 'b.rb', 'c.rb'] + + sources.shuffle.each do |source| + instance.source(source).modules = ['A'] + end + + expect { + instance.flush + }.to change { + described_class.new(tempfile.path).instance_variable_get(:@source_map).keys + }.from([]).to(sources) + end + end + + describe '#to_h' do + it 'returns sorted hash' do + tempfile = Tempfile.new(['test', '.yaml']) + instance = described_class.new(tempfile.path) + + source_names = ['a.rb', 'b.rb', 'c.rb'] + + source_names.shuffle.each do |source_name| + source = instance.source(source_name) + source.modules = ['A'] + source.memo = 'memo' + end + + expect(instance.to_h[:sources].keys).to eq(source_names) + end + end + end +end diff --git a/spec/diver_down/web/module_store_spec.rb b/spec/diver_down/web/module_store_spec.rb deleted file mode 100644 index e9918b1..0000000 --- a/spec/diver_down/web/module_store_spec.rb +++ /dev/null @@ -1,135 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe DiverDown::Web::ModuleStore do - describe 'InstanceMethods' do - describe '#initialize' do - it "does not raise error if file doesn't exist" do - path = "#{Dir.tmpdir}/not_exist.yaml" - expect { described_class.new(path) }.to_not raise_error - end - end - - describe '#set_modules' do - it 'set_modules modules to source' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - - expect { - instance.set_modules('a.rb', ['A', 'B']) - }.to change { - instance.get_modules('a.rb') - }.from([]).to(['A', 'B']) - end - end - - describe '#get_modules' do - it 'returns blank array if source is not set_modules' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - - expect(instance.get_modules('a.rb')).to eq([]) - - instance.set_modules('a.rb', ['A', 'B']) - expect(instance.get_modules('a.rb')).to eq(['A', 'B']) - end - end - - describe '#set_memo' do - it 'sets memo' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - - expect { - instance.set_memo('a.rb', ' memo ') - }.to change { - instance.get_memo('a.rb') - }.from('').to('memo') - end - end - - describe '#get_memo' do - it 'returns blank string if source is not set_modules' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - - expect(instance.get_memo('a.rb')).to eq('') - - instance.set_memo('a.rb', 'a') - expect(instance.get_memo('a.rb')).to eq('a') - end - end - - describe '#classified?' do - it 'returns bool' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - - instance.set_modules('a.rb', []) - instance.set_modules('b.rb', ['A']) - - expect(instance.classified?('a.rb')).to be(false) - expect(instance.classified?('b.rb')).to be(true) - expect(instance.classified?('c.rb')).to be(false) - end - end - - describe '#flush' do - it 'writes modules to path' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - instance.set_modules('a.rb', ['A', 'B']) - - expect { - instance.flush - }.to change { - described_class.new(tempfile.path).get_modules('a.rb') - }.from([]).to(['A', 'B']) - end - - it 'writes memo to path' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - instance.set_memo('a.rb', 'memo') - - expect { - instance.flush - }.to change { - described_class.new(tempfile.path).get_memo('a.rb') - }.from('').to('memo') - end - - it 'sorts by source name' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - - sources = ['a.rb', 'b.rb', 'c.rb'] - - sources.shuffle.each do |source| - instance.set_modules(source, ['A']) - end - - expect { - instance.flush - }.to change { - described_class.new(tempfile.path).instance_variable_get(:@store).keys - }.from([]).to(sources) - end - end - - describe '#to_h' do - it 'returns sorted hash' do - tempfile = Tempfile.new(['test', '.yaml']) - instance = described_class.new(tempfile.path) - - sources = ['a.rb', 'b.rb', 'c.rb'] - - sources.shuffle.each do |source| - instance.set_modules(source, ['A']) - instance.set_memo(source, 'memo') - end - - expect(instance.to_h.keys).to eq(sources) - end - end - end -end diff --git a/spec/diver_down/web_spec.rb b/spec/diver_down/web_spec.rb index 24084f0..7b54f22 100644 --- a/spec/diver_down/web_spec.rb +++ b/spec/diver_down/web_spec.rb @@ -4,7 +4,7 @@ include Rack::Test::Methods def app - @app ||= described_class.new(definition_dir:, module_store:, store:) + @app ||= described_class.new(definition_dir:, metadata:, store:) end let(:definition_dir) do @@ -15,9 +15,9 @@ def app DiverDown::Web::DefinitionStore.new end - let(:module_store) do - module_store_path = Tempfile.new(['test', '.yaml']).path - DiverDown::Web::ModuleStore.new(module_store_path) + let(:metadata) do + metadata_path = Tempfile.new(['test', '.yaml']).path + DiverDown::Web::Metadata.new(metadata_path) end describe 'GET /' do @@ -76,7 +76,7 @@ def app ] ) store.set(definition_1, definition_2) - module_store.set_modules('a.rb', ['A']) + metadata.source('a.rb').modules = ['A'] get '/api/definitions.json' @@ -315,8 +315,8 @@ def assert_definition_group(definition_group, expected_ids) ) store.set(definition) - module_store.set_modules('a.rb', ['A']) - module_store.set_memo('a.rb', 'memo') + metadata.source('a.rb').modules = ['A'] + metadata.source('a.rb').memo = 'memo' get '/api/sources.json' @@ -370,8 +370,8 @@ def assert_definition_group(definition_group, expected_ids) ] ) store.set(definition) - module_store.set_modules('a.rb', ['A', 'B']) - module_store.set_modules('b.rb', ['B', 'C']) + metadata.source('a.rb').modules = ['A', 'B'] + metadata.source('b.rb').modules = ['B', 'C'] get '/api/modules.json' @@ -422,9 +422,9 @@ def assert_definition_group(definition_group, expected_ids) ) ids = store.set(definition_1, definition_2) - module_store.set_modules('a.rb', ['A']) - module_store.set_modules('b.rb', ['A', 'B']) - module_store.set_memo('a.rb', 'memo') + metadata.source('a.rb').modules = ['A'] + metadata.source('b.rb').modules = ['A', 'B'] + metadata.source('a.rb').memo = 'memo' get '/api/modules/A.json' @@ -494,8 +494,8 @@ def assert_definition_group(definition_group, expected_ids) ) ids = store.set(definition) - module_store.set_modules('a.rb', ['グローバル']) - module_store.set_memo('a.rb', 'memo') + metadata.source('a.rb').modules = ['グローバル'] + metadata.source('a.rb').memo = 'memo' get "/api/modules/#{CGI.escape('グローバル')}.json" @@ -565,7 +565,7 @@ def assert_definition_group(definition_group, expected_ids) ) bit_ids = store.set(definition_1, definition_2) - module_store.set_memo('a.rb', 'memo') + metadata.source('a.rb').memo = 'memo' get "/api/definitions/#{bit_ids.inject(0, &:|)}.json" @@ -637,7 +637,7 @@ def assert_definition_group(definition_group, expected_ids) store.set(definition_1) store.set(definition_2) - module_store.set_memo('a.rb', 'memo') + metadata.source('a.rb').memo = 'memo' get '/api/sources/a.rb.json' @@ -681,7 +681,7 @@ def assert_definition_group(definition_group, expected_ids) expect(last_response.status).to eq(200) - expect(module_store.get_modules('a.rb')).to eq(['A', 'B']) + expect(metadata.source('a.rb').modules).to eq(['A', 'B']) end it 'ignores blank modules' do @@ -699,7 +699,7 @@ def assert_definition_group(definition_group, expected_ids) expect(last_response.status).to eq(200) - expect(module_store.get_modules('a.rb')).to eq(['B']) + expect(metadata.source('a.rb').modules).to eq(['B']) end end @@ -725,7 +725,7 @@ def assert_definition_group(definition_group, expected_ids) expect(last_response.status).to eq(200) - expect(module_store.get_memo('a.rb')).to eq('memo') + expect(metadata.source('a.rb').memo).to eq('memo') end end end