561 lines
14 KiB
C
561 lines
14 KiB
C
|
/*
|
||
|
*
|
||
|
* Copyright (c) 2001-2002, Biswapesh Chattopadhyay
|
||
|
*
|
||
|
* This source code is released for free distribution under the terms of the
|
||
|
* GNU General Public License.
|
||
|
*
|
||
|
*/
|
||
|
#include "general.h"
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <limits.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <unistd.h>
|
||
|
#ifndef G_OS_WIN32
|
||
|
# include <fnmatch.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#include "options.h"
|
||
|
#define LIBCTAGS_DEFINED
|
||
|
#include "tm_tag.h"
|
||
|
#include "tm_workspace.h"
|
||
|
#include "tm_source_file.h"
|
||
|
#include "tm_file_entry.h"
|
||
|
#include "tm_project.h"
|
||
|
|
||
|
#define TM_FILE_NAME ".tm_project.cache"
|
||
|
|
||
|
static const char *s_sources[] = { "*.c", "*.pc" /* C/Pro*C files */
|
||
|
, "*.C", "*.cpp", "*.cc", "*.cxx", "*.c++" /* C++ files */
|
||
|
, "*.h", "*.hh", "*.hpp", "*.H", "*.h++", "*.i" /* Header files */
|
||
|
, "*.oaf", "*.gob", "*.idl" /* CORBA/Bonobo files */
|
||
|
, "*.l", "*.y" /* Lex/Yacc files */
|
||
|
#if 0
|
||
|
, "*.ui", "*.moc" /* KDE/QT Files */
|
||
|
, "*.glade" /* UI files */
|
||
|
#endif
|
||
|
, "*.java", "*.pl", "*.pm", "*.py", "*.sh" /* Other languages */
|
||
|
, NULL /* Must terminate with NULL */
|
||
|
};
|
||
|
|
||
|
static const char *s_ignore[] = { "CVS", "intl", "po", NULL };
|
||
|
|
||
|
static GList *glist_from_array(const char **arr)
|
||
|
{
|
||
|
GList *l = NULL;
|
||
|
int i;
|
||
|
for (i =0; arr[i]; ++ i)
|
||
|
l = g_list_prepend(l, (gpointer) arr[i]);
|
||
|
return g_list_reverse(l);
|
||
|
}
|
||
|
|
||
|
guint project_class_id = 0;
|
||
|
|
||
|
gboolean tm_project_init(TMProject *project, const char *dir
|
||
|
, const char **sources, const char **ignore, gboolean force)
|
||
|
{
|
||
|
struct stat s;
|
||
|
char path[PATH_MAX];
|
||
|
|
||
|
g_return_val_if_fail((project && dir), FALSE);
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message("Initializing project %s", dir);
|
||
|
#endif
|
||
|
if (0 == project_class_id)
|
||
|
{
|
||
|
project_class_id = tm_work_object_register(tm_project_free, tm_project_update
|
||
|
, tm_project_find_file);
|
||
|
}
|
||
|
|
||
|
if ((0 != stat(dir, &s)) || (!S_ISDIR(s.st_mode)))
|
||
|
{
|
||
|
g_warning("%s: Not a valid directory", dir);
|
||
|
return FALSE;
|
||
|
}
|
||
|
project->dir = tm_get_real_path(dir);
|
||
|
if (sources)
|
||
|
project->sources = sources;
|
||
|
else
|
||
|
project->sources = s_sources;
|
||
|
if (ignore)
|
||
|
project->ignore = ignore;
|
||
|
else
|
||
|
project->ignore = s_ignore;
|
||
|
project->file_list = NULL;
|
||
|
g_snprintf(path, PATH_MAX, "%s/%s", project->dir, TM_FILE_NAME);
|
||
|
if ((0 != stat(path, &s)) || (0 == s.st_size))
|
||
|
force = TRUE;
|
||
|
if (FALSE == tm_work_object_init(&(project->work_object),
|
||
|
project_class_id, path, force))
|
||
|
{
|
||
|
g_warning("Unable to init project file %s", path);
|
||
|
g_free(project->dir);
|
||
|
return FALSE;
|
||
|
}
|
||
|
tm_workspace_add_object(TM_WORK_OBJECT(project));
|
||
|
tm_project_open(project, force);
|
||
|
if (!project->file_list || (0 == project->file_list->len))
|
||
|
tm_project_autoscan(project);
|
||
|
#ifdef TM_DEBUG
|
||
|
tm_workspace_dump();
|
||
|
#endif
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
TMWorkObject *tm_project_new(const char *dir, const char **sources
|
||
|
, const char **ignore, gboolean force)
|
||
|
{
|
||
|
TMProject *project = g_new(TMProject, 1);
|
||
|
if (FALSE == tm_project_init(project, dir, sources, ignore, force))
|
||
|
{
|
||
|
g_free(project);
|
||
|
return NULL;
|
||
|
}
|
||
|
return (TMWorkObject *) project;
|
||
|
}
|
||
|
|
||
|
void tm_project_destroy(TMProject *project)
|
||
|
{
|
||
|
g_return_if_fail (project != NULL);
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message("Destroying project: %s", project->work_object.file_name);
|
||
|
#endif
|
||
|
|
||
|
if (project->file_list)
|
||
|
{
|
||
|
guint i;
|
||
|
for (i = 0; i < project->file_list->len; ++i)
|
||
|
tm_source_file_free(project->file_list->pdata[i]);
|
||
|
g_ptr_array_free(project->file_list, TRUE);
|
||
|
}
|
||
|
tm_workspace_remove_object(TM_WORK_OBJECT(project), FALSE);
|
||
|
g_free(project->dir);
|
||
|
tm_work_object_destroy(&(project->work_object));
|
||
|
}
|
||
|
|
||
|
void tm_project_free(gpointer project)
|
||
|
{
|
||
|
if (NULL != project)
|
||
|
{
|
||
|
tm_project_destroy(TM_PROJECT(project));
|
||
|
g_free(project);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gboolean tm_project_add_file(TMProject *project, const char *file_name
|
||
|
,gboolean update)
|
||
|
{
|
||
|
TMWorkObject *source_file;
|
||
|
const TMWorkObject *workspace = TM_WORK_OBJECT(tm_get_workspace());
|
||
|
char *path;
|
||
|
gboolean exists = FALSE;
|
||
|
|
||
|
g_return_val_if_fail((project && file_name), FALSE);
|
||
|
path = tm_get_real_path(file_name);
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message("Adding %s to project", path);
|
||
|
#endif
|
||
|
/* Check if the file is already loaded in the workspace */
|
||
|
source_file = tm_workspace_find_object(TM_WORK_OBJECT(workspace), path, FALSE);
|
||
|
if (NULL != source_file)
|
||
|
{
|
||
|
if ((workspace == source_file->parent) || (NULL == source_file->parent))
|
||
|
{
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message("%s moved from workspace to project", path);
|
||
|
#endif
|
||
|
tm_workspace_remove_object(source_file, FALSE);
|
||
|
}
|
||
|
else if (TM_WORK_OBJECT(project) == source_file->parent)
|
||
|
{
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message("%s already exists in project", path);
|
||
|
#endif
|
||
|
exists = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_warning("Source file %s is shared among projects - will be duplicated!", path);
|
||
|
source_file = NULL;
|
||
|
}
|
||
|
}
|
||
|
if (NULL == source_file)
|
||
|
{
|
||
|
if (NULL == (source_file = tm_source_file_new(file_name, TRUE)))
|
||
|
{
|
||
|
g_warning("Unable to create source file for file %s", file_name);
|
||
|
g_free(path);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
source_file->parent = TM_WORK_OBJECT(project);
|
||
|
if (NULL == project->file_list)
|
||
|
project->file_list = g_ptr_array_new();
|
||
|
if (!exists)
|
||
|
g_ptr_array_add(project->file_list, source_file);
|
||
|
TM_SOURCE_FILE(source_file)->inactive = FALSE;
|
||
|
if (update)
|
||
|
tm_project_update(TM_WORK_OBJECT(project), TRUE, FALSE, TRUE);
|
||
|
g_free(path);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
TMWorkObject *tm_project_find_file(TMWorkObject *work_object
|
||
|
, const char *file_name, gboolean name_only)
|
||
|
{
|
||
|
TMProject *project;
|
||
|
|
||
|
g_return_val_if_fail(work_object && file_name, NULL);
|
||
|
if (!IS_TM_PROJECT(work_object))
|
||
|
{
|
||
|
g_warning("Non project pointer passed to tm_project_find_file(%s)", file_name);
|
||
|
return NULL;
|
||
|
}
|
||
|
project = TM_PROJECT(work_object);
|
||
|
if ((NULL == project->file_list) || (0 == project->file_list->len))
|
||
|
return NULL;
|
||
|
else
|
||
|
{
|
||
|
guint i;
|
||
|
char *name, *name1;
|
||
|
if (name_only)
|
||
|
{
|
||
|
name = strrchr(file_name, '/');
|
||
|
if (name)
|
||
|
name = g_strdup(name + 1);
|
||
|
else
|
||
|
name = g_strdup(file_name);
|
||
|
}
|
||
|
else
|
||
|
name = tm_get_real_path(file_name);
|
||
|
for (i=0; i < project->file_list->len; ++i)
|
||
|
{
|
||
|
if (name_only)
|
||
|
name1 = TM_WORK_OBJECT(project->file_list->pdata[i])->short_name;
|
||
|
else
|
||
|
name1 = TM_WORK_OBJECT(project->file_list->pdata[i])->file_name;
|
||
|
if (0 == strcmp(name, name1))
|
||
|
{
|
||
|
g_free(name);
|
||
|
return TM_WORK_OBJECT(project->file_list->pdata[i]);
|
||
|
}
|
||
|
}
|
||
|
g_free(name);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
gboolean tm_project_remove_object(TMProject *project, TMWorkObject *w)
|
||
|
{
|
||
|
guint i;
|
||
|
|
||
|
g_return_val_if_fail((project && w), FALSE);
|
||
|
if (!project->file_list)
|
||
|
return FALSE;
|
||
|
for (i=0; i < project->file_list->len; ++i)
|
||
|
{
|
||
|
if (w == project->file_list->pdata[i])
|
||
|
{
|
||
|
tm_work_object_free(w);
|
||
|
g_ptr_array_remove_index(project->file_list, i);
|
||
|
tm_project_update(TM_WORK_OBJECT(project), TRUE, FALSE, TRUE);
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
void tm_project_recreate_tags_array(TMProject *project)
|
||
|
{
|
||
|
guint i, j;
|
||
|
TMWorkObject *source_file;
|
||
|
|
||
|
g_return_if_fail(project);
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message("Recreating tags for project: %s", project->work_object.file_name);
|
||
|
#endif
|
||
|
|
||
|
if (NULL != project->work_object.tags_array)
|
||
|
g_ptr_array_set_size(project->work_object.tags_array, 0);
|
||
|
else
|
||
|
project->work_object.tags_array = g_ptr_array_new();
|
||
|
|
||
|
if (!project->file_list)
|
||
|
return;
|
||
|
for (i=0; i < project->file_list->len; ++i)
|
||
|
{
|
||
|
source_file = TM_WORK_OBJECT(project->file_list->pdata[i]);
|
||
|
if ((NULL != source_file) && !(TM_SOURCE_FILE(source_file)->inactive) &&
|
||
|
(NULL != source_file->tags_array) && (source_file->tags_array->len > 0))
|
||
|
{
|
||
|
for (j = 0; j < source_file->tags_array->len; ++j)
|
||
|
{
|
||
|
g_ptr_array_add(project->work_object.tags_array,
|
||
|
source_file->tags_array->pdata[j]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
tm_tags_sort(project->work_object.tags_array, NULL, FALSE);
|
||
|
}
|
||
|
|
||
|
gboolean tm_project_update(TMWorkObject *work_object, gboolean force
|
||
|
, gboolean recurse, gboolean update_parent)
|
||
|
{
|
||
|
TMProject *project;
|
||
|
guint i;
|
||
|
gboolean update_tags = force;
|
||
|
|
||
|
if (!work_object || !IS_TM_PROJECT(work_object))
|
||
|
{
|
||
|
g_warning("Non project pointer passed to project update");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message("Updating project: %s", work_object->file_name);
|
||
|
#endif
|
||
|
|
||
|
project = TM_PROJECT(work_object);
|
||
|
if ((NULL != project->file_list) && (project->file_list->len > 0))
|
||
|
{
|
||
|
if (recurse)
|
||
|
{
|
||
|
for (i=0; i < project->file_list->len; ++i)
|
||
|
{
|
||
|
if (TRUE == tm_source_file_update(TM_WORK_OBJECT(
|
||
|
project->file_list->pdata[i]), FALSE, FALSE, FALSE))
|
||
|
update_tags = TRUE;
|
||
|
}
|
||
|
}
|
||
|
if (update_tags || (TM_WORK_OBJECT (project)->tags_array == NULL))
|
||
|
{
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message ("Tags updated, recreating tags array");
|
||
|
#endif
|
||
|
tm_project_recreate_tags_array(project);
|
||
|
}
|
||
|
}
|
||
|
work_object->analyze_time = time(NULL);
|
||
|
if ((work_object->parent) && (update_parent))
|
||
|
tm_workspace_update(work_object->parent, TRUE, FALSE, FALSE);
|
||
|
return update_tags;
|
||
|
}
|
||
|
|
||
|
|
||
|
#define IGNORE_FILE ".tm_ignore"
|
||
|
static void tm_project_set_ignorelist(TMProject *project)
|
||
|
{
|
||
|
struct stat s;
|
||
|
char *ignore_file = g_strconcat(project->dir, "/", IGNORE_FILE, NULL);
|
||
|
if (0 == stat(ignore_file, &s))
|
||
|
{
|
||
|
if (NULL != Option.ignore)
|
||
|
stringListClear(Option.ignore);
|
||
|
addIgnoreListFromFile(ignore_file);
|
||
|
}
|
||
|
g_free(ignore_file);
|
||
|
}
|
||
|
|
||
|
gboolean tm_project_open(TMProject *project, gboolean force)
|
||
|
{
|
||
|
FILE *fp;
|
||
|
TMSourceFile *source_file = NULL;
|
||
|
TMTag *tag;
|
||
|
|
||
|
if (!project || !IS_TM_PROJECT(TM_WORK_OBJECT(project)))
|
||
|
return FALSE;
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message("Opening project %s", project->work_object.file_name);
|
||
|
#endif
|
||
|
tm_project_set_ignorelist(project);
|
||
|
if (NULL == (fp = fopen(project->work_object.file_name, "r")))
|
||
|
return FALSE;
|
||
|
while (NULL != (tag = tm_tag_new_from_file(source_file, fp)))
|
||
|
{
|
||
|
if (tm_tag_file_t == tag->type)
|
||
|
{
|
||
|
if (!(source_file = TM_SOURCE_FILE(
|
||
|
tm_source_file_new(tag->name, FALSE))))
|
||
|
{
|
||
|
#ifdef TM_DEBUG
|
||
|
g_warning("Unable to create source file %s", tag->name);
|
||
|
#endif
|
||
|
if (!force)
|
||
|
{
|
||
|
tm_tag_free(tag);
|
||
|
fclose(fp);
|
||
|
return FALSE;
|
||
|
}
|
||
|
else
|
||
|
source_file = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
source_file->work_object.parent = TM_WORK_OBJECT(project);
|
||
|
source_file->work_object.analyze_time = tag->atts.file.timestamp;
|
||
|
source_file->lang = tag->atts.file.lang;
|
||
|
source_file->inactive = tag->atts.file.inactive;
|
||
|
if (!project->file_list)
|
||
|
project->file_list = g_ptr_array_new();
|
||
|
g_ptr_array_add(project->file_list, source_file);
|
||
|
}
|
||
|
tm_tag_free(tag);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ((NULL == source_file) || (source_file->inactive)) /* Dangling tag */
|
||
|
{
|
||
|
#ifdef TM_DEBUG
|
||
|
g_warning("Dangling tag %s", tag->name);
|
||
|
#endif
|
||
|
tm_tag_free(tag);
|
||
|
if (!force)
|
||
|
{
|
||
|
fclose(fp);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (NULL == source_file->work_object.tags_array)
|
||
|
source_file->work_object.tags_array = g_ptr_array_new();
|
||
|
g_ptr_array_add(source_file->work_object.tags_array, tag);
|
||
|
#ifdef TM_DEBUG
|
||
|
g_message ("Added tag %s", tag->name);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fclose(fp);
|
||
|
tm_project_update((TMWorkObject *) project, FALSE, TRUE, TRUE);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
gboolean tm_project_save(TMProject *project)
|
||
|
{
|
||
|
guint i;
|
||
|
FILE *fp;
|
||
|
|
||
|
if (!project)
|
||
|
return FALSE;
|
||
|
if (NULL == (fp = fopen(project->work_object.file_name, "w")))
|
||
|
{
|
||
|
g_warning("Unable to save project %s", project->work_object.file_name);
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (project->file_list)
|
||
|
{
|
||
|
for (i=0; i < project->file_list->len; ++i)
|
||
|
{
|
||
|
if (FALSE == tm_source_file_write(TM_WORK_OBJECT(project->file_list->pdata[i])
|
||
|
, fp, tm_tag_attr_max_t))
|
||
|
{
|
||
|
fclose(fp);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fclose(fp);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static void tm_project_add_file_recursive(TMFileEntry *entry
|
||
|
, gpointer user_data, guint __unused__ level)
|
||
|
{
|
||
|
TMProject *project;
|
||
|
if (!user_data || !entry || (tm_file_dir_t == entry->type))
|
||
|
return;
|
||
|
project = TM_PROJECT(user_data);
|
||
|
tm_project_add_file(project, entry->path, FALSE);
|
||
|
}
|
||
|
|
||
|
gboolean tm_project_autoscan(TMProject *project)
|
||
|
{
|
||
|
TMFileEntry *root_dir;
|
||
|
GList *file_match;
|
||
|
GList *dir_unmatch;
|
||
|
|
||
|
file_match = glist_from_array(project->sources);
|
||
|
dir_unmatch = glist_from_array(project->ignore);
|
||
|
|
||
|
if (!project || !IS_TM_PROJECT(TM_WORK_OBJECT(project))
|
||
|
|| (!project->dir))
|
||
|
return FALSE;
|
||
|
if (!(root_dir = tm_file_entry_new(project->dir, NULL, TRUE
|
||
|
, file_match, NULL, NULL, dir_unmatch, TRUE, TRUE)))
|
||
|
{
|
||
|
g_warning("Unable to create file entry");
|
||
|
return FALSE;
|
||
|
}
|
||
|
g_list_free(file_match);
|
||
|
g_list_free(dir_unmatch);
|
||
|
tm_file_entry_foreach(root_dir, tm_project_add_file_recursive
|
||
|
, project, 0, FALSE);
|
||
|
tm_file_entry_free(root_dir);
|
||
|
tm_project_update(TM_WORK_OBJECT(project), TRUE, FALSE, TRUE);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
gboolean tm_project_sync(TMProject *project, GList *files)
|
||
|
{
|
||
|
GList *tmp;
|
||
|
guint i;
|
||
|
|
||
|
if (project->file_list)
|
||
|
{
|
||
|
for (i = 0; i < project->file_list->len; ++i)
|
||
|
tm_source_file_free(project->file_list->pdata[i]);
|
||
|
g_ptr_array_free(project->file_list, TRUE);
|
||
|
project->file_list = NULL;
|
||
|
if (project->work_object.tags_array)
|
||
|
{
|
||
|
g_ptr_array_free(project->work_object.tags_array, TRUE);
|
||
|
project->work_object.tags_array = NULL;
|
||
|
}
|
||
|
}
|
||
|
for (tmp = files; tmp; tmp = g_list_next(tmp))
|
||
|
{
|
||
|
tm_project_add_file(project, (const char *) tmp->data, FALSE);
|
||
|
}
|
||
|
tm_project_update(TM_WORK_OBJECT(project), TRUE, FALSE, TRUE);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void tm_project_dump(const TMProject *p)
|
||
|
{
|
||
|
if (p)
|
||
|
{
|
||
|
tm_work_object_dump(TM_WORK_OBJECT(p));
|
||
|
if (p->file_list)
|
||
|
{
|
||
|
int i;
|
||
|
for (i=0; i < p->file_list->len; ++i)
|
||
|
{
|
||
|
fprintf(stderr, "->\t");
|
||
|
tm_work_object_dump(TM_WORK_OBJECT(p->file_list->pdata[i]));
|
||
|
}
|
||
|
}
|
||
|
fprintf(stderr, "-------------------------\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gboolean tm_project_is_source_file(TMProject *project, const char *file_name)
|
||
|
{
|
||
|
const char **pr_extn;
|
||
|
|
||
|
if (!(project && IS_TM_PROJECT(TM_WORK_OBJECT(project))
|
||
|
&& file_name && project->sources))
|
||
|
return FALSE;
|
||
|
for (pr_extn = project->sources; pr_extn && *pr_extn; ++ pr_extn)
|
||
|
{
|
||
|
if (0 == fnmatch(*pr_extn, file_name, 0))
|
||
|
return TRUE;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|