local config_file = '' local config_dir = '' --- -- Find config file and set global variables -- -- @return string local function find_config_file() if config_file ~= '' then return config_file end local scandir = require('plenary.scandir').scan_dir local Path = require('plenary.path') local current_dir = Path:new('.') while config_file == '' do local files = scandir(current_dir.filename, { depth = 1 }) for _, file in ipairs(files) do if string.find(file, 'rclone.conf') then config_file = file config_dir = current_dir.filename return config_file end end --if current_dir:is_root() then if current_dir.filename == '/' then -- set to 'rclone config file' output local handle = io.popen('rclone config file', 'r') handle:read() -- advance one line because we want the second line only local output = handle:read(); handle:close() config_file = output local config_file_path = Path:new(config_file) config_dir = config_file_path:parent().filename return config_file end current_dir = current_dir:parent() end end --- -- Get rclone config as JSON -- and parse to table -- -- @return string local function get_config() local handle = io.popen('rclone config dump --config=' .. find_config_file(), 'r') local config = handle:read('*all'); handle:close() return vim.fn.json_decode(config) end --- -- Get the desired remote config from the config file -- -- @param remote string -- -- @return string -- @return table -- @return nil local function get_remote_config(remote) local config = get_config() local target_remote = nil local target_config = nil if remote then if config[remote] then target_remote = remote target_config = config[remote] else print("Couldn't find remote: \"" .. remote .. '"') return end else local config_length = 0 for _, _ in pairs(config) do config_length = config_length + 1 end if config_length == 0 then print('No remotes defined in config file at "' .. find_config_file() .. '"') return end if config_length == 1 then for remote_name, remote_config in pairs(config) do target_remote = remote_name target_config = remote_config end else for remote_name, remote_config in pairs(config) do if remote_config.vim_rclone_default then target_remote = remote_name target_config = remote_config end end if target_remote == nil then print('No default remote specified') return end end end if target_config.vim_rclone_local_path == nil or target_config.vim_rclone_local_path == '' or target_config.vim_rclone_remote_path == nil or target_config.vim_rclone_remote_path == '' then print('No local and or remote path set for remote "' .. target_remote .. '"') return end return target_remote, target_config end --- -- Build the local path -- -- @param config table -- -- @return string local function prepare_cmd_local_path(config) local Path = require('plenary.path') local local_path = Path:new(config.vim_rclone_local_path) local_path._cwd = config_dir -- set cwd to config dir to resolve path correctly local_path = local_path:absolute() local_path = local_path:gsub('%./', '') -- remove wrongly kept ./ for relative paths return local_path end --- -- Add remote path -- -- @param config table -- @param remote string -- -- @return string local function build_cmd_remote_path(config, remote) return remote .. ':' .. config.vim_rclone_remote_path end --- -- Add config location path -- -- @return string local function build_cmd_config() return '--config=' .. find_config_file() end --- -- Add exclude patterns -- -- @param local_path string -- -- @return string local function build_cmd_exclude(local_path) local Path = require('plenary.path') local cmd = '' -- TODO: add configurable default exclude patterns local configurable_default_exclude_patterns = {'.git/', '.gitignore', '.rcloneignore'} for _, pattern in pairs(configurable_default_exclude_patterns) do cmd = cmd .. ' --exclude=' .. pattern end -- add .gitignore patterns local gitignore = Path:new(local_path .. '/.gitignore') if gitignore:exists() then cmd = cmd .. ' --exclude-from=' .. gitignore.filename end -- add .rcloneignore patterns local rcloneignore = Path:new(local_path .. '/.rcloneignore') if rcloneignore:exists() then cmd = cmd .. ' --exclude-from=' .. rcloneignore.filename end return cmd:gsub('^%s?', '') -- ltrim end --- -- Add logging -- -- @return string local function build_cmd_logging() local log_level = 'INFO' local cmd = '' cmd = '--log-level=' .. log_level cmd = cmd .. ' --log-file=' .. vim.fn.stdpath('data') .. '/rclone_nvim/rclone.log' return cmd end --- -- Call the 'rclone copy' command with config values -- 'rclone copy ' -- -- @return any local function copy(options) local remote, config = get_remote_config(options.remote) if remote == nil then return end local local_path = prepare_cmd_local_path(config) local cmd = 'rclone copy ' .. local_path .. ' ' .. build_cmd_remote_path(config, remote) .. ' ' .. build_cmd_config() .. ' ' .. build_cmd_exclude(local_path) .. ' ' .. build_cmd_logging() if options['--dry-run'] then print(cmd) else os.execute(cmd) end print("Copied!") return config end --- -- Call the 'rclone copy' command with config values for the current file -- 'rclone copy ' -- -- @return any local function copyFile(options) local remote, config = get_remote_config(options.remote) if remote == nil then return end local local_path = prepare_cmd_local_path(config) -- build relative path to file for local and remote local Path = require('plenary.path') local local_file_path = Path:new(vim.fn.expand('%')):absolute() local local_file_path_relative = local_file_path:gsub(local_path:gsub('([^%w])', '%%%1'), '') local local_file_path_relative_parent = Path:new(local_file_path_relative):parent().filename local cmd = 'rclone copy ' .. local_path .. local_file_path_relative .. ' ' .. build_cmd_remote_path(config, remote) .. local_file_path_relative_parent .. ' ' .. build_cmd_config() .. ' ' .. build_cmd_exclude(local_path) .. ' ' .. build_cmd_logging() if options['--dry-run'] then print(cmd) else os.execute(cmd) end print("Copied file!") return config end --- -- Call the 'rclone sync' command with config values -- 'rclone sync ' -- -- @return any local function sync(options) local remote, config = get_remote_config(options.remote) if remote == nil then return end local local_path = prepare_cmd_local_path(config) local cmd = 'rclone sync ' .. local_path .. ' ' .. build_cmd_remote_path(config, remote) .. ' ' .. build_cmd_config() .. ' ' .. build_cmd_exclude(local_path) .. ' ' .. build_cmd_logging() if options['--dry-run'] then print(cmd) else os.execute(cmd) end print("Synced!") return config end -- Table for dynamic command access local commands = { copy = copy, copyFile = copyFile, sync = sync, } --- -- Parse args and run specified command local function run(cmd, ...) local args = { ... } if cmd == nil then print('No command supplied!') return end if commands[cmd] == nil then print('Command "' .. cmd .. '" does not exist.') return end local options = {} for _, arg in ipairs(args) do if arg:find('%-%-') == 1 then options[arg] = true elseif arg:find('=', 1) == nil then options['remote'] = arg else local param = vim.split(arg, '=') local key = table.remove(param, 1) param = table.concat(param, '=') options[key] = param end end commands[cmd](options) end return { run = run, }