labnotes 0.2.4__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {labnotes-0.2.4 → labnotes-0.2.6}/PKG-INFO +6 -2
- {labnotes-0.2.4 → labnotes-0.2.6}/README.md +4 -1
- {labnotes-0.2.4 → labnotes-0.2.6}/pyproject.toml +1 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/context.md +4 -1
- labnotes-0.2.6/src/labnotes/copy_experiment.py +12 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/create_experiment.py +26 -3
- labnotes-0.2.6/src/labnotes/experiment_definition.py +22 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/experiments.py +45 -2
- labnotes-0.2.6/src/labnotes/parse_param_value.py +8 -0
- labnotes-0.2.6/src/labnotes/parse_params.py +11 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/project_snapshot.py +3 -1
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/read_experiment_config.py +4 -0
- labnotes-0.2.6/src/labnotes/runner/untracked_diff.py +39 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/uv.lock +11 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/.gitignore +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/CHANGELOG.md +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/MIGRATIONS.md +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/__init__.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/__main__.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/cli.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/create_app.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/create_note.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/data_html.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/format_started.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/format_value.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/get_state.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/init.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/latest_log.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/load_config.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/load_run.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/log.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/log_path.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/models.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/modes.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/note_path.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/output_html.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/read_config.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/read_path.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/retries.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/run_output.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/cgroup_memory.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/check_hook_placeholders.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/claim_run.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/command_flags.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/container_cgroup.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/container_memory.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/continue_run.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/docker_output.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/fill_dirs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/fill_experiment_env.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/finalize_lost_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/first_invocation.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/group_args.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/invocation_hook.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/override_flags.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/placeholders.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/run_experiment.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/run_git.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/run_locked.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/save_note.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/server.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/set_state.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/state_path.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/list_resumable_runs.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/view/build.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/testing-conventions.toml +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/tests/conftest.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/tests/e2e/conftest.py +0 -0
- {labnotes-0.2.4 → labnotes-0.2.6}/tests/integration/conftest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: labnotes
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Browse experiment designs, notes, and run data from a folder.
|
|
5
5
|
Project-URL: Repository, https://github.com/thekevinscott/labnotes
|
|
6
6
|
Author-email: Kevin Scott <me@thekevinscott.com>
|
|
@@ -20,6 +20,7 @@ Requires-Dist: psutil>=7.2.2
|
|
|
20
20
|
Requires-Dist: rich>=14
|
|
21
21
|
Requires-Dist: starlette>=1.8.0
|
|
22
22
|
Requires-Dist: textual>=8.2.8
|
|
23
|
+
Requires-Dist: tomlkit>=0.15.2
|
|
23
24
|
Requires-Dist: uvicorn>=0.54.0
|
|
24
25
|
Description-Content-Type: text/markdown
|
|
25
26
|
|
|
@@ -83,7 +84,10 @@ one. Piped, they print a table.
|
|
|
83
84
|
|
|
84
85
|
`labnotes experiments new [NAME]` creates an experiment folder with a starter
|
|
85
86
|
`experiment.toml`, picking a random name if you give none. `new` itself can't
|
|
86
|
-
be an experiment name.
|
|
87
|
+
be an experiment name. `--command`, `--param NAME=VALUE` (repeat it for several
|
|
88
|
+
values) and `--repeats` write those settings into the file. `--from OTHER`
|
|
89
|
+
copies another experiment's `experiment.toml` and files, never its runs, and
|
|
90
|
+
records `from = "OTHER"`; the other flags replace the copied values.
|
|
87
91
|
|
|
88
92
|
`labnotes experiments foo run` runs `foo` from its `experiment.toml`, in the
|
|
89
93
|
experiments folder:
|
|
@@ -58,7 +58,10 @@ one. Piped, they print a table.
|
|
|
58
58
|
|
|
59
59
|
`labnotes experiments new [NAME]` creates an experiment folder with a starter
|
|
60
60
|
`experiment.toml`, picking a random name if you give none. `new` itself can't
|
|
61
|
-
be an experiment name.
|
|
61
|
+
be an experiment name. `--command`, `--param NAME=VALUE` (repeat it for several
|
|
62
|
+
values) and `--repeats` write those settings into the file. `--from OTHER`
|
|
63
|
+
copies another experiment's `experiment.toml` and files, never its runs, and
|
|
64
|
+
records `from = "OTHER"`; the other flags replace the copied values.
|
|
62
65
|
|
|
63
66
|
`labnotes experiments foo run` runs `foo` from its `experiment.toml`, in the
|
|
64
67
|
experiments folder:
|
|
@@ -43,7 +43,10 @@ keys that never come.
|
|
|
43
43
|
|
|
44
44
|
`labnotes experiments new [NAME]` creates an experiment folder with a starter
|
|
45
45
|
`experiment.toml`, picking a random name if you give none. `new` itself can't
|
|
46
|
-
be an experiment name.
|
|
46
|
+
be an experiment name. `--command`, `--param NAME=VALUE` (repeat it for several
|
|
47
|
+
values) and `--repeats` write those settings into the file. `--from OTHER`
|
|
48
|
+
copies another experiment's `experiment.toml` and files, never its runs, and
|
|
49
|
+
records `from = "OTHER"`; the other flags replace the copied values.
|
|
47
50
|
|
|
48
51
|
`labnotes experiments foo run` runs `foo` from its `experiment.toml`, in the
|
|
49
52
|
experiments folder:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def copy_experiment(source: Path, target: Path, runs: Path) -> None:
|
|
6
|
+
def skip(folder: str, names: list[str]) -> set[str]:
|
|
7
|
+
here = Path(folder)
|
|
8
|
+
if here == runs:
|
|
9
|
+
return {name for name in names if (here / name / "run.json").exists()}
|
|
10
|
+
return {name for name in names if here / name == runs}
|
|
11
|
+
|
|
12
|
+
shutil.copytree(source, target, ignore=skip, symlinks=True)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
+
from labnotes.copy_experiment import copy_experiment
|
|
4
|
+
from labnotes.experiment_definition import experiment_definition
|
|
3
5
|
from labnotes.models import LabnotesConfig
|
|
4
6
|
from labnotes.runner.new_run_id import new_run_id
|
|
7
|
+
from labnotes.runner.runs_parent import runs_parent
|
|
5
8
|
|
|
6
9
|
STARTER = """\
|
|
7
10
|
# Runs from the project root. {run_dir} becomes the run's folder.
|
|
@@ -14,7 +17,14 @@ repeats = 1
|
|
|
14
17
|
"""
|
|
15
18
|
|
|
16
19
|
|
|
17
|
-
def create_experiment(
|
|
20
|
+
def create_experiment(
|
|
21
|
+
config: LabnotesConfig,
|
|
22
|
+
name: str | None,
|
|
23
|
+
source: str | None = None,
|
|
24
|
+
command: str | None = None,
|
|
25
|
+
repeats: int | None = None,
|
|
26
|
+
params: dict[str, list[object]] | None = None,
|
|
27
|
+
) -> Path:
|
|
18
28
|
def folder(candidate: str) -> Path:
|
|
19
29
|
return config.root / config.experiments.replace("{experiment}", candidate)
|
|
20
30
|
|
|
@@ -31,6 +41,19 @@ def create_experiment(config: LabnotesConfig, name: str | None) -> Path:
|
|
|
31
41
|
path = folder(name)
|
|
32
42
|
if path.exists():
|
|
33
43
|
raise ValueError(f"{name} already exists at {path}.")
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
text = STARTER
|
|
45
|
+
if source is not None:
|
|
46
|
+
origin = folder(source) / "experiment.toml"
|
|
47
|
+
if not origin.is_file():
|
|
48
|
+
raise ValueError(f"No experiment.toml for {source} at {origin}.")
|
|
49
|
+
text = origin.read_text(encoding="utf-8")
|
|
50
|
+
copy_experiment(
|
|
51
|
+
origin.parent, path, runs_parent(config.root, config.runs, source)
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
path.mkdir(parents=True)
|
|
55
|
+
(path / "experiment.toml").write_text(
|
|
56
|
+
experiment_definition(text, source, command, repeats, params or {}),
|
|
57
|
+
encoding="utf-8",
|
|
58
|
+
)
|
|
36
59
|
return path
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import tomlkit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def experiment_definition(
|
|
5
|
+
text: str,
|
|
6
|
+
source: str | None,
|
|
7
|
+
command: str | None,
|
|
8
|
+
repeats: int | None,
|
|
9
|
+
params: dict[str, list[object]],
|
|
10
|
+
) -> str:
|
|
11
|
+
document = tomlkit.parse(text)
|
|
12
|
+
document.pop("from", None)
|
|
13
|
+
if command is not None:
|
|
14
|
+
document["command"] = command
|
|
15
|
+
if repeats is not None:
|
|
16
|
+
document["repeats"] = repeats
|
|
17
|
+
if params:
|
|
18
|
+
table = document.setdefault("params", tomlkit.table())
|
|
19
|
+
for name, values in params.items():
|
|
20
|
+
table[name] = values
|
|
21
|
+
head = "" if source is None else tomlkit.dumps({"from": source})
|
|
22
|
+
return head + tomlkit.dumps(document)
|
|
@@ -8,6 +8,7 @@ from labnotes.create_experiment import create_experiment
|
|
|
8
8
|
from labnotes.runner.start_experiment import start_experiment
|
|
9
9
|
from labnotes.utils.format_bytes import format_bytes
|
|
10
10
|
from labnotes.load_config import load_config
|
|
11
|
+
from labnotes.parse_params import parse_params
|
|
11
12
|
from labnotes.parse_subset import parse_subset
|
|
12
13
|
from labnotes.print_rows import print_rows
|
|
13
14
|
from labnotes.read_config import CONFIG_FILE
|
|
@@ -32,7 +33,8 @@ class PassthroughCommand(click.Command):
|
|
|
32
33
|
@click.command(
|
|
33
34
|
cls=PassthroughCommand,
|
|
34
35
|
help="List experiments, show one, list its runs, or run it."
|
|
35
|
-
" `new [NAME]` creates one
|
|
36
|
+
" `new [NAME]` creates one, from --command, --param, --repeats and"
|
|
37
|
+
" --from OTHER if given. With run, --PARAM=VALUE runs only the"
|
|
36
38
|
" matrix cells where that param has that value (repeat it for several"
|
|
37
39
|
" values), and anything after --"
|
|
38
40
|
" is appended to every run's command and recorded in run.json, so never"
|
|
@@ -79,6 +81,31 @@ class PassthroughCommand(click.Command):
|
|
|
79
81
|
help="With run: shell command to run once after every run ends."
|
|
80
82
|
" Overrides experiment.toml and .labnotes.toml.",
|
|
81
83
|
)
|
|
84
|
+
@click.option(
|
|
85
|
+
"--command",
|
|
86
|
+
metavar="COMMAND",
|
|
87
|
+
help="With new: the experiment's command.",
|
|
88
|
+
)
|
|
89
|
+
@click.option(
|
|
90
|
+
"--param",
|
|
91
|
+
"params",
|
|
92
|
+
multiple=True,
|
|
93
|
+
metavar="NAME=VALUE",
|
|
94
|
+
help="With new: a value for a param, parsed as TOML or else taken as a"
|
|
95
|
+
" string. Repeat it for several values.",
|
|
96
|
+
)
|
|
97
|
+
@click.option(
|
|
98
|
+
"--repeats",
|
|
99
|
+
type=click.IntRange(min=1),
|
|
100
|
+
help="With new: how many times each combination runs.",
|
|
101
|
+
)
|
|
102
|
+
@click.option(
|
|
103
|
+
"--from",
|
|
104
|
+
"source",
|
|
105
|
+
metavar="OTHER",
|
|
106
|
+
help="With new: start from OTHER's experiment.toml and files, never its"
|
|
107
|
+
" runs. Other flags replace the copied values.",
|
|
108
|
+
)
|
|
82
109
|
@click.pass_context
|
|
83
110
|
def experiments(
|
|
84
111
|
ctx: click.Context,
|
|
@@ -90,6 +117,10 @@ def experiments(
|
|
|
90
117
|
timeout: int | None,
|
|
91
118
|
setup: str | None,
|
|
92
119
|
teardown: str | None,
|
|
120
|
+
command: str | None,
|
|
121
|
+
params: tuple[str, ...],
|
|
122
|
+
repeats: int | None,
|
|
123
|
+
source: str | None,
|
|
93
124
|
) -> None:
|
|
94
125
|
passthrough: list[str] | None = ctx.meta.get(PASSTHROUGH)
|
|
95
126
|
config = load_config(ctx)
|
|
@@ -97,10 +128,22 @@ def experiments(
|
|
|
97
128
|
raise click.ClickException(
|
|
98
129
|
f"No {CONFIG_FILE} here. Run labnotes init, or pass -c PATH."
|
|
99
130
|
)
|
|
131
|
+
if name != "new":
|
|
132
|
+
given = {
|
|
133
|
+
"--command": command,
|
|
134
|
+
"--param": params or None,
|
|
135
|
+
"--repeats": repeats,
|
|
136
|
+
"--from": source,
|
|
137
|
+
}
|
|
138
|
+
for flag, value in given.items():
|
|
139
|
+
if value is not None:
|
|
140
|
+
raise click.UsageError(f"{flag} needs new.")
|
|
100
141
|
match name, action:
|
|
101
142
|
case "new", _:
|
|
102
143
|
try:
|
|
103
|
-
path = create_experiment(
|
|
144
|
+
path = create_experiment(
|
|
145
|
+
config, action, source, command, repeats, parse_params(list(params))
|
|
146
|
+
)
|
|
104
147
|
except ValueError as error:
|
|
105
148
|
raise click.ClickException(str(error)) from error
|
|
106
149
|
click.echo(f"Created {path / 'experiment.toml'}")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from labnotes.parse_param_value import parse_param_value
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_params(pairs: list[str]) -> dict[str, list[object]]:
|
|
5
|
+
params: dict[str, list[object]] = {}
|
|
6
|
+
for pair in pairs:
|
|
7
|
+
name, equals, value = pair.partition("=")
|
|
8
|
+
if not equals or not name:
|
|
9
|
+
raise ValueError(f"--param {pair} must look like NAME=VALUE.")
|
|
10
|
+
params.setdefault(name, []).append(parse_param_value(value))
|
|
11
|
+
return params
|
|
@@ -11,6 +11,8 @@ from pathlib import Path
|
|
|
11
11
|
from tempfile import TemporaryDirectory
|
|
12
12
|
from types import FrameType
|
|
13
13
|
|
|
14
|
+
from labnotes.runner.untracked_diff import untracked_diff
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
@dataclass
|
|
16
18
|
class ProjectSnapshot:
|
|
@@ -37,7 +39,7 @@ def project_snapshot(root: Path) -> Iterator[ProjectSnapshot]:
|
|
|
37
39
|
sha = git(root, "rev-parse", "HEAD").decode("ascii").strip()
|
|
38
40
|
repo_root = Path(git(root, "rev-parse", "--show-toplevel").decode().strip())
|
|
39
41
|
project_prefix = root.resolve().relative_to(repo_root.resolve())
|
|
40
|
-
diff = git(repo_root, "diff", "HEAD")
|
|
42
|
+
diff = git(repo_root, "diff", "HEAD") + untracked_diff(repo_root)
|
|
41
43
|
except (OSError, subprocess.SubprocessError):
|
|
42
44
|
logging.warning("Project has no readable Git HEAD; running from the live tree")
|
|
43
45
|
yield ProjectSnapshot(root)
|
|
@@ -28,6 +28,7 @@ SETTINGS = {
|
|
|
28
28
|
"teardown",
|
|
29
29
|
"env",
|
|
30
30
|
"continues",
|
|
31
|
+
"from",
|
|
31
32
|
}
|
|
32
33
|
HOOKS = ("setup", "teardown")
|
|
33
34
|
VIEWS = {"runs"}
|
|
@@ -86,6 +87,9 @@ def read_experiment_config(
|
|
|
86
87
|
memory_interval = values.get("memory_interval", DEFAULT_MEMORY_INTERVAL)
|
|
87
88
|
if not is_non_negative_number(memory_interval):
|
|
88
89
|
raise ValueError(f"{path}: memory_interval must be 0 or more seconds")
|
|
90
|
+
origin = values.get("from")
|
|
91
|
+
if origin is not None and (not isinstance(origin, str) or not origin.strip()):
|
|
92
|
+
raise ValueError(f"{path}: from must be an experiment name")
|
|
89
93
|
for hook in HOOKS:
|
|
90
94
|
run = values.get(hook)
|
|
91
95
|
if run is not None and (not isinstance(run, str) or not run.strip()):
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from tempfile import TemporaryDirectory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def untracked_diff(repo_root: Path) -> bytes:
|
|
8
|
+
"""Patch adding every untracked, non-ignored file, as the snapshot copies them."""
|
|
9
|
+
listing = subprocess.check_output(
|
|
10
|
+
["git", "ls-files", "-o", "--exclude-standard", "-z"],
|
|
11
|
+
cwd=repo_root,
|
|
12
|
+
stderr=subprocess.DEVNULL,
|
|
13
|
+
)
|
|
14
|
+
# A trailing slash marks an untracked nested repository, which git cannot add.
|
|
15
|
+
paths = [path for path in listing.split(b"\0") if path and not path.endswith(b"/")]
|
|
16
|
+
if not paths:
|
|
17
|
+
return b""
|
|
18
|
+
with TemporaryDirectory(prefix="labnotes-index-") as directory:
|
|
19
|
+
# A throwaway index keeps the user's real index untouched.
|
|
20
|
+
env = {**os.environ, "GIT_INDEX_FILE": str(Path(directory) / "index")}
|
|
21
|
+
subprocess.run(
|
|
22
|
+
["git", "add", "--intent-to-add", "--pathspec-from-file=-"]
|
|
23
|
+
+ ["--pathspec-file-nul"],
|
|
24
|
+
cwd=repo_root,
|
|
25
|
+
env=env,
|
|
26
|
+
input=b"\0".join(paths),
|
|
27
|
+
stdout=subprocess.DEVNULL,
|
|
28
|
+
stderr=subprocess.DEVNULL,
|
|
29
|
+
check=True,
|
|
30
|
+
)
|
|
31
|
+
# Files over 1 MiB show as binary so stray data cannot bloat every patch.
|
|
32
|
+
return subprocess.run(
|
|
33
|
+
["git", "-c", "core.bigFileThreshold=1m", "diff"],
|
|
34
|
+
cwd=repo_root,
|
|
35
|
+
env=env,
|
|
36
|
+
stdout=subprocess.PIPE,
|
|
37
|
+
stderr=subprocess.DEVNULL,
|
|
38
|
+
check=True,
|
|
39
|
+
).stdout
|
|
@@ -568,6 +568,7 @@ dependencies = [
|
|
|
568
568
|
{ name = "rich" },
|
|
569
569
|
{ name = "starlette" },
|
|
570
570
|
{ name = "textual" },
|
|
571
|
+
{ name = "tomlkit" },
|
|
571
572
|
{ name = "uvicorn" },
|
|
572
573
|
]
|
|
573
574
|
|
|
@@ -597,6 +598,7 @@ requires-dist = [
|
|
|
597
598
|
{ name = "rich", specifier = ">=14" },
|
|
598
599
|
{ name = "starlette", specifier = ">=1.8.0" },
|
|
599
600
|
{ name = "textual", specifier = ">=8.2.8" },
|
|
601
|
+
{ name = "tomlkit", specifier = ">=0.15.2" },
|
|
600
602
|
{ name = "uvicorn", specifier = ">=0.54.0" },
|
|
601
603
|
]
|
|
602
604
|
|
|
@@ -1307,6 +1309,15 @@ wheels = [
|
|
|
1307
1309
|
{ url = "https://files.pythonhosted.org/packages/fb/be/35261223d9416a0751cdff1c7b4a6f881387218a12d439fe22fefebc8c04/textual-8.2.8-py3-none-any.whl", hash = "sha256:267375fd402dc8d981457212efa71f0e3365fd17bba144ba9bb3ed7563cb374a", size = 731418, upload-time = "2026-06-30T06:51:26.364Z" },
|
|
1308
1310
|
]
|
|
1309
1311
|
|
|
1312
|
+
[[package]]
|
|
1313
|
+
name = "tomlkit"
|
|
1314
|
+
version = "0.15.2"
|
|
1315
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1316
|
+
sdist = { url = "https://files.pythonhosted.org/packages/08/72/fc01563df95c189a9f65850096bdc205bc2fe37da6c740cd64afaf18d07e/tomlkit-0.15.2.tar.gz", hash = "sha256:83e32755ba1d20d9340bd069ee97e9f6232ba2346b63ffcba425de01f7e327a6", size = 190522, upload-time = "2026-10-16T04:07:08.166Z" }
|
|
1317
|
+
wheels = [
|
|
1318
|
+
{ url = "https://files.pythonhosted.org/packages/da/46/7d84138a60a8bd796825b2d38ebe92d0d16501ca57d501b7c5d677401bbc/tomlkit-0.15.2-py3-none-any.whl", hash = "sha256:05562412163a3037b94497385fb7f9204c637bc605af7a48199e41cf2272f437", size = 52225, upload-time = "2026-10-16T04:07:09.216Z" },
|
|
1319
|
+
]
|
|
1320
|
+
|
|
1310
1321
|
[[package]]
|
|
1311
1322
|
name = "tornado"
|
|
1312
1323
|
version = "6.5.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|